多线程进阶-JUC并发
文章目录
-
- **多线程进阶-JUC并发**
- **1、搭建基础环境**
- **2、什么是JUC**
- **3、线程和进程**
- **4、Lock锁(重点)**
- **5、生产者和消费者问题**
- **6、8锁现象**
- **7、集合类不安全**
- **8、Callable**
- **9、常用的辅助类(必会)**
- **10、读写锁**
- **11、阻塞队列**
- **12、线程池(重点)**
- **13、四大函数式接口(必须掌握)**
- **14、Stream流式计算**
- **15、ForkJoin详解**
- **16、异步回调**
- **17、JMM**
- **18、Volatile**
- **19、彻底玩转单例模式**
- **20、深入理解CAS**
- **21、原子引用**
- **22、各种锁的理解**
1、搭建基础环境
1.1.新建一个Maven工程com.can
1.2.POM文件引入依赖
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
1.3.检查Project Settings的Project language level版本是:8- Lambdas, type annotations etc.
还有Modules-language level版本也要是:8- Lambdas, type annotations etc.
这样才能确保1.8的新特性可以使用
1.4.检查编译版本Settings-Java Compiler-Module版本为:1.8
准备环境就OK了
2、什么是JUC
源码+官方文档
面试高频问!
java.util.concurrent
java.util.concurrent.atomic 原子性
java.util.concurrent.locks lock
java.function
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iw1NZVlj-1587409579763)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200411154007524.png)]
java.util工具包
业务:普通的线程代码 Thread
**Runnale:**没有返回值,效率相比入 Callable 相对较低!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zrt3QOcL-1587409579766)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200414144746092.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a4VfGcN1-1587409579768)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200414145137417.png)]
3、线程和进程
线程、进程,如果不能一句话说出技术,就是不扎实
进程:
- 一个程序,例如QQ.exe,Music.exe 程序的集合
- 一个进程往往可以包含多个线程,至少包含一个。
- Java默认有几个线程?2个,一个是main,一个是GC
线程:
- 开了一个进程Typora,写字,自动保存(线程负责的)
- Thread、Runnable、Callable
Java真的可以开启线程吗?开不了的
Thread源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//本地方法,底层的C++,java无法操作硬件
private native void start0();
native关键字,它只能通过本地方法去调用开启线程。
并发、并行
并发编程:并发、并行
并发(多线程操作同一资源):
- CPU一核,模拟出多条线程,天下武功,唯快不破,快速交替形成一种假象
并行(多个人一起行走)
- CPU多核,多个线程可以同时进行。线程池
package com.can;
public class TestAvailableProcessors获取CPU核数 {
public static void main(String[] args) {
//获取CPU的核数,
//CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
所有公司都很看重
企业,挣钱=>提高效率,裁员,找一个厉害的人顶替三个不怎么样的人。
人员(减)、技术成本(高)
线程有几个状态
/**
* 新生
*/
NEW,
/**
*运行
*/
RUNNABLE,
/**
*阻塞
*/
BLOCKED,
/**
* 等待,死死得等
*/
WAITING,
/**
* 超时等待
*/
TIMED_WAITING,
/**
* 终止
*/
TERMINATED;
6个状态
wait/sleep区别
-
来自不同得类:
- wait=>Object
- sleep=>Thread
-
关于锁得释放
- wait 会释放锁
- sleep 抱着锁睡觉,不会释放锁
-
使用的范围不同
- wait 必须在同步代码块中
- sleep 可以在任何地方睡
-
是否需要捕获异常
-
wait 需要捕获异常
-
sleep 也需要捕获异常
-
4、Lock锁(重点)
传统的Synchronized
synchronized(深Q赖死)
package com.can;
//基本的卖票例子
import java.util.concurrent.locks.ReentrantLock;
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性、方法
*/
public class SaleTicket {
public static void main(String[] args) {
//并发,多线程操作同一资源类,把资源类丢入线程
Ticket ticket = new Ticket();
for (int i = 0; i < 60; i++) {
new Thread(()->ticket.sale(),"A").start();
}
for (int i = 0; i < 60; i++) {
new Thread(()->ticket.sale(),"B").start();
}
for (int i = 0; i < 60; i++) {
new Thread(()->ticket.sale(),"C").start();
}
}
}
//资源类 OOP
class Ticket{
//属性、方法
private int number=50;
//卖票的方式
// synchronized 本质:队列,锁
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);
}
}
}
Lock接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZXEA3zXl-1587409579771)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200414182059512.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PtRmgmu9-1587409579773)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200414182556771.png)]
公平锁:
- 十分公平,可以先来后到
非公平锁:
- 可以插队(默认是非公平锁)
package com.can;
//基本的卖票例子
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性、方法
*/
public class SaleTicketLock {
public static void main(String[] args) {
//并发,多线程操作同一资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
for (int i = 0; i < 60; i++) new Thread(ticket::sale,"A").start();
for (int i = 0; i < 60; i++) new Thread(()->ticket.sale(),"B").start();
for (int i = 0; i < 60; i++) new Thread(()->ticket.sale(),"C").start();
}
}
//资源类 OOP
//lock三部曲
//1.new ReentrantLock();
//2.lock.lock();加锁
//3.finally=>lock.unlock();解锁
class Ticket2{
//属性、方法
private int number=50;
Lock lock = new ReentrantLock();
//卖票的方式
public void sale(){
lock.lock();
try {
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);
}
}finally {
lock.unlock();
}
}
}
Synchronized和Lock锁区别:
- Synchronized 内置的Java关键字;Lock是一个Java类。
- Synchronized 无法判断获取锁的状态;Lock可以判断是否获取到了锁。
- Synchronized 会自动释放锁;Lock必须要手动释放!如果不释放锁,死锁。
- Synchronized 线程1(获得锁)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去。
- Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置,默认非公平)。
- Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的同步代码;
5、生产者和消费者问题
Synchronized版
package com.PC;
//线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒
//线程交替执行 A线程,B线程操作同一个变量, num=0
//A num+1
//B num-1
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//等待,业务,通知
//数字,资源类
class Data{
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//通知其他线程,我-1完毕了
this.notifyAll();
}
}
问题存在:A,B,C,D或者两个以上的线程就会存在问题!虚假唤醒
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K4Mgti7n-1587409579775)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200416175427829.png)]
if改为while判断,解决虚假唤醒问题
防止虚假唤醒的代码
package com.PC;
//线程之间的通信问题:生产者和消费者问题!等待唤醒,通知唤醒
//线程交替执行 A线程,B线程操作同一个变量, num=0
//A num+1
//B num-1
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
//等待,业务,通知
//数字,资源类
class Data{
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
while(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while(number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//通知其他线程,我-1完毕了
this.notifyAll();
}
}
JUC版
通过Lock找到Condition
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toFvoSBl-1587409579777)(C:\Users\Can\AppData\Roaming\Typora\typora-user-images\image-20200416190301539.png)]
代码实现
package com.PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//condition.await();//等待
//condition.signalAll();//唤醒全部
public class B_Lock版 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//等待,业务,通知
//数字,资源类
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await();//等待
//condition.signalAll();//唤醒全部
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while(number!=0){
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//通知其他线程,我+1完毕了
condition.signalAll();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while(number==0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"==>"+number);
//通知其他线程,我-1完毕了
condition.signalAll();
}finally {
lock.unlock();
}
}
}
日志打印
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充;
可以通过Condition精准的通知和唤醒线程,实现有序的唤醒
Condition实现精准的通知唤醒
condition(康第悉no)
-
Lock
替换synchronized
方法和语句的使用,Condition
取代了对象监视器方法的使用 -
一个
Condition
实例本质上绑定到一个锁。要获得特定Condition
实例的Condition实例,请使用其newCondition()
方法。package com.can.PC; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //A执行完,调用B,B执行完,调用C,C执行完,调用A public class C_Condition精准通知唤醒 { public static void main(String[] args) { Data3 data3 = new Data3(); new Thread(()->{ for (int i = 0; i < 10; i++) { data3.printA(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data3.printB(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data3.printC(); } },"C").start(); } } //资源类 //Lock锁 class Data3{ private Lock lock = new ReentrantLock