JUC : 并发编程工具类的使用
个人博客网:https://wushaopei.github.io/ (你想要这里多有)
一、JUC是什么
1、JUC定义
JUC,即java.util.concurrent 在并发编程中使用的工具类
2、进程、线程的定义
2.1 进程、线程是什么?
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
2.2 进程、线程例子
- 使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。
- 大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。
- word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查
二、Lock 接口
1、Lock 接口定义
Lock :Lock 是juc 下的一个接口类,用于对多线程的同步实现。
2、Lock接口的实现ReentrantLock可重入锁
2.1 Lock 的使用示例:
3、Thread 类 创建线程的方式
3.1 继承Thread 类
问题:java是单继承,资源宝贵,要用接口方式
如果使用继承Thread 类的方式来创建线程,对资源是很大的浪费。所以实际开发中不建议这么写。
3.2 new Thread()
这里通过创建线程实例的方式来创建线程,但在实际开发中,这样是不好的,对资源的浪费依旧居高不下。
3.3 通过接口实现类为形参的方式注入创建 Thread 线程
这里是通过 Tread 有参构造的方式创建一个新的线程对象。
4、实现现成的三种方法
4.1 新建类实现 runnable接口
这种方法会新增类,有更新更好的方法
4.2 匿名内部类
这种方法不需要创建新的类,可以 new 接口
4.3 lambda 表达式
这种方法代码更简洁精炼
5、案例代码:
三、java 8 新特性
1、lambda 表达式
1.1 查看例子: LambdaDemo
1.2 要求:接口只有一个方法
写法分析: 拷贝小括号(),写死右箭头->,落地大括号{...}
1.3 函数式接口
lambda表达式,必须是函数式接口,必须只有一个方法;如果接口只有一个方法java默认它为函数式接口。
为了正确使用Lambda表达式,需要给接口加个注解:
如有两个方法,立刻报错
Runnable接口为什么可以用lambda表达式?
2、接口里是否能有实现方法?
2.1 default 方法
接口里在java8后容许有接口的实现,default方法默认实现
接口里default方法可以有几个?
2.2 静态方法实现
静态方法实现:接口新增
可以有几个?
注意静态的叫类方法,能用foo去调吗?要改成Foo
3、代码
四、Callalbe 接口
1、Callable接口
1.1 是什么?
定义:Callable 是用来获得多线程的方法。
注意:(1)继承thread类(2)runnable接口
如果只回答这两个你连被问到juc的机会都没有
2、与Runnable 对比
创建新类MyThread实现runnable接口
新类MyThread2实现callable接口
面试题: callable接口与runnable接口的区别?
答:(1)是否有返回值
(2)是否抛异常
(3)落地方法不一样,一个是run,一个是call
3、Callable 怎么用?
不能直接替换Runnable 来使用,原因: thread类的构造方法根本没有Callable
解决办法:java 多态,一个类可以实现多个接口
从上图可以知道,Runnable 实现类FutureTask 接口;Callable 的实例可以作为Future Task 的构造参数。
如以上的函数一样,实现多线程的创建。
关于 运行成功后如何获得返回值?
4、Future Task
4.1 Future Task 是什么?
在项目中,用它在执行线程时,做异步调用,类似于使用 main 将一个个方法串起来,从而解决: 正常调用挂起堵塞问题。
4.2 原理
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
- 只计算一次
- get方法放到最后
4.3 代码
五、线程间通信
1、面试题:两个线程打印
两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z,
要求用线程间通信
2、例子:NotifyWaitDemo
3、线程间通信:1、生产者+消费者2、通知等待唤醒机制
4、多线程编程模板下
- 判断
- 干活
- 通知
5、synchronized实现
5.1 代码
5.2 如果换成4个线程执行呢?
如果换成4个线程会导致错误,虚假唤醒
- 原因:在java多线程判断时,不能用if,程序出事出在了判断上面
- 突然有一天加的线程进到if了,突然中断了交出控制权
- 没有进行验证,而是直接走下去了,加了两次,甚至多次
5.3 解决办法
解决虚假唤醒:查看API,java.lang.Object
中断和虚假唤醒是可能产生的,所以要用loop循环,if只判断一次,while是只要唤醒就要拉回来再判断一次。if换成while
(1)代码
(2)原理图:
6、java8新版实现
6.1 对标实现
6.2 Condition
代码:
六、线程间定制化调用通信
1、例子:ThreadOrderAccess
2、线程-调用-资源类
3、判断-干活-通知
- 有顺序通知,需要有标识位
- 有一个锁Lock,3把钥匙Condition
- 判断标志位
- 输出线程名+第几次+第几轮
- 修改标志位,通知下一个
4、代码
七、多线程锁
1、例子:ThreadOrderAccess
2、锁的 8 个问题
- 标准访问,先打印短信还是邮件
- 停4秒在短信方法内,先打印短信还是邮件
- 普通的hello方法,是先打短信还是hello
- 现在有两部手机,先打印短信还是邮件
- 两个静态同步方法,1部手机,先打印短信还是邮件
- 两个静态同步方法,2部手机,先打印短信还是邮件
- 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
- 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
3、8 锁分析
A 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
- 加个普通方法后发现和同步锁无关
- 换成两个对象后,不是同一把锁了,情况立刻变化。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下3种形式:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Synchonized括号里配置的对象
也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
4、代码
八、JUC强大的辅助类讲解
1、ReentrantReadWriteLock 读写锁
该类用于解决 写写、读写互斥的场景下,当进行写操作时,不进行读操作和其他的写操作;进行读操作时不进行写操作;确保数据 的一致性和梯次读取到数据的一致性。
(1)例子:ReadWriteLockDemo
(2)类似软件: 红蜘蛛
(3)代码:
2、CountDownLatch
(1)例子:CountDownLatchDemo
(2)原理:
- CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
- 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
- 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
(3)代码:
3、CyclicBarrier 循环栅栏
(1)例子: CountDownLatchDemo
(2)原理:
- CyclicBarrier
- 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
- 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
- 直到最后一个线程到达屏障时,屏障才会开门,所有
- 被屏障拦截的线程才会继续干活。
- 线程进入屏障通过CyclicBarrier的await()方法。
(3)代码
4、Semaphore 信号灯
(1)例子:CountDownLatchDemo
(2)原理:
在信号量上我们定义两种操作:
- acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
- 要么一直等下去,直到有线程释放信号量,或超时。
- release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
- 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
(3)代码