学习笔记-JUC
1.JUC概述
1.什么是JUC?
在Java中,JUC是关于线程的。JUC是java。util。concurrent工具包。
2.线程和进程
线程与进程
进程:资源分配的最小单位
线程:程序执行的最小单位
线程的状态(5种)
- NEW(新建)
- RUNNABLE(准备就绪)
- BLOCKED(阻塞)
- WAITING(不见不散)
- TIMED_WATING(过时不候)
- TERMINATED(终结)
wait和sleep
1.sleep是Thread的静态方法,wait是Object的方法,任何实例对象都能调用。
2.sleep不会释放锁,也不需要占用锁。wait会释放锁,但调用它前提是当前线程占有锁(即代码要在synchronized中)
3.它们都可以被interrupted方法中断
并发和并行
管程
2.Lock接口
复习synchronized
LOCK接口不让等待的线程一直无期限地等待,可以通过设置一定的时间或者是中断处理机制
讲解LOCK接口之前涉及另一个锁机制synchronized
创建线程的多种方式
- 继承Thread类(用的少,Java是单继承,很珍贵)
- 实现Runnable接口
- 使用Callable接口
- 使用线程池
结合synchronized多线程的编程步骤主要是:(高内聚低耦合)
synchronized作用范围
synchronized是Java的关键字,是一种同步锁。修饰一下对象:
1.修饰一个代码块
synchronized(this) {
}
2.修饰一个方法
3.修改一个静态方法
4.修改一个类
synchronized卖票例子
多线程编程步骤
1.创建资源类,在资源类创建属性和操作方法
2.创建多个线程,调用资源类的操作方法
package com.tabjin.juc.sync;
/**
* @author Tabjin
* @program JUC
* @description synchronized案例
*/
// 1.创建资源类,定义属性和操作方法
class Ticket {
// 票数
private int number = 30;
// 操作方法:卖票
public synchronized void sale() {
// 判断:是否有票
if (number > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出 " + (number--) + "剩下:" + number);
}
}
}
public class SaleTicket {
// 2.创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
// 创建Ticket对象
Ticket ticket = new Ticket();
// 创建3个线程
new Thread(new Runnable() {
@Override
public void run() {
// 调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
}, "AA").start();
new Thread(new Runnable() {
@Override
public void run() {
// 调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
}, "BB").start();
new Thread(new Runnable() {
@Override
public void run() {
// 调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
}, "CC").start();
}
}
代码执行的时候,AA、BB与CC三个线程执行堆叠在一起,这是因为不同线程不同对象启动执行,都可以访问。
Tip
具体创建线程的方法是new Thread(new Runnable({ //重写run方法},线程名)
或者可以通过lambda表达式这样创建
new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } } },"AA").start();
Lock接口
Lock接口介绍
synchronized的上锁和解锁是由其自动完成,实际上上锁和解锁也可以使用Lock接口手动控制。
Lock为锁和等待条件提供一个框架的接口和类,不同于内置同步和监视器, LOCK是类,可通过类实现同步访问,多个接口实现类:可重入锁等
- Lock与synchronized区别
- Lock不是Java内置的,synchronized是Java关键字。Lock是一个类,通过这个类可以实现同步访问。
- Lock与synchronized有一个非常大的不同,采用synchronized不需要用户去手动释放锁,当syncchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放锁的占用;而Lock则必须要用户手动释放锁,否则可能出现死锁。
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁;而Lock发生异常时,若没有主动通过unlock()去释放锁,则很可能造成死锁,因此使用Lock时需要在finally块中释放锁。
- Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能响应中断
- 通过Lock可以知道有没有成功获取锁,而synchronized做不到
- Lock可以提高多个线程进行读操作的效率。
Lock实现可重入锁
Lock的编程步骤同synchronized
1.创建资源类,在资源类中船舰属性和操作方法
2.创建多个线程,调用资源类的操作方法
可重入锁的代码定义private final ReentrantLock lock = new ReentrantLock(true);
上锁lock.lock();
解锁lock.unlock();
上锁与解锁中的代码如果出现异常,解锁会执行不了,所以最好加try..finally
package com.tabjin.juc.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Tabjin
* create at 2022/3/9 09:48
* @program JUC
* @description
*/
// 1.创建资源类,定义属性和操作方法
class LTicket {
// 票数
private int number = 30;
// 创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
// 操作方法:卖票
public void sale() {
// 上锁
lock.lock();
try {
// 判断:是否有票
if (number > 0) {
System.out.println(Thread.currentThread().getName() + ":卖出 " + (number--) + "剩下:" + number);
}
} finally {
// 解锁
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
// 2.创建多个线程,调用资源类的操作方法
LTicket ticket = new LTicket();
// 创建3个线程
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "CC").start();
}
}
start不一定是即时创建的,start有个native,交给操作系统执行。
Lock常用接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock()
方法用来获取锁
- 如果锁已被其他线程获取,则进行等待
- 发生异常不会自动解锁,需用在 try{}catch{}块中进行
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
Condition 类
也可以实现等待/通知模式
关键字 synchronized
与 wait()/notify()
这两个方法一起使用可以实现等待/通知模式,Lock 锁的 newContition()
方法返回 Condition 对象,Condition 类也可以实现等待/通知模式
用 notify()
通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类
可以进行选择性通知, Condition 比较常用的两个方法:
await()
会使当前线程等待,同时会释放锁,当其他线程调用 signal()
时,线程会重新获得锁并继续执行
signal()
用于唤醒一个等待的线程
ReentrantLock
是唯一实现了 Lock 接口的类,并且 ReentrantLock
提供了更多的方法
ReentrantLock
可重入锁
ReentrantReadWriteLock
里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()
和 writeLock()
用来获取读锁和写锁
writeLock();
来获取读锁
readLock();
获取写锁
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
3.线程间通信
线程间通信的模型有两种:共享内存和消息传递
线程间的通信具体步骤:(涉及上中下部)
- 创建资源类,在资源类中船舰属性和操作方法
- 在资源类操作方法:判断、操作、通知
- 创建多个线程,调用资源类的操作方法
- 防止虚拟唤醒问题
1.synchronized实现线程间通信
操作线程的时候,等待线程使用wait()
通知另外的线程操作用notify()
、notifyAll()
假设有两个线程,该线程在执行过程中,判断值(不是该值等待,让其他线程抢),操作值,通知另外一个线程的调度。
通过使用两个线程对0这个值操作,一个线程加1,一个线程减1,交替实现多次。
package com.tabjin.juc.sync;
/**
* @author Tabjin
* @program JUC
* @description
*/
// 第一步 创建资源类,定义属性和操作方法
class Share {
// 初始值
private int number = 0;
// +1操作
public synchronized void increment() throws InterruptedException {
// 第二步 判断 干活 通知
if (number != 0) {// 判断number是否是0,如果不是0,等待
this.wait();
}
// number是0,+1操作
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
// 通知其他线程
this.notify();
}
// -1操作
public synchronized void decrement() throws InterruptedException {
if (number != 1) {// 判断number是否是1,如果不是1,等待
this.wait();
}
// number是1,-1操作
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
// 通知其他线程
this.notify();
}
}
public class ThreadDemo1 {
// 第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.increment();// +1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decrement();// -1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
}
}
2.虚假唤醒问题
如果使用多个线程,添加额外两个线程,且操作要依次执行
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.incr(); //+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.decr(); //-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
主要是虚拟唤醒导致:如果一个线程执行完毕后,通知其他线程,该线程又进入等待睡眠,可能会因为某些原因被唤醒后,if结构的语句就不会判断了,一直往下执行,所以需要将if换成while结构,每次都判断。因为wait在哪里睡眠就在哪里被唤醒,结果被某个异常唤醒了后回不去了,if结构不会在判断了,需要更改为while。
主要问题出现在下面的代码
if (number != 0) {// 判断number是否是0,如果不是0,等待
this.wait();// 在哪里睡,在哪里醒
}
// 需要将if改为while,每次都wait
while (number != 0) {// 判断number是否是0,如果不是0,等待
this.wait();// 在哪里睡,在哪里醒
}
2.Lock实现线程间通信
使用lock先要创建锁的对象以及通知的对象
放置在资源类中
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
上锁lock.lock();
解锁lock.unlock();
以下都为condition类:
唤醒所有等待的线程signalAll(),带上类名condition.signalAll();
唤醒一个等待线程signal(),带上类名,condition.signal();
造成当前线程在接到信号或者被中断之前一直处于等待状态await(),带上类名,condition.await();
同样是上面的案例题目换成lock
比如一个加1操作
package com.tabjin.juc.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Tabjin
* @program JUC
* @description Lock实现线程间通信
*/
// 第一步 创建资源类,定义属性和操作方法
class Share {
// 初始值
private int number = 0;
// 创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// +1操作
public void increment() throws InterruptedException {
// 上锁
lock.lock();
try {
// 第二步 判断 干活 通知
// 判断
while (number != 0) {// 判断number是否是0,如果不是0,等待
condition.await();
}
// 干活 number是0,+1操作
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
// 通知其他线程
condition.signalAll();
} finally {
// 解锁
lock.unlock();
}
}
// -1操作
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number != 1) {// 判断number是否是1,如果不是1,等待
condition.await();
}
// number是1,-1操作
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
// 通知其他线程
condition.signalAll();
} finally {
// 解锁
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.increment();// +1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.increment();// +1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.increment();// +1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decrement();// -1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "DD").start();
}
}
4.线程间定制化通信
所谓定制化通信,需要让线程进行一定的顺序操作。
案列:启动三个线程,按照如下要求:
AA线程打印5次,BB线程打印10次,CC线程打印15次,一共进行10轮。
具体思路:
每个线程添加一个标志位,是该标志位则执行操作,并且修改为下一个标志位,通知下一个标志位的线程。
创建一个可重入锁private Lock lock = new ReentrantLock();
分别创建三个开锁通知private Condition c1 = lock.newCondition();
具体资源类中的A线程代码操作
上锁,(执行具体操作(判断、操作、通知),解锁)放于try
、finally
。

package com.tabjin.juc.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Tabjin
* @program JUC
* @description 线程间定制化通信
*/
// 第一步 创建资源类
class ShareResource {
// 标志位
private int flag = 1;// 1 A 2B 3C
// 创建Lock锁
private Lock lock = new ReentrantLock();
// 创建3个Condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
// 打印5次
public void print5(int loop) throws InterruptedException {
// 上锁
lock.lock();
try {
// 第二步
// 判断
while (flag != 1) {
// 等待
c1.await();
}
// 干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i + "轮数" + loop);
}
// 通知
flag = 2;// 修改标志位
c2.signal();// 通知BB线程
} finally {
// 解锁
lock.unlock();
}
}
// 打印10次
public void print10(int loop) throws InterruptedException {
// 上锁
lock.lock();
try {
// 第二步
// 判断
while (flag != 2) {
c2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i + "轮数" + loop);
}
// 通知
flag = 3;// 修改标志位
c3.signal();// 通知CC线程
} finally {
// 解锁
lock.unlock();
}
}
// 打印15次
public void print15(int loop) throws InterruptedException {
// 上锁
lock.lock();
try {
// 第二步
// 判断
while (flag != 3) {
c3.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i + "轮数" + loop);
}
// 通知
flag = 1;// 修改标志位
c1.signal();// 通知AA线程
} finally {
// 解锁
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 0; i <= 10; i++) {
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i <= 10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i <= 10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
}
}
5.集合的线程安全
在讲解线程安全的之前,先讲解线程不安全的实例
其中的代码主要涉及一些知识点
1.java中Arrays.toString()详细分析(全)https://blog.csdn.net/weixin_47872288/article/details/116782849
2.java之UUID.randomUUID().toString()详细解析(全)https://blog.csdn.net/weixin_47872288/article/details/119573423
以及涉及更细微的函数3.javaSE从入门到精通(全)https://blog.csdn.net/weixin_47872288/article/details/119009945?spm=1001.2014.3001.5502
1.集合线程不安全演示
2.解决方案-Vector
3.解决方案-Collections
4.解决方案-CopyOnWriteArrayList
6.多线程锁
7.
8.JUC强大的辅助类
9.ReentranReadWritedLock读写锁
10.BlockingQueue阻塞队列
11.ThreadPool线程池
12.分支合并框架
13.异步回调
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构