JUC并发编程
1、什么是JUC
JUC 是 java.util.concurrent 包名的简写,是关于并发编程的API,与 JUC 相关的有三个包:
- java.util.concurrent、
- java.util.concurrent.atomic、
- java.util.concurrent.locks。
1.1、线程和进程
进程是一个程序的集合,如:QQ.exe,进程往往包含多个线程(至少包含一个)。
- Java 默认有几个线程?
2个,main 线程和 GC 线程
- Java 真的可以开启线程吗?
通过源码可以分析出 Java 不能开启线程,需要通过本地方法调用底层 C++ 实现。 同理 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资源
package com.liuxiang;
public class Test {
public static void main(String[] args) {
//在Java中可以通过此方法 查看CPU核心数量
int i = Runtime.getRuntime().availableProcessors();
System.out.println(i);
}
}
8
1.2、多线程
- 线程六大状态
阅读源码可知,线程有以下六大状态:
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//死亡
TERMINATED;
}
- wait和sleep的区别
- 来自不同的类:wait 来自 Object 类,sleep 来自线程类
- 关于锁的释放 :wait 会释放锁,sleep 不会释放锁
- 使用范围不同:wait 必须在同步代码块中使用,sleep 任何地方都可以睡眠
- 是否需要捕获异常:wait 不需要捕获异常,sleep 必须捕获异常
Thread.sleep(1000); //睡眠1秒
//在实际工作中,不会使用sleep让线程睡眠
//而使用TimeUnit让线程睡眠
TimeUnit.DAYS.sleep(1);//睡眠一天
TimeUnit.SECONDS.sleep(2);//睡眠两秒
2、锁
2.1、synchronized
synchronized
是解决线程不安全的关键,它的实现原理就是队列和锁。
由于我们可以通过 private
关键词来保证数据变量(对象)只能被方法访问,所以我们只需要针对方法提出一套机制。这套机制就是 synchronized
关键字。
synchronized
方法控制对对象的访问,每个对象对应一把锁,每一个 synchronized
方法都必须获得调用该方法对象的锁才能执行,否则会线程阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续往下执行。
缺陷:如果一个大的方法被申明 synchronized
将会影响效率。
- 使用方法
- synchronized 同步方法
//同步方法
public synchronized void method(int ages){}
synchronized 方法锁的是对象本身 this,谁获得这个对象的锁才能执行这个方法。
如果锁的是静态方法,则锁住的是 class 模板类(唯一)
- synchronized 同步代码块
//synchronized同步代码块
synchronized(obj){}
同步块锁的是 obj,obj 称为同步监视器
他可以是任何对象,但是推荐使用共享资源对象
同步方法种无需指定同步监视器,因为同步方法的同步监视器就是 this
就是这个对象的本身
2.2、Lock锁
Lock 实现提供比使用 synchronized
方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象 Condition
。
Lock 默认有三个实现类:
- 从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock 对象充当。
- java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。
- ReentrantLock 类实现了Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock(可重复锁),可以显式加锁、释放锁。
ReentrantLock() 源码:
- 公平锁:先来后到,不可插队
- 非公平锁:可插队(默认)
package com.liuxiang;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试JUC Lock的使用
* @author 86187
*/
public class TestLock {
public static void main(String[] args) {
Tikect tikect = new Tikect();
new Thread(() -> {
for (int i = 0; i < 20; i++) tikect.numTest();
}).start();
new Thread(() -> {
for (int i = 0; i < 20; i++) tikect.numTest();
}).start();
new Thread(() -> {
for (int i = 0; i < 20; i++) tikect.numTest();
}).start();
}
}
class Tikect {
private Integer num = 20;
//通过显示声明锁 进行手动加锁 和解锁
private Lock lock = new ReentrantLock();
public void numTest() {
lock.lock();//加锁
try {
if (num > 0) {
num--;
System.out.println(num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
2.3、synchronized与Lock的区别
- Synchronized 是内置的 Java 关键字,Lock 是一个 Java 类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock必须要手动释放锁,如果不释放锁,可能造成死锁
- Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等),Lock 锁就不一定会等待下去
- Synchronized 可重入锁,不可以中断的,非公平,Lock 可重入锁,可以判断锁,非公平(可以自己设置)
- Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码
2.4、ReadWriteLock读写锁
ReadWriteLock 只有唯一的一个实现类 ReentrantReadWriteLock:
package com.liuxiang;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 测试ReadWriteLock读写锁
* @author 86187
*/
public class TestReadWriteLock {
public static void main(String[] args) {
myCacheLock myCacheLock = new myCacheLock();
for (int i = 1; i <=5 ; i++) {
final int temp = i;
new Thread(()->{
myCacheLock.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
for (int i = 1; i <=5 ; i++) {
final int temp = i;
new Thread(()->{
myCacheLock.get(temp+"");
},String.valueOf(i)).start();
}
}
}
//自定义缓存
class myCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
//读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存的时候只有一个线程 存 取的时候可以多个线程取
//存
public void put(String key,Object value){
lock.writeLock().lock();//写锁
try {
//业务代码
System.out.println(Thread.currentThread().getName()+"号线程写入"+value);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"号线程写入成功");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();//释放锁
}
}
//取
public void get(String key){
lock.readLock().lock();//读锁
try {
//业务代码
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"号线程读取"+o);
System.out.println(Thread.currentThread().getName()+"号线程读取成功");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
}
1号线程写入1
1号线程写入成功
3号线程写入3
3号线程写入成功
4号线程写入4
4号线程写入成功
2号线程写入2
2号线程写入成功
5号线程写入5
5号线程写入成功
1号线程读取1
1号线程读取成功
5号线程读取5
5号线程读取成功
2号线程读取2
2号线程读取成功
4号线程读取4
3号线程读取3
3号线程读取成功
4号线程读取成功
3、生产者与消费者
3.1、传统synchronized版
package com.liuxiang;
/**
* 使用 synchronized 测试生产者消费者问题
* @author 86187
*/
public class TestPC {
public static void main(String[] args) {
Tikect tikect = new Tikect();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Tikect{
private int num =0;
public synchronized void increment() throws InterruptedException {
if (num!=0) {
this.wait();//睡眠
}
System.out.println(Thread.currentThread().getName()+"==>"+(++num));
this.notifyAll();//唤醒睡眠的线程
}
public synchronized void decrement() throws InterruptedException {
if (num==0) {
this.wait();
}
System.out.println(Thread.currentThread().getName()+"==>"+(--num));
this.notifyAll();
}
}
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
如果只是两个线程之间进行通信是没有问题的,如果多个线程进行通信就会产生虚假唤醒问题(因为if()
只会判断一次)。
因此我们应该根据官方文档,改成 while()
循环判断,来防止虚假唤醒:
package com.liuxiang;
/**
* 使用 synchronized 测试生产者消费者问题
* @author 86187
*/
public class TestPC {
public static void main(String[] args) {
Tikect tikect = new Tikect();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Tikect{
private int num =0;
public synchronized void increment() throws InterruptedException {
while (num!=0) {
this.wait();//睡眠
}
System.out.println(Thread.currentThread().getName()+"==>"+(++num));
this.notifyAll();//唤醒睡眠的线程
}
public synchronized void decrement() throws InterruptedException {
while (num==0) {
this.wait();
}
System.out.println(Thread.currentThread().getName()+"==>"+(--num));
this.notifyAll();
}
}
A==>1
B==>0
C==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
D==>0
A==>1
D==>0
C==>1
D==>0
3.2、Lock版
通过 Condition 替代了 Object 中睡眠唤醒方法:
通过 lock 找到 Condition:
package com.liuxiang;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用 Lock 测试生产者消费者问题
* @author 86187
*/
public class TestPC {
public static void main(String[] args) {
Tikect tikect = new Tikect();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
tikect.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Tikect{
private int num =0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try{
//防止虚假唤醒 使用while判断
while (num!=0) {
condition.await();//睡眠
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition.signalAll();//唤醒睡眠的线程
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try{
while (num==0) {
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
A==>1
B==>0
A==>1
B==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
3.3、Condition精准唤醒
package com.liuxiang;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition实现精准唤醒
* @author 86187
*/
public class TestCondition {
// A 执行完 唤醒B B执行完 唤醒C 顺序唤醒
public static void main(String[] args) {
Date date = new Date();
for (int i = 0; i < 5; i++) {
new Thread(() -> { date.prinltTest1(); },"A").start();
new Thread(() -> { date.prinltTest2(); },"B").start();
new Thread(() -> { date.prinltTest3(); },"C").start();
}
}
}
class Date{
private Lock lock = new ReentrantLock();
//准备三个监视器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;
//使用同一把锁进行加锁
public void prinltTest1(){
lock.lock();
try {
// 判断是否执行-> 执行具体业务 -> 唤醒其他线程
while (number!=1){
condition1.await();//睡眠
}
//业务
number = 2;
System.out.println("A执行了");
//唤醒B
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void prinltTest2(){
lock.lock();
try {
// 判断是否执行-> 执行具体业务 -> 唤醒其他线程
while (number!=2){
condition2.await();//睡眠
}
//业务
number = 3;
System.out.println("B执行了");
//唤醒C
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void prinltTest3(){
lock.lock();
try {
// 判断是否执行-> 执行具体业务 -> 唤醒其他线程
while (number!=3){
condition3.await();//睡眠
}
//业务
number = 1;
System.out.println("C执行了");
//唤醒A
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
A执行了
B执行了
C执行了
实现精准唤醒:需要准备多个监视器,通过 condition1.signal();
唤醒指定的监视器。
4、集合类不安全
在多线程下操作集合容易出现 java.util.concurrentModificationException
并发修改异常!
4.1、CopyOnWriteArrayList
在多线程并发情况下 ArrayList
不再具有安全性,此时我们应该使用安全的 List:
- 使用线程安全的类:Vector,Vector 在底层源码中使用
synchronized
来进行修饰的,所以它是线程安全的
- 使用 Collections 工具类转化线程不安全的类
//Collections转化线程安全的类
List<String> list = Collections.synchronizedList(new ArrayList<>());
- 使用 JUC 下线程安全的类
CopyOnWriteArrayList
List<Object> objects = new CopyOnWriteArrayList<>();
-
CopyOnWrite 的意思是:写入时复制 List,简称 COW,它是计算机程序设计领域的一种优化策略,多个线程调用时,List 读取的时候,固定的,产生写入(覆盖)
-
CopyOnWriteArrayList 比 Vector 好在哪里呢?
我们查看底层源码可知,在 CopyOnWriteArrayList 中使用的是高性能 lock 锁:
4.2、CopyOnWriteArraySet
- 与 List 相似,我们可以使用 Collections 工具类的方式来转换线程不安全的类
Set<Object> set = Collections.synchronizedSet(new HashSet<>());
- 使用 JUC 下线程安全的类
CopyOnWriteArraySet
Set<Object> set1 = new CopyOnWriteArraySet<>();
4.3、ConcurrentHashMap
- 使用线程安全的类 HashTable
Map<Object, Object> objectHashtable = new Hashtable<>();
HashTable 和 HashMap 的实现原理几乎一样,差别:HashTable 不允许 key 和 value 为 null
HashTable 线程安全的策略实现代价却比较大,get/put 所有相关操作都是 synchronized 的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞。
- 与 List 相似,我们可以使用 Collections 工具类的方式来转换线程不安全的类
Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
- 使用 JUC 下线程安全的类
ConcurrentHashMap
Map<Object, Object> map = new ConcurrentHashMap<>();
5、Callable
Callable 与 Runnable 的区别:
- 有返回值
- 可以抛出异常
- 方法不同 call()/run()
package com.liuxiang;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//寻常 多线程是如下方式开启 ,但是在Thread中不能直接传入Callable
/* new Thread(new Runnable() {
@Override
public void run() {
}
}).start();*/
//那么如何执行Callable呢?
//我们知道在Runnable的实现类FutureTask 构造方法中可以传入Callable 于是可以如下使用
MyCall call = new MyCall();
FutureTask<String> futureTask = new FutureTask<>(call);
new Thread(futureTask).start();
//如何获取结果呢?
String s = futureTask.get();//使用get参数 获取获取结果
System.out.println(s);
//如果多条线程执行呢?
new Thread(futureTask).start();
new Thread(futureTask).start();
//会发现方法打印只执行了一次
//原因是Callable有缓存
}
}
class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("执行方法");
//调用结果时可能会产生阻塞,因为方法内部逻辑的执行是一个耗时操作
return "ok";
}
}
执行方法
ok
6、常用辅助类
6.1、CountDownLatch
package com.liuxiang;
import java.util.concurrent.CountDownLatch;
/**
* 测试CountDownLatch减法计数器
* @author 86187
*/
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"号线程 Go out");
count.countDown();//数量-1
},String.valueOf(i)).start();
}
count.await();//等待计数器归零后才往下执行
System.out.println("Close the door");
}
}
1号线程 Go out
6号线程 Go out
5号线程 Go out
3号线程 Go out
4号线程 Go out
2号线程 Go out
Close the door
6.2、CyclicBarrier
CyclicBarrier
有两个构造方法,一个只用于计数,一个是计数完可以执行一个线程:
package com.liuxiang;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class TestCyclicBarrier {
public static void main(String[] args) {
//计数器加到7时 执行线程
CyclicBarrier barrier = new CyclicBarrier(7,() -> {
System.out.println("召唤神龙");
});
//创建7个线程
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"号线程集齐第"+temp+"颗龙珠");
try {
barrier.await();//线程等待 计数器+1
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
1号线程集齐第1颗龙珠
5号线程集齐第5颗龙珠
4号线程集齐第4颗龙珠
3号线程集齐第3颗龙珠
2号线程集齐第2颗龙珠
6号线程集齐第6颗龙珠
7号线程集齐第7颗龙珠
召唤神龙
注意点:如果无法计数到指定数量,那么线程会卡死,一直等待下去
6.3、Semaphore
package com.liuxiang;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class TestSemaphore {
public static void main(String[] args) {
//Semaphore 主要应用于限流
Semaphore semaphore = new Semaphore(3);
//Semaphore的位置只有3个 现在有6个线程 先拿到通行证的线程先执行 其他线程必须等待释放以后执行
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();//获取通行证
System.out.println(Thread.currentThread().getName()+"号抢到停车位");
TimeUnit.SECONDS.sleep(2);
System.out.println("离开停车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
1号抢到停车位
2号抢到停车位
3号抢到停车位
离开停车位
离开停车位
离开停车位
5号抢到停车位
4号抢到停车位
6号抢到停车位
离开停车位
离开停车位
离开停车位
7、阻塞队列
BlockingQueue 不是一个新东西,它是跟 List,Set 同级的集合框架:
7.1、四组API
- 抛出异常测试
package com.liuxiang;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 抛出异常测试
* @author 86187
*/
public class TestBlockingQueue {
public static void main(String[] args) {
//构造方法 需要传入队列大小
ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);
//往队列放入
System.out.println(b.add("a"));
System.out.println(b.add("b"));
System.out.println(b.add("c"));
System.out.println("===============");
//队列已满 如果继续放会报错 java.lang.IllegalStateException: Queue full 队列已满异常
//b.add("d");
System.out.println(b.element()); //查看队首元素
System.out.println(b.remove()); //弹出元素
System.out.println(b.remove());
System.out.println(b.remove());
//队列已空 如果继续取出 会报错 java.util.NoSuchElementException
b.remove();
}
}
true
true
true
===============
a
a
b
c
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.liuxiang.TestBlockingQueue.main(TestBlockingQueue.java:28)
- 有返回值,不抛出异常测试
package com.liuxiang;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 有返回值,不抛出异常测试
* @author 86187
*/
public class TestBlockingQueue {
public static void main(String[] args) {
//构造方法 需要传入队列大小
ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);
//往队列放入
System.out.println(b.offer("a"));
System.out.println(b.offer("b"));
System.out.println(b.offer("c"));
//队列已满 继续放入 返回值为false 没有异常
System.out.println(b.offer("d"));
System.out.println(b.peek()); //查看队首元素
System.out.println(b.poll()); //弹出元素
System.out.println(b.poll());
System.out.println(b.poll());
//队列已空 如果继续取出 为null 没有异常
System.out.println(b.poll());
}
}
true
true
true
false
a
a
b
c
null
- 阻塞等待测试
package com.liuxiang;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 阻塞等待测试
* @author 86187
*/
public class TestBlockingQueue {
public static void main(String[] args) throws InterruptedException {
//等待 阻塞(一直阻塞)
//构造方法 需要传入队列大小
ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);
//往队列放入
b.put("a");
b.put("a");
b.put("a");
b.put("a"); //队列已满 一直阻塞
b.take();
b.take();
b.take();
b.take(); //队列已空 一直阻塞
}
}
- 超时等待测试
package com.liuxiang;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 超时等待测试
* @author 86187
*/
public class TestBlockingQueue {
public static void main(String[] args) throws InterruptedException {
//等待 超时等待
//构造方法 需要传入队列大小
ArrayBlockingQueue<Object> b = new ArrayBlockingQueue<>(3);
//往队列放入
System.out.println(b.offer("a"));
System.out.println(b.offer("b"));
System.out.println(b.offer("c"));
System.out.println(b.offer("d", 2, TimeUnit.SECONDS));//超时等待两秒 两秒之后退出
//取出
System.out.println(b.poll(2, TimeUnit.SECONDS));
System.out.println(b.poll(2, TimeUnit.SECONDS));
System.out.println(b.poll(2, TimeUnit.SECONDS));
System.out.println(b.poll(2, TimeUnit.SECONDS));//超时等待两秒 取不到值就退出
}
}
true
true
true
false
a
b
c
null
7.2、同步队列SynchronousQueue
没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素。
package com.liuxiang;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 测试同步队列SynchronousQueue
* @author 86187
*/
public class TestSynchronousQueue {
public static void main(String[] args) {
//同步队列
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println("存"+"a");
queue.put("a");
System.out.println("存"+"b");
queue.put("b");
System.out.println("存"+"c");
queue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("取"+queue.take());
System.out.println("取"+queue.take());
System.out.println("取"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
存a
取a
存b
存c
取b
取c
8、线程池
线程池有哪些好处?
- 降低资源的消耗
- 提供响应速度
- 统一的管理(方便管理)
总结:可以控制最大并发数,管理线程
8.1、Executors三大方法
package com.liuxiang;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Executors 工具类 3大方法
* @author 86187
*/
public class TestPool {
public static void main(String[] args) {
//ExecutorService executor = Executors.newSingleThreadExecutor();//单个线程
ExecutorService executor = Executors.newFixedThreadPool(5);//创建一个固定的线程
//ExecutorService executor = Executors.newCachedThreadPool();//可伸缩的 遇强则强
//使用线程池 不再用原来的方式创建线程 而是用executor.execute()
try {
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName()+"线程执行");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
executor.shutdown();//使用完一定要关闭线程池
}
}
}
pool-1-thread-1线程执行
pool-1-thread-5线程执行
pool-1-thread-1线程执行
pool-1-thread-4线程执行
pool-1-thread-2线程执行
pool-1-thread-3线程执行
pool-1-thread-2线程执行
pool-1-thread-4线程执行
pool-1-thread-1线程执行
pool-1-thread-5线程执行
8.2、线程池七大参数
查看源码可以发现,上面创建线程池的三大方法都是调用了一个 ThreadPoolExecutor
的方法:
//单个线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//创建一个固定的线程
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//可伸缩的 遇强则强
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//线程池七大参数
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//存活时间,超时了没人调用就会释放
TimeUnit unit,//存活的单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工程 创建线程 一般不用动
//拒绝策略
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
在阿里云开发手册中有这样一句话:
8.3、四种拒绝策略
超过最大承载数时启动拒绝策略:
- AbortPolicy():不处理新的任务,直接抛出异常
- CallerRunsPolicy():哪来的回哪里去,如果是 main 线程传递过来的,让它回 main 线程处理去
- DiscardPolicy():队列满了,不会抛出异常,丢掉任务
- DiscardOldestPolicy():不会抛出异常,尝试跟最早的竞争,竞争失败也会丢掉任务
8.4、ThreadPoolExecutor创建线程池
-
线程池的最大承载数:阻塞队列数+最大线程数
-
最大线程数到底应该如何定义?
-
CPU密集型:有几核CPU就定义几,可以保证CPU效率最高
//获取CPU核心数 确保再不同的电脑上运行 Runtime.getRuntime().availableProcessors();
-
IO密集型:判断程序中,非常耗费 IO 的线程数,大于这个 IO 数(一般两倍)
-
package com.liuxiang;
import java.util.concurrent.*;
/**
* 使用七大参数自定义线程池
* @author 86187
*/
public class TestThreadPoolExecutor {
public static void main(String[] args) {
//使用七大参数自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2, //核心线程数
5, //最大线程数
3, //超过核心线程数的最大空闲时间
TimeUnit.SECONDS, //以秒为时间单位
new ArrayBlockingQueue<>(2), //创建阻塞队列 超过最大线程数后 启用队列 存放等待执行任务
Executors.defaultThreadFactory(), //默认线程工厂 一般不用改变
new ThreadPoolExecutor.AbortPolicy()); //拒绝策略 当最大线程数满了 并且阻塞队列也满了时 启用拒绝策略
try{
for (int i = 1; i <= 7; i++) {
//使用了线程池来创建线程
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+" ==> OK");
});
}
} catch (Exception e){
e.printStackTrace();
} finally {
//线程池用完,程序结束,关闭线程池
threadPoolExecutor.shutdown();
}
}
}
pool-1-thread-1 ==> OK
pool-1-thread-3 ==> OK
pool-1-thread-5 ==> OK
pool-1-thread-2 ==> OK
pool-1-thread-3 ==> OK
pool-1-thread-1 ==> OK
pool-1-thread-4 ==> OK
9、四大函数式接口
函数式接口用于简化编程模型
- Function 函数型接口
Function 函数型接口:只有一个方法的接口
package com.liuxiang.Function;
import java.util.function.Function;
/**
* Function函数型接口
* @author 86187
*/
public class TestFunction {
public static void main(String[] args) {
//工具类
Function function =new Function<String,String>() {
@Override
public String apply(String str) {
return str;
}
};
//Lambda表达式
//Function function = (str) -> {return str;};
System.out.println(function.apply("123"));
}
}
123
- Predicate 断定型接口
Predicate 断定型接口:有一个输入参数,返回值只能是布尔值
package com.liuxiang.Function;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Predicate断定型接口:有一个输入参数,返回值只能是布尔值
* @author 86187
*/
public class TestPredicate {
public static void main(String[] args) {
//工具类
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};
//Lambda表达式
//Predicate<String> predicate1 = (str) -> {return str.isEmpty();};
System.out.println(predicate.test(""));
}
}
true
- Consumer 消费型接口
Consumer 消费型接口:只有输入,没有返回值
package com.liuxiang.Function;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Consumer 消费型接口:只有输入,没有返回值
* @author 86187
*/
public class TestConsumer {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String str) {
System.out.println(str);
}
};
//Lambda表达式
//Consumer<String> consumer = (str)->{System.out.println(str);};
consumer.accept("123");
}
}
123
- Supplier 供给型接口
Supplier 供给型接口:没有参数,只有返回值
package com.liuxiang.Function;
import java.util.function.Supplier;
/**
* Supplier 供给型接口:没有参数,只有返回值
* @author 86187
*/
public class TestSupplier {
public static void main(String[] args) {
Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return 1024;
}
};
//Lambda表达式
//Supplier<Integer> supplier = ()->{return 1024;};
System.out.println(supplier.get());
}
}
1024
10、Stream流式计算
package com.liuxiang;
import java.util.ArrayList;
import java.util.List;
public class TestStream {
public static void main(String[] args) {
User a = new User(1, "a", 13);
User b = new User(2, "b", 23);
User c = new User(3, "c", 10);
User d = new User(4, "d", 5);
User e = new User(5, "e", 9);
List<User> list = new ArrayList<>();
list.add(a);
list.add(b);
list.add(c);
list.add(d);
list.add(e);
list.stream()
//filter过滤
.filter(user -> {return user.getId()%2!=0;})
.filter(user -> {return user.getAge()>10;})
//Function接口 传入一个user参数 返回一个user参数
.map(user -> {return user.getName().toUpperCase();})
//比较 排序
.sorted()
//分页 只获取一个参数
.limit(1)
//消费性接口Consumer 无返回值
.forEach((user) -> {
System.out.println(user);
});
}
}
class User{
int id;
String name;
int age;
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
A
11、ForkJoin
- 什么是 ForkJoin
分支合并,并行处理任务,将比较大的任务拆成小任务
适用场景:适合处理大型数据
ForkJoin 特点:工作窃取,此时有两个线程,A线程执行了一半,B线程已经执行完了,此时B线程会去窃取A线程的任务,来帮助它完成,这就叫工作窃取。
这里面维护的是双端队列
- ForkJoin 的使用
- 集成 RecursiveTask 递归任务,重写 compute 方法
- 创建 ForkJoinPool 类,submit() 提交任务–有返回值,execute() 执行任务–无返回值
package com.liuxiang.Function;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class TestForkJoin extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp = 10000L;
public TestForkJoin(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
long middle = (start+end)/2;//中间值
TestForkJoin fork1 = new TestForkJoin(start, middle);
fork1.fork();//拆分任务
TestForkJoin fork2 = new TestForkJoin(middle, end);
fork2.fork();
return fork1.join()+fork2.join();//结果
}
}
class TestFork{
public static void main(String[] args) throws ExecutionException, InterruptedException {
test2();
}
//ForkJoin 计算long
public static void test1() throws ExecutionException, InterruptedException {
ForkJoinPool joinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = joinPool.submit(new TestForkJoin(0L, 10_0000_0000L));//提交任务
long sum = submit.get();//获取结果
System.out.println(sum);
}
//使用stream流式计算
public static void test2(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println((end-start));
}
}
500000000500000000
241
12、异步回调
CompletableFuture 类似与 ajax,用于异步执行任务
package com.liuxiang.Function;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Future<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("没有返回值的异步回调");
});
//有返回值的supplyAsync异步回调
//与ajax一样 成功和失败都可以获取响应信息
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("有返回值的异步回调");
return 1024;
});
future2.whenComplete((t,u) -> {
//成功时 的回调方法
System.out.println(t);//1024 t正常的返回结果
System.out.println(u); // 错误的信息
}).exceptionally(e -> {
//失败时的回调
e.printStackTrace();
return 244;
});
future2.get();//阻塞等待执行结果
}
}
没有返回值的异步回调
有返回值的异步回调
1024
null
13、JMM
JMM:Java内存模型,不存在的东西,概念!
- 关于 JMM 的一些同步约定
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存
- 加锁和解锁是用一把锁
- JMM 的八种交互操作(每个操作都为原子操作)
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load动作使用
- load(载入):作用于工作内存的变量,它把 read 操作从主存中变量放入工作内存中
- use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write 使用
- write(写入):作用于主内存中的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中
- 对八种操作的规则
- 不允许 read 和 load、store 和 write 操作之一单独出现。即使用了 read 必须 load,使用了 store 必须 write
- 不允许线程丢弃他最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有 assign 的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施 use、store 操作之前,必须经过 assign 和 load 操作
- 一个变量同一时间只有一个线程能对其进行 lock。多次 lock 后,必须执行相同次数的 unlock 才能解锁
- 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load 或 assign 操作初始化变量的值
- 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量
- 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存
14、Volatile
Volatile 是 Java虚拟机提供的轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
14.1、保证可见性
package com.liuxiang;
import java.util.concurrent.TimeUnit;
public class TestVolatile {
//多个线程使用同一个变量时 需要加入volatile保证可见性
//main线程修改了变量 要及时通知其他线程 否则会造成死循环
private volatile static int sum =0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (sum==0){ //sum等于0时 程序死循环
}
}).start();
TimeUnit.SECONDS.sleep(1);//确保线程启动
sum=1;//主线程修改sum的值
System.out.println(sum);
}
}
1
Process finished with exit code 0
14.2、不保证原子性
package com.liuxiang;
import java.util.concurrent.TimeUnit;
public class TestVolatile {
//volatile不保证原子性
private volatile static int sum =0;
public static void add(){
sum++;
}
public static void main(String[] args) {
//理论上结果为10000 实际上volatile并不保证原子性 结果肯定不为两万
for (int i = 1; i <= 10000; i++) {
new Thread(()-> {
add();
}).start();
}
while (Thread.activeCount() > 2){//线程存活数
//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" ==> "+sum);
}
}
main ==> 9999
在不使用 synchronized 和 lock 的情况下,我们应该如何保证原子性?
通过 javap -c
命令我们了解到 sum++
并不是一个原子性操作:
可以使用 java.util.concurrent.atomic
包下的原子类来确保原子性:
package com.liuxiang;
import java.util.concurrent.atomic.AtomicInteger;
public class TestVolatile {
//AtomicInteger int类型的原子类
private volatile static AtomicInteger sum =new AtomicInteger();
public synchronized static void add(){
sum.getAndIncrement();// +1 操作 底层使用CAS 非常高效
}
public static void main(String[] args) {
//理论上结果为10000 实际上volatile并不保证原子性 结果肯定不为两万
for (int i = 1; i <= 10000; i++) {
new Thread(()-> {
add();
}).start();
}
while (Thread.activeCount() > 2){//线程存活数
//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+""+sum);
}
}
main10000
这些类的底层都直接和操作系统挂钩,直接操作内存 unsafe
14.3、禁止指令重排
什么是指令重排?
- 你写的程序,计算机并不是按照你写的顺序执行
- 源代码 -> 编译器优化的重排 -> 指令并行也可能会重排 ->内存系统也会重排 ->执行
- 处理器在进行指令重排的时候,会考虑数据的依赖问题
Volatile 如何避免指令重排?
内存屏障,作用:
- 保证特定的操作执行顺序
- 可以保证某些变量的内存可见性
15、CAS
15.1、什么是CAS
compareAndSet:比较并交换
如果期望的值达到了,那么就会更新,否则就会不更新
public static void main(String[] args) {
//CAS 如果期望的值达到了 就更新
//CAS 是CPU的并发原语
AtomicInteger atomicInteger = new AtomicInteger(2020);
atomicInteger.compareAndSet(2020,2021);
}
点进源码可以看到一个 unsafe
的东西,它是什么?
我们知道 Java 是无法操作内存的,但是可以通过 unsafe 这个类来操作内存:
查看原子类 AtomicInteger
的加1操作,再次理解 CAS:
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,则进行交换,否则会一直循环(自旋锁)
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
15.2、原子引用解决ABA问题
什么是ABA问题?(狸猫换太子)
- 比如是有两个线程A,B,一个变量:苹果
- A线程期望拿到一个苹果
- B线程一进来把苹果改成了梨子,但是在最后结束的时候又把梨子换成了苹果
- A线程在此期间是不知情的,以为自己拿到的苹果还是原来的那一个,其实已经被换过了
如何解决ABA问题?
- 原子引用 AtomicStampedReference
- 类似于乐观锁,比较时会去对比版本号,确认变量是否被换过了
package com.liuxiang;
import java.util.concurrent.atomic.AtomicStampedReference;
public class TestAtomicStampedReference {
public static void main(String[] args) {
//AtomicStampedReference 构造方法需要传入变量 和 版本号
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
int stamp = reference.getStamp();//获取版本号
System.out.println("a1=>"+stamp);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//期望2020 换成2021 期望的版本号是reference.getStamp() 结束后版本号+1
System.out.println(reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1));
System.out.println("a2=>"+reference.getStamp());
reference.compareAndSet(2,1,reference.getStamp(),reference.getStamp()+1);
System.out.println("a3=>"+reference.getStamp());
}).start();
new Thread(() -> {
int stamp = reference.getStamp();//获取版本号
System.out.println("b1=>"+stamp);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果版本号不是原来的那一个 那么修改会不成功
System.out.println(reference.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b2=>"+reference.getStamp());
}).start();
}
}
a1=>1
b1=>1
true
false
b2=>2
a2=>2
a3=>3
16、各种锁的理解
16.1、可重入锁
又叫递归锁
package com.liuxiang;
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
//可重入锁 拿到外面的锁 也就获得了里面的锁
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
Asms
Acall
Bsms
Bcall
16.2、自旋锁
自旋锁就是不断尝试,直到成功为止!
package com.liuxiang;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class TestSpinlock {
public static void main(String[] args) {
//底层使用的自旋锁CAS
Spinlock spinlock = new Spinlock();
new Thread(()->{
spinlock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlock.myUnLock();
}
},"T1").start();
new Thread(()->{
spinlock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlock.myUnLock(); //必须等T1释放锁T2才能释放锁
}
},"T2").start();
}
}
class Spinlock{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==> myLock");
//自旋锁
while (!atomicReference.compareAndSet(null,thread));{
}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==> myUnLock");
atomicReference.compareAndSet(thread,null);
}
}
T1==> myLock
T2==> myLock
T1==> myUnLock
T2==> myUnLock
16.3、死锁
多个线程同时争夺对方的资源
- 怎么排查死锁
- 使用 JPS 定位进程号,命令:
jps -l
- 使用
jstack
加进程号找到死锁问题