目录
八:Java多线程
1、多线程概述
-
因为现实需求生活中是多任务的,因此引出了多线程
-
但是同一个时刻,单核cpu依旧是只做一件时期
-
一个进程里面有很多个线程,至少有一个线程(即是主线程)
-
多线程的调度,是cpu依靠调度算法调度的
-
线程就是独立的执行路径
-
main线程,程序的入口
-
对同一份资源,会存在资源抢夺的问题,需要加入并发控制(上锁)
-
线程会带来额外的开销,如cpu调度时间,并发控制开销
2、线程创建
1.线程创建的几种方式
-
继承Thread类
-
实现Runnable接口(重点)
-
实现Callable接口(了解)
-
利用Lambda表达式
-
spring boot异步注解
-
线程池
2.继承Thread类
Thread简介
-
Thread类实现了Runnable接口
-
继承了Object类
Thread类使用步骤
-
继承Thread类
-
重写run方法(@Override)
-
调用start方法开启线程(不是立即执行的)
//多线程案例1 public class Demo extends Thread { @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("我在看代码-------" + i); } } public static void main(String[] args) { Thread thread1 = new Demo(); thread1.start(); for (int i = 0; i < 200; i++) { System.out.println("我在学习多线程-------" + i); } } }
3.实现Runnable接口
Runnable接口使用步骤
-
定义类实现Runnable接口
-
重写run方法
-
执行线程需要丢入Runnable接口的实现类(Thread类),调用start方法
public class Demo implements Runnable { @Override public void run() { for (int i = 0; i < 2000; i++) { System.out.println("我在看代码-------" + i); } } public static void main(String[] args) { Demo demo = new Demo(); Thread thread1 = new Thread(demo); thread1.start(); for (int i = 0; i < 2000; i++) { System.out.println("我在学习多线程-------" + i); } } }//使用该方式会更加灵活,避免java的单一继承缺点,接口可以多继承
4.实现Callable接口
使用步骤
-
实现Callable接口,需要返回类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
-
提交执行:Future<Boolean> result1 = ser.submit(t1);
-
获取结果:boolean r1 = result1.get();
-
关闭服务:ser.shutdownNow();
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; //位于juc包下,属于并发编程领域
5.静态代理模式
6.Lambda表达式
3、线程状态
1.线程礼让
-
礼让线程,让当前正在执行的线程暂停,但不阻塞
-
将线程从运行态转换为就绪态
-
让cpu重新调度,礼让不一定成果!看cpu心情
-
yield():线程让步方法
2.线程强制执行
-
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
-
可以想象成阻塞
-
join():线程强制执行
3.线程状态观测
-
线程创建:新生,尚未启动的线程
-
线程就绪:start()方法执行后,进入就绪队列,不会立即开始执行
-
线程运行:cpu调度后
-
线程阻塞:I/O阻塞,sleep()线程睡眠,notify()线程唤醒
-
线程死亡:run方法执行完毕
-
state方法观测线程状态
4.线程优先级
-
getPriority():得到线程的优先级(默认为5)
-
setPriority():设置线程的优先级
-
优先级就是线程被cpu调度的优先级(1——10)
-
优先级越高,线程被调度的可能性越大
-
高优先级的不一定先调度
5.守护线程
-
线程分为用户线程和守护线程
-
虚拟机必须确保用户线程执行完毕(main主线程,其他用户创建的线程)
-
虚拟机不用等待守护线程执行完毕(GC垃圾回收线程,日志log线程,内存监控线程等等)
4、线程同步机制
1.线程同步
-
线程同步的引出就是多个线程操作同一个资源
-
并发:同一个对象(对象锁)被多个线程同时操作
-
解决方案
-
第一步:要访问对象的线程进入这个对象的等待池,形成对象(进入对象等待池)
-
第二步:线程拿到对象资源锁(拿到锁资源)
-
注意:多个锁资源有可能造成死锁问题
-
synchronized锁:一种对象锁,使用方便
-
上锁会造成资源浪费,不上锁会产生线程安全性问题
2.同步方法和同步块
同步方法
-
方法加上了synchronized关键字就会变为同步方法
-
缺陷,一个大的synchronized方法会影响效率
-
原因:一个方法里面有A代码(只读);B代码(修改)
-
修改的代码才需要上锁,才会产生线程安全性问题
同步块
-
synchronized(obj){ }
-
obj:称为同步监视器
-
obj可以是任何对象,默认为this(本个对象,就是这个自己;在反射,类加载机制中会详细介绍)
同步监视器执行过程
-
第一个线程访问:锁定同步监视器,执行其中代码
-
第二个线程访问:发现同步监视器被锁定,无法访问,线程阻塞
-
第一个线程访问完毕:解锁同步监视器
-
第二个线程访问:发现同步监视器没有锁,然后锁定并访问
补充:
-
juc并发包:java.util.concurrent包
-
juc并发编程会深入学习
3.死锁
-
某一个同步块存在两个以上的对象锁的时候,就可能会发生死锁
-
多个线程相互占有对方所需要的锁资源,然后形成僵持
4.Lock锁
-
JDK 5.0 出现,显示定义同步锁,释放锁
-
属于juc并发包下的一个类
-
java.util.concurrent.locks.Lock接口
-
使用Lock锁需要处理一个异常
-
与synchronized(obj){ }的区别只有一点
-
第一步:lock.lock(),加锁,放在try{ }中
-
第二步:lock.unlock(),释放锁
5、线程通信
1.线程通信分析
-
wait():线程等待,与sleep不同,这个会释放锁;(sleep抱着锁睡觉)
-
notify():唤醒一个处于等待状态的线程
-
这两个方法源于Object类
2.解决方式
生产者/消费者模型
-
生产者:生成产品
-
消费者:消费产品
-
生产者将生产好的产品放入缓冲区,消费者从缓冲区里面取出产品