12.多线程
12.1线程简介
进程(process)和线程(Thread)
程序:指令和数据的有序集合,静态
进程:执行程序的一次执行过程,动态;系统资源的分配单位
线程:一个进程中包含多个线程;为CPU调度和执行的单位
核心概念:
-线程就是独立执行的路径;
-在程序运行时,即使没有自己创建线程,后台也有多个线程,如主线程,
gc
线程;-main()函数称为主函数,为系统的入口,用于执行整个程序;
-在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的不能干预先后执行顺序
-对于同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
-线程回带来额外的开销,如
cpu
调度时间,并发控制开销-每个线程在自己的工作内存中交互,内存控制不当会造成数据不一致
12.2 线程如何创建
三种创建方式:
1、Thread class 继承Thread类(重点)
2、Runnable接口 实现Runnable接口(重点)
3、Callable接口 实现Callable接口(了解)
1、Thread class
-自定义线程类继承Thread类
-重写run()方法,编写线程执行体
-创建线程对象,调用start()方法启动线程
注意:线程开启不一定立即执行,由CPU调度执行
package com.thread;
public class StartThread extends Thread{
//线程主入口
下载图片的栗子:
package com.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程下载图片
public class Demo02_thread extends Thread{
private String url;
private String name;
public Demo02_thread(String url,String name){
this.name=name;
this.url=url;
}
//下载图片线程的执行体
2、Runnable
-定义
MyRunnable
类实现Runnable接口-实验run()方法,编写线程执行体
-创建线程对象,调用start()方法启动线程
案例:
//买票模拟
package com.thread;
//多个线程同时操作同一个对象
public class Demo04_thread implements Runnable{
//票数
private int ticketNums = 10;
//模拟龟兔赛跑
package com.thread;
//模拟龟兔赛跑
public class Race_thread implements Runnable{
private static String Winner;
12.3 Callable接口
1、实现Callable接口,需要返回值类型
2、重写call方法,需要抛出异常
3、创建目标对象
4、创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(1);
5、提交执行:
Future<Boolean> result1 = ser.submit(t1);
6、获取结果:
boolean r1 = result.get()
7、关闭服务:
ser.shutdownNow();
package com.thread;
import com.oop.inherit.B;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class Callable_Test implements Callable<Boolean> {
private String url;
private String name;
public Callable_Test(String url,String name){
this.name=name;
this.url=url;
}
//下载图片线程的执行体
12.4静态代理模式
package com.thread;
public class StaticProxy {
public static void main(String[] args) {
// WeddingCompany weddingCompany = new WeddingCompany(new You());
// weddingCompany.HappyMarry();
You you = new You();
new Thread(new Runnable() {
12.5 Lamda表达式
避免匿名内部类定义过多
属于函数式编程概念
lamda
表达式-理解Functional Interface(函数式接口)是学习
Java8 ``lamda
表达式的关键所在-函数式接口定义:
·任何接口,如果只包含唯一一个抽象方法,那他就是一个函数式接口
·对于函数式接口,我们可以通过
lamda
表达式来创建该接口的对象
package com.lamda;
/**
* 推导lambda表达式
* */
public class Test01 {
//3、静态内部类
static class Like2 implements ILike{
package com.lamda;
//lambda表达式的简化过程
public class Test02 {
public static void main(String[] args) {
ILove love = null;
// ILove love = (int a)-> {
// System.out.println("I love yuo" + a);
// };
// love.love(520);
//简化1
love = (a)->{
System.out.println("I love yuo" + a);
};
love.love(8);
//简化2
love = a->{
System.out.println("I love yuo" + a);
};
love.love(521);
//简化3
love = a -> System.out.println("love"+a);
love.love(999);
/**
* 总结:
* 1、lambda表达式只能有一行代码的情况下才能简化为一行,多行则使用代码块
* 2、前提:接口是函数式接口(仅有一个方法)
* 3、多个参数也可以去掉参数类型(要去掉都去掉)必须加上括号
*
* */
}
}
interface ILove{
void love(int a);
}
//class Love implements ILove{
// @Override
// public void love(int a) {
// System.out.println("I love yuo1"+a);
// }
//}
12.6线程状态
线程方法:
方法 | 说明 |
---|---|
setPriority(int newPriority) |
更改线程的优先级 |
static void sleep(long millis) |
在指定毫秒数内让当前正在执行的线程休眠 |
void join() |
等待该线程终止 |
static void yield() |
暂停当前正在执行的线程对象,并执行其它线程 |
void interrupt() |
中断线程(不建议使用) |
boolean isAlive() |
测试线程是否处于活动状态 |
停止线程:
-建立一个标志位进行终止变量;当flag=false则终止线程
package com.thread;
public class TestStop implements Runnable{
//1、定义一个标志位
private boolean flag = true;
线程休眠
-sleep(时间)指定当前线程阻塞的毫秒数;
-sleep存在异常
InterruptedException
;-sleep时间到达后时间进入就绪状态
-sleep可以模拟网络延时
-每一个对象都有一个锁,sleep不会释放锁
package com.thread;
public class TestStop implements Runnable{
//1、定义一个标志位
private boolean flag = true;
package com.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟倒计时
public class TestStop02 {
public static void main(String[] args) {
// try {
// tenDown();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//打印当前时间间隔一秒
Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());//更新时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
线程强制执行——Join:
-Join合并线程后,待此线程合并完成后,在执行其它线程,而其它线程则阻塞
package com.thread;
public class TestJoin implements Runnable{
12.7线程优先级
package com.thread;
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
//先设置优先级,再启动
t1.setPriority(Thread.MIN_PRIORITY); //1
t1.start();
t2.setPriority(2);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY); //10
t4.start();
}
}
class MyPriority implements Runnable{
12.8守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
package com.thread;
//测试守护线程
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Self self = new Self();
Thread thread = new Thread(god);
thread.setDaemon(true); //默认值是false表示用户线程
thread.start();//上帝守护进程启动
new Thread(self).start();//用户线程启动
}
}
//上帝
class God implements Runnable{
12.9线程同步
并发:同一个对象被多个线程同时操作。(形成条件:队列加锁)
由于统一进程的多个线程共享同一块存储空间(方便但冲突),为了数据在方法中访问的正确性,必须加上锁机制(synchronized,当一个线程获得对象的排他锁,独占资源,其它线程必须等待,使用后释放锁即可;(公共厕所使用一个原理,代码源于生活)
-一个线程持有锁会导致其它需要锁的线程处于挂起状态
-在多线程竞争下加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
-如果一个优先级高的线程等待和一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
由于我们可以通过private关键字来保证数据只能被方法访问,针对方法提出一套机制
synchronized关键字:synchronized方法和synchronized块
同步方法:public synchronized void methd(int args){}
影响效率
synchronized方法控制对“对象的访问”,每个对象对应着一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行。否则线程堵塞,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
买票安全性改进
package com.thread.syn;
//不安全的买票,出现负数
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket bt = new BuyTicket();
new Thread(bt,"me").start();
new Thread(bt,"you").start();
new Thread(bt,"YellowCow").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNum = 10;
private boolean flag = true;
银行取钱安全化操作:
package com.thread.syn;
//不安全的取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"Study");
Drawing you = new Drawing(account, 50, "You");
Drawing girlFriend = new Drawing(account, 70, "GirlFriend");
you.start();
girlFriend.start();
}
}
//定义账户,属性有余额、卡名;有参构造
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取款
class Drawing extends Thread{
Account account;
//取钱数量
int drawingMoney;
//还剩下多少钱
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
//取钱
//synchronized 默认锁的是this
测试JUC安全类型集合
package com.thread.syn;
import java.util.concurrent.CopyOnWriteArrayList;
//测试JUC安全类型集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
12.10 死锁
多个线程各自占有一些公共资源,并且相互等待其它线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止的情形。某一个同步块同时拥有两个以上对象的锁就可能会发生死锁的问题
package com.thread.syn;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持状态
public class DeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0,"Rose");
MakeUp g2 = new MakeUp(1, "Ruse");
g1.start();
g2.start();
}
}
//口红
class LipStick{
}
//镜子
class Mirror{
}
//化妆
class MakeUp extends Thread{
//需要的资源只有一份,用static来保证只有一份
static LipStick lipStick = new LipStick();
static Mirror mirror = new Mirror();
int choice;//选择
String UserName;//化妆的人
MakeUp(int choice,String UserName){
this.choice=choice;
this.UserName=UserName;
}
死锁避免的方法:
产生死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用
2、请求与保持条件:一个进程因请求资源而阻塞,对以获得的资源保持不放
3、不剥夺条件:进程已获得资源,在未使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源的关系
破坏一个就可以避免死锁发生
高级锁Lock ReentranLock:
package com.thread.syn;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//测试Lock锁
public class TestLock {
public static void main(String[] args) {
TestLock2 lock2 = new TestLock2();
new Thread(lock2).start();
new Thread(lock2).start();
new Thread(lock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNum = 20;
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
12.11线程通信
Java提供的线程通信解决办法:
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其它线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有的调用wait()方法的线程,按优先级别来调度 |
//信号灯法解决生产者、消费者问题;标志位
package com.thread.syn;
//测试生产者、消费者问题2:信号灯法,标志位解决
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者--演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
//管程法;利用缓冲区解决生产者、消费者问题
package com.thread.syn;
//测试生产者消费者--》利用缓冲区解决问题:管程法
//需要生产者、消费者、缓冲区
public class TestPC {
public static void main(String[] args) {
SysContainer container = new SysContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread{
SysContainer container;
public Producer(SysContainer container){
this.container=container;
}
//生产
12.12线程池
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取即可,使用完放回池中,
可以避免频繁的创建销毁线程,实现重复利用。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源的消耗(重复利用线程池中的线程,不需要每次都创建)
便于线程的管理
核心池的大小
最大线程数量
线程没有任务时最多保持多长时间会终止
线程池相关的
API
:ExecutorService
&Executors
ExecutorService
:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runable command) //执行任务/命令;没有返回值,一般用来执行Runable
<T>Future<T>submit(Callable<T>task) //执行任务,有返回值,一般来执行Callable
void shutdown() //关闭连接池
Executors
:工具类、线程池的工厂类,用于创建和返回不同类型的线程池
12.13总结代码
package com.thread.sum;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
//总结线程创建
public class SumUpThread {
public static void main(String[] args) {
//1、继承Thread类是调用
new MyThread1().start();
//2、继承Runnable接口是调用
new Thread(new MyThread2()).start();
//3、继承Callable接口调用方法
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
new Thread(futureTask).start();
try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1、继承Thread类
class MyThread1 extends Thread{