JUC 多线程基础复习
📣 📣 📣 📢📢📢
☀️☀️你好啊!小伙伴,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
✏️高质量技术专栏专栏链接: 微服务, netty, 单点登录, SSM , SpringCloudAlibaba等
😝公众号😝 : 想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
⏩ 当前专栏: JUC系列
什么是JUC
问:如何学习JUC?
答: 源码 + Java帮助文档 面试高频,
juc 其实就是 Java.util 包下的线程分类的工具
以往学习的线程只是:
我们使用的普通的线程代码 Thread,==Runnable== 等其实回顾JavaSE的线程知识我们可以发现,
其实我们学习线程基础的时候,也是有用到concurrent
包下的东西
比如 Runnable 和 Callable ,Callable 就是我们concurrent 包下的
还有就是 Lock
线程与进程
线程、进程、如何来解释
进程 : 一个程序 如 QQ.exe Music.exe 程序的集合
一个进程可以包含多个线程,至少包含一个线程
==Java 默认是开启两个线程 main GC==
线程: 开了一个进程 比如: typora 写字,自动保存(线程负责的)
对于Java 而言创建线程我们学习到的方法有三种 : Thread , Runnable , Callable
PS :Java本身是无法开启线程的!!!
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();
并发,并行
我们要学习并发编程,首先要了解 : 并发和并行是什么
并发(多线程操作同一资源)
- CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替。
并行(多个程序一起走)
- CPU 多核,多个线程同时执行;如果需要提高性能 : 线程池
查看一下自己的cpu核数
并发编程的本质:充分利用CPU的资源
所有的公司都很看重,就是效率,比如裁员:一个厉害的可以大于两个不再那么厉害的甚至更多
比如: 做事情的速度 高手:1s,一般人: 10s 肯定是 1s 的更加的之前也更加的有效率;
做到人员(减少),但是技术力却提高了
线程有几个状态
通过Thread 源码里的枚举类:State 中的属性可以看出来
线程有六个状态
public e
num State {
//新的线程
NEW,
//运行中
RUNNABLE,
// 阻塞
BLOCKED,
//等待,一直等
WAITING,
//等待一段时间就不等了
TIMED_WAITING,
// 终止线程
TERMINATED;
}
wait/sleep的区别
1、他们来自不同的类
wait => object
sleep => Thread
2、关于锁的释放
wait会释放锁,sleep不会释放锁
抽象理解 : sleep 睡着了没办法释放, wait 是等待,有人需要的释放
wait必须在同步代码块中使用,得有需要等的人
sleep可以在任何地方使用
3、是否需要捕获异常
wait 不需要捕获异常
sleep 需要捕获异常
Lock锁
传统 Synchronized
传统火车票案例
/**
* @projectName: JUC
* @package: Thread
* @className: SaleTicketDemo
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 20:28
* @version: 1.0
*/
public class SaleTicketDemo {
/**
* 真正的多线程开发 公司中的需要降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1、 属性。方法
* */
public static void main(String[] args) {
//并发: 多线程操作同一个资源类,把资源丢入线程
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "C").start();
}
}
//资源类 OOP
class Ticket {
//属性 方法
private int number = 50;
// 买票的方式
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
}
}
}
Lock接口
常用的一些实现类
公平锁 : 十分公平,可以先来后到 比如 前一个线程要执行30s 后面的 需要3s 后者必须等待前者执行完
非公平锁 : 十分不公平,可以根据条件来插队
如何用Lock来编写火车票Demo
- new ReentrantLock();
- Lock.lock(); 解锁
- finally => lock.unlock(); 解锁
Synchronized和Lock区别
- Synchronized 内置的java关键字,Lock是一个java类
- Synchronized 无法获取锁的状态 Lock 可以判断是否获取到锁
- Synchronized 会自动释放锁, Lock 必须手动解锁,如果不释放锁 就会死锁
- Synchronized 线程1(获得锁,阻塞),线程2(等待,死等),Lock锁不一定会等待
- Synchronized 可重入锁,不可以中断,非公平,Lock 可重入锁,可以判断锁,手动调整
- Synchronized 适合锁少量代码同步问题,Lock 适合锁大量的同步代码
锁是什么,如何判断锁的是谁
生产者和消费者问题
生产者和消费者问题:Synchronized 版
面试高频 : 单例模式,排序算法,生产者和消费者 死锁
package ProduceAndconsum;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: A
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:08
* @version: 1.0
*/
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();
}
}
//数字 资源类
// 1、 判断等待,通知
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 现在还是安全的吗?答案是肯定的不是虚假唤醒问题:我们增加到四个线程输出的时候就会发现一些问题来,输出不再是0101了
这里如何解决呢? 将 if 换成 while循环
修改之后根据官方文档的解释之后,将if改编成while
输出就又回到了正常
package ProduceAndconsum;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: A
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:08
* @version: 1.0
*/
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.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();
}
}
//数字 资源类
// 1、 判断等待,通知
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版
在新的学习中 synchronized 有 Lock 替换
那么 wait 和 notify谁来替换呢?
通过 Lock来找到 Condition
package ProduceAndconsum;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: B
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:31
* @version: 1.0
*/
public class B {
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();
}
}
//数字 资源类
// 1、 判断等待,通知
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();
} catch (InterruptedException e) {
e.printStackTrace();
} 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();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
package ProduceAndconsum;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: B
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:31
* @version: 1.0
*/
public class B {
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();
}
}
//数字 资源类
// 1、 判断等待,通知
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();
} catch (InterruptedException e) {
e.printStackTrace();
} 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();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
任何的一个新技术,都不可能是单纯的去替代老的技术,一定有优化和补充
Condition
我们更换完代码之后,可以正常输出但是还是混乱的,我们想要达到 A->B->C->D 这样输出,这个时候就引出了新的知识点
Conddition精准唤醒
package ProduceAndconsum;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: JUC
* @package: ProduceAndconsum
* @className: B
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/1 21:31
* @version: 1.0
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();
}
}
//数字 资源类
// 1、 判断等待,通知
class Data3 {
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
public void printA() {
lock.lock();
try {
// 业务,判断 执行 通知
while (number != 1) {
//等待
condition.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAA");
number = 2;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
// 业务,判断 执行 通知
while (number != 2) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBB");
number = 3;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
// 业务,判断 执行 通知
while (number != 3) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBB");
number = 1;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出的结果就达到我们的预期了