概述
1.什么是JUC
1.1 简介
JUC即 java.util.concurrent
工具包,这是一个处理线程的工具包,JDK1.5开始出现
1.2 进程与线程
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程是程序执行的最小单位。
1.3 线程的状态
Java线程可以处于以下状态之一:
- 新建状态(New):当线程对象被创建时,线程处于新建状态。此时线程没有开始执行,也没有分配到任何系统资源。
- 就绪状态(Runnable):当线程被start()方法调用后,线程进入就绪状态。此时线程已经分配到了系统资源,但是还没有开始执行。线程处于就绪状态时,它可能正在等待CPU时间片,以便被调度执行。
- 运行状态(Running):当线程获得CPU时间片并开始执行时,线程处于运行状态。
- 阻塞状态(Blocked):线程处于阻塞状态时,它暂时停止了执行。线程可能因为某些原因被阻塞,例如等待I/O操作完成、等待获取一个锁、等待其他线程执行完成等。
- 等待状态(Waiting):线程处于等待状态时,它暂时停止了执行。此时线程需要等待其他线程发出特定的通知或者等待特定时间后自动唤醒。
- 计时等待状态(Timed Waiting):线程处于计时等待状态时,它暂时停止了执行,但是会在一定时间内自动唤醒。例如,线程调用sleep()方法或者等待超时时就会进入计时等待状态。
- 终止状态(Terminated):当线程执行完run()方法并退出时,线程处于终止状态。此时线程已经释放了所有的系统资源。
wait
和sleep
sleep
是Thread
的静态方法,wait
是Object
的方法,任何实例都可以调用sleep
不会释放锁,它也不需要占用锁。wait
会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized
中)- 它们都可以被
interrupted
方法中断
1.4 并发与并行
并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
例如:春运抢票 电商秒杀
并行:多项工作一起执行,之后再汇总
例如:泡方便面,烧水的同时,一边撕调料导入桶中
1.5 管程
Monitor 监视器 -> 锁
是一种同步机制,保证再同一时间,只有一个线程访问被保护的数据或代码
JVM中的同步基于进入和退出(monitorenter/ monitorexit)操作,使用管程对象实现
1.6 用户线程和守护线程
用户线程:自定义线程。程序运行时创建的普通线程,它们不会影响程序的退出。当所有的用户线程都执行完毕后,Java虚拟机会退出。
守护线程:比如垃圾回收。在程序运行时在后台运行,它的作用是为其他线程提供服务支持。守护线程的生命周期与程序的生命周期相同,当所有的用户线程执行完毕后,守护线程也会自动退出。
Lock接口
2.Lock接口
2.1 复习synchronized
多线程编程步骤(上部)
第一步:创建资源类,在资源类创建属性和操作方法
第二步:创建多个线程,调用资源类的操作方法
售票案例:
package com.bbedu.juc.sync;
/**
* @projectName: jvm-study
* @package: com.bbedu.juc
* @className: SaleTicket
* @author: BBChen
* @description: 演示synchronized基本使用方法
* @date: 2023/5/22 20:22
* @version: 1.0
*/
class Ticket{
// 票数
private int number = 30;
// 操作方法
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName()
+ " 卖出第:" + (number--) + "张票 剩余:" + number);
}
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}, "AA").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}, "BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}, "CC").start();
}
}
2.2 什么是Lock
Lock锁实现提供了比使用同步方法和渔具可以获得更广泛的锁操作。
Lock
和synchronized
的区别
- Lock不是Java语言内置的, synchronized是Java语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问
- Lock和synchronized有一点非常大的不同,采用synchrgnized不需要用户去手动释放锁,当synchronized方法或者synchronized 代码块执行完之后系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用
synchronized时,等待的线程会一直等待下去,不能够响应中断 - 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
Lock实现的卖票示例:
package com.bbedu.juc.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @projectName: jvm-study
* @package: com.bbedu.juc.lock
* @className: LSaleTicket
* @author: BBChen
* @description: TODO
* @date: 2023/5/22 20:39
* @version: 1.0
*/
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);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
LTicket ticket = new LTicket();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "AA").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "BB").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "CC").start();
}
}
线程间通信
多线程编程步骤(中部)
第一步:创建资源类,在资源类创建属性和操作方法
第二步:在资源类操作方法
- 判断
- 干活
- 通知
第三步:创建多个线程,调用资源类的操作方法
虚假唤醒问题
为什么会出现?
当一个线程在等待某个条件时,如果该条件发生了变化,但另一个线程已经在修改该条件并释放锁,则等待的线程可能被错误地唤醒。
使用synchronized
关键字时,必须使用while
判断条件,而非if
synchronized (lock) {
while (!condition) {
lock.wait();
}
// 执行操作
}
多线程编程步骤(下部)
第一步:创建资源类,在资源类创建属性和操作方法
第二步:在资源类操作方法
- 判断
- 干活
- 通知
第三步:创建多个线程,调用资源类的操作方法
第四步:防止虚假唤醒问题
线程间定制化通信
代码演示:
package com.bbedu.juc.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource{
// 创建标志位
private int flag = 1; // 1 AA 2 BB 3 CC
// 创建Lock锁
private Lock lock = new ReentrantLock();
// 创建3个condition
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5(int loop) throws InterruptedException {
lock.lock();
try {
// 判断 -> 防止虚假唤醒while
while (flag != 1){
condition1.await();
}
// do something
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() +
" 循环次数: " + i + " 轮数:" + loop);
}
// notify
flag = 2; // 修改标志位为2
condition2.signal(); // 通知BB
}finally {
lock.unlock();
}
}
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
// 判断 -> 防止虚假唤醒while
while (flag != 2){
condition2.await();
}
// do something
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() +
" 循环次数: " + i + " 轮数:" + loop);
}
// notify
flag = 3; // 修改标志位为3
condition3.signal(); // 通知CC
}finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
// 判断 -> 防止虚假唤醒while
while (flag != 3){
condition3.await();
}
// do something
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() +
" 循环次数: " + i + " 轮数:" + loop);
}
// notify
flag = 1; // 修改标志位为1
condition1.signal(); // 通知AA
}finally {
lock.unlock();
}
}
}
/**
* @projectName: jvm-study
* @package: com.bbedu.juc.lock
* @className: CustomizedThreadCommunication
* @author: BBChen
* @description: 演示线程间的定制化通信
* @date: 2023/5/24 13:40
* @version: 1.0
*/
public class CustomizedThreadCommunication {
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) {
throw new RuntimeException(e);
}
}
}, "AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "CC").start();
}
}
评论