#1.什么是JUC
JUC:java.utils.concurrent 并发编程
业务:普通的线程代码 Thread
Runnable 没有返回值、效率相比Callable较低
#2.线程和进程
一个进程包含多个线程,至少一个。
java默认有2个线程。main线程和GC线程(垃圾回收)。
进程Typora:写字线程,自动保存线程
对于java而言:Thread、Runnable、Callable
java无法直接操作硬件,无法真正开启线程,底层是C++
并发:多个线程调用同一个资源
CPU一核模拟出来的多条线程,快速交替。
并行:多核同时运行。
CPU多核,多个线程同时执行。
并发编程的本质:充分利用CPU资源
#3.线程有几个状态:
NEW,RUNNABLE,BLOCKED,WAITING(死等,不释放锁),TIMED_WAITING(超时等待,过时不候),TERMINATED
wait/sleep区别:
1.来自不同的类
wait=>Object
sleep=>Thread
2.关于锁的释放
wait会释放锁,sleep不会释放
3.使用范围不同
wait:必须在同步块中
sleep:可以在任何地方睡
#4.Lock锁(重点)
传统 synchronized: Java关键字
新 Lock:Java类
公平锁:
非公平锁:
Synchronized 无法判断获取锁的状态,Lock可以
Synchronized 会自动释放锁,Lock必须手动解锁。如果不释放,“死锁”
Synchronized 线程1(获得锁,阻塞)、线程2(等待,一直);Lock不一定会等
Synchronized 可重入锁,不可以中断,非公平;Lock可重入锁,可以判断锁,公平
Synchronized 适合锁少量的代码同步问题;Lock锁适合锁大量的同步代码
锁是什么,如何判断锁的是谁?
锁的是资源,避免访问冲突。
#5.生产者消费者问题
传统&JUC版
Condition 精准的通知和唤醒线程
package com.JUC.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 {
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();
}
}
class Data3{
// 1个lock锁, 多个condition
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1->A, 2->B, 3->C
public void printA(){
lock.lock();
try{
// 业务, 判断->执行->通知
while(number!=1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>A is running");
// 精准唤醒, 唤醒指定的人 A->B
number = 2;
condition2.signal(); // condition1.signalALL();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
// 业务, 判断->执行->通知
while(number!=2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>B is running");
// 精准唤醒, 唤醒指定的人 A->B
number = 3;
condition3.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
// 业务, 判断->执行->通知
while(number!=3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>C is running");
// 精准唤醒, 唤醒指定的人 A->B
number = 1;
condition1.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
// 生产线: 购物->下单->支付->交易->物流
}
#6."8锁"现象
如何判断锁的是谁!
对象 class
深刻理解
package com.JUC.lock8;
/*
* 8锁,就是关于锁的8个问题
* 1.Ques:标准情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话
* 2.Ques: sendSms sleep 4s的情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话
*/
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args){
Phone phone = new Phone();
// synchronized 锁的对象是方法的调用者
new Thread(()->{
phone.sendSms();
}, "A").start();
//
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} // 注意,sleep没有释放锁
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
}
package com.JUC.lock8;
import java.util.concurrent.TimeUnit;
/*
* 8锁,就是关于锁的8个问题
* 1.Ques:标准情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话
* 2.Ques: sendSms sleep 4s的情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话
* 3.Ques: 增加了一个未Synchronized的普通方法, 是先发短信还是hello? Ans: 1.hello 2.发短信
* 4.Ques: 两个对象, 两个调用者, 两把锁, 是先发短信还是hello? Ans: 1.打电话 2.发短信
*/
public class Test2 {
public static void main(String[] args){
Phone2 phoneA = new Phone2();
Phone2 phoneB = new Phone2();
// synchronized 锁的对象是方法的调用者, 即phone
new Thread(()->{
phoneA.sendSms();
}, "A").start();
//
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phoneB.call();
}, "B").start();
}
}
class Phone2{
// Synchronized 锁的对象是方法的调用者
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} // 注意,sleep没有释放锁
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
// 没有锁, 不是同步方法, 不受锁的影响
public void hello(){
System.out.println("hello");
}
}
package com.JUC.lock8;
import java.util.concurrent.TimeUnit;
/*
* 8锁,就是关于锁的8个问题
* 1.Ques:标准情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话
* 2.Ques: sendSms sleep 4s的情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话
* 3.Ques: 增加了一个未Synchronized的普通方法, 是先发短信还是hello? Ans: 1.hello 2.发短信 因为hello不受锁的影响
* 4.Ques: 两个对象, 两个调用者, 两把锁, 是先发短信还是hello? Ans: 1.打电话 2.发短信 因为锁不一样, 两者互不干涉
*/
/*
* 5.Ques:增加两个静态的同步方法,只有一个对象,先发短信还是打电话 Ans:1.发短信2.打电话 因为这里把类模板锁了, 所有对象有且只有一个类模板
* 6.Ques:增加两个静态的同步方法,有两个对象,先发短信还是打电话 Ans:1.发短信2.打电话 同理,锁的是类模板, 多个对象情况下也是同一个锁
*
*
*/
public class Test3 {
public static void main(String[] args){
// 两个对象的Call类模板有且只有一个, static+Synchronized==>锁的是类模板
Phone3 phoneA = new Phone3();
Phone3 phoneB = new Phone3();
// synchronized 锁的对象是方法的调用者, 这里锁的是Phone3
new Thread(()->{
phoneA.sendSms();
}, "A").start();
//
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phoneB.call();
}, "B").start();
}
}
class Phone3 {
// static 静态, 那么Synchronized锁的是类模板了
// static 修饰的方法在类预加载就有了!
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} // 注意,sleep没有释放锁
System.out.println("sendSms");
}
public static synchronized void call() {
System.out.println("call");
}
}
package com.JUC.lock8;
import java.util.concurrent.TimeUnit;
/*
* 8锁,就是关于锁的8个问题
* 1.Ques: 标准情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话 因为锁的是对象
* 2.Ques: sendSms sleep 4s的情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话 因为sleep不会释放锁
* 3.Ques: 增加了一个未Synchronized的普通方法, 是先发短信还是hello? Ans: 1.hello 2.发短信 因为hello不受锁的影响
* 4.Ques: 两个对象, 两个调用者, 两把锁, 是先发短信还是hello? Ans: 1.打电话 2.发短信 因为锁不一样, 两者互不干涉 按时间顺序
*/
/*
* 5.Ques:增加两个静态的同步方法,只有一个对象,先发短信还是打电话 Ans:1.发短信 2.打电话 因为这里把类模板锁了, 所有对象有且只有一个类模板
* 6.Ques:增加两个静态的同步方法,有两个对象,先发短信还是打电话 Ans:1.发短信 2.打电话 同理,锁的是类模板, 多个对象情况下也是同一个锁
* 7.Ques:一个静态同步方法, 一个普通同步方法, 一个对象, 先发短信还是打电话 Ans:1.打电话 2.发短信 两个锁不一样, 所以互不干涉 按时间顺序
* 8.Ques:一个静态同步方法, 一个普通同步方法, 两个对象, 先发短信还是打电话 Ans:1.打电话 2.发短信 两个锁不一样, 所以互不干涉 按时间顺序
*/
public class Test4 {
public static void main(String[] args){
// 两个对象的Call类模板有且只有一个, static+Synchronized==>锁的是类模板
Phone4 phoneA = new Phone4();
Phone4 phoneB = new Phone4();
// synchronized 锁的对象是方法的调用者, 这里锁的是Phone3
new Thread(()->{
phoneA.sendSms();
}, "A").start();
//
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phoneB.call();
}, "B").start();
}
}
class Phone4 {
// 静态同步方法 锁的类模板
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} // 注意,sleep没有释放锁
System.out.println("sendSms");
}
// 普通同步方法 锁的对象
public synchronized void call() {
System.out.println("call");
}
}
- 8锁,就是关于锁的8个问题
- 1.Ques: 标准情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话 因为锁的是对象
- 2.Ques: sendSms sleep 4s的情况下, 两个线程先输出 发短信还是打电话? Ans: 1.发短信 2.打电话 因为sleep不会释放锁
- 3.Ques: 增加了一个未Synchronized的普通方法, 是先发短信还是hello? Ans: 1.hello 2.发短信 因为hello不受锁的影响
- 4.Ques: 两个对象, 两个调用者, 两把锁, 是先发短信还是hello? Ans: 1.打电话 2.发短信 因为锁不一样, 两者互不干涉 按时间顺序
- 5.Ques:增加两个静态的同步方法,只有一个对象,先发短信还是打电话 Ans:1.发短信 2.打电话 因为这里把类模板锁了, 所有对象有且只有一个类模板
- 6.Ques:增加两个静态的同步方法,有两个对象,先发短信还是打电话 Ans:1.发短信 2.打电话 同理,锁的是类模板, 多个对象情况下也是同一个锁
- 7.Ques:一个静态同步方法, 一个普通同步方法, 一个对象, 先发短信还是打电话 Ans:1.打电话 2.发短信 两个锁不一样, 所以互不干涉 按时间顺序
- 8.Ques:一个静态同步方法, 一个普通同步方法, 两个对象, 先发短信还是打电话 Ans:1.打电话 2.发短信 两个锁不一样, 所以互不干涉 按时间顺序
#7.集合类不安全
package com.JUC.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
// 单线程 非常安全
// List<String> list = Arrays.asList("1", "2", "3");
// list.forEach(System.out::println); // 按顺序输出
// 多线程写法
// 并发下 ArrayList 不安全
// List<String> list = new ArrayList<>();
// for (int i = 0; i < 100; i++) {
// new Thread(()->{
// list.add(UUID.randomUUID().toString().substring(0,5)); // 生成随机字符串
// System.out.println(list);
// }, String.valueOf(i)).start();
/**
* 解决方法:
* 1.使用vector, 因为vector里面是同步方法写的.
* 2.让ArrayList变安全, 使用Collections工具类
* 3.写入时复制CopyOnWrite, COW思想, 计算机程序设计领域优化策略.
* 多个线程调用时候, list, 读取的时候, 是固定的,写入(覆盖)的时候,
* 在写入的时候,避免覆盖,造成数据问题
* 只在增删改时加锁, 提高效率
*/
// List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>()); // 变安全
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5)); // 生成随机字符串
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
Set不安全
package com.JUC.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
// 同理可证 : 并发修改异常
// 1. 使用工具类Collections变安全
// 2. 使用CopyonWrite
public class setTest {
public static void main(String[] args) {
// HashSet<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
hashSet的底层是什么? 是hashMap
// add: set 本质就是map, key是无法重复的
private static final Object PRESENT = new Object(); // 不变的值
#8.Callable(简单)
- 可以有返回值
- 可以抛出异常
- 方法不同, run()/ call()
futuretask实现了runnable接口的子接口runnablefuture,futuretask有一个带callable类型参数的构造器,所以futuretask可以作为runnable接口的实现类传到thread的构造器
代码测试:
package com.JUC.callable01;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class callableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread().start(); // 怎么启动Callable?
MyThread thread = new MyThread();
// 通过futuretask 来启动callable
FutureTask futureTask = new FutureTask(thread); // 适配类
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); // 这个线程不会执行, 因为futuretask只会执行一次且会更新state, 第二次根据state判断后直接结束线程,导致任务不执行,
Integer O = (Integer) futureTask.get(); // 可能会导致阻塞, 要放到最后
// 或者通过异步通信来处理
System.out.println(O);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");
return 512;
}
}
细节:
1.FutureTask在第一次调用后就返回结果保存了, 第二次任务不执行
2.结果可能需要等待, 会阻塞
#9.常用的辅助类:并发控制
8.1 CountDownLatch 减法计数围栏
package com.JUC.add;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 倒计时
// 必须要让当前任务执行完, 再结束.
CountDownLatch countDownLatch = new CountDownLatch(15);
for (int i = 0; i < 15; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go Out");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零, 再往下执行
System.out.println("Close Door");
}
}
原理:
countDownLatch.countDown(); //数量-1
countDownLatch.await(): //等待计数器归零,再向下执行
每次有线程调用countDown() 数量-1, 假设计数器归零, await()就会被唤醒,继续执行后续任务
8.2 CyclicBarrier 加法计数围栏
package com.JUC.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(8, ()->{
System.out.println("successfully!");
});
for (int i = 0; i <= 7; i++) {
final int temp = i;
// lambda 不能直接使用i
new Thread(()->{System.out.println(Thread.currentThread().getName()+"collect"+temp+"ball");// thread 里是在重写内部方法,不能调用i
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
8.3 Semaphore:信号量
例子:6车–3个停车位
原理:
semaphore.acquire() 获得,假设已经满了,等待,等待到被释放为止
semaphore.release() 释放,会将当前的信号量+1, 然后唤醒等待的线程
作用:多个共享资源互斥的作用,并发限流, 控制最大线程数.
#10.读写锁
原理:
读可以被多线程读
写只能被单线程写.
作用:提高性能,保证安全
package com.JUC.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* ReadWriteLock
* 读-读 可以共存!
* 读-写 不可共存!
* 写-写 不可共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
for (int i = 1 ; i <= 10; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"", temp+"");
}, String.valueOf(i)).start();
}
for (int i = 1 ; i <= 10; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
}, String.valueOf(i)).start();
}
}
}
/**
*
* 自定义缓存
*
*/
class MyCache{
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
class MyCacheLock{
private volatile Map<String, Object> map = new HashMap<>();
// 读写锁:更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 存, 写入的时候, 只希望同时只有一个线程写,
public void put(String key, Object value){
readWriteLock.writeLock().lock();// 写锁
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readWriteLock.writeLock().unlock();
}
}
// 取, 读入的时候,所有人都可以读.
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
readWriteLock.readLock().unlock();
}
}
}
#阻塞队列
---- BlockQueue
阻塞:暂停
队列(queue, FIFO):
写入:如果队列满了, 就必须阻塞等待
取:如果队列是空的,必须阻塞等待生产
blockingqueue不是新东西
什么情况下使用? 多线程并发处理.线程池.
学会使用: 添加&&移除&&判断队列首
四组API:
1.抛出异常
2.不会抛出异常
3.阻塞等待
4.超时等待
package com.JUC.bq;
//Collection
//Set
//list
//queue
//BlockingQueque
import java.util.concurrent.ArrayBlockingQueue;
public class test {
public static void main(String[] args) {
test1();
}
/**
* 抛出异常
*/
public static void test1(){
// 队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// 抛出队列已满异常d Queue Full
// System.out.println(blockingQueue.add("d"));
//
System.out.println("============");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// 抛出 NoSuchElementException
// System.out.println(blockingQueue.remove());
}
}
/**
* 有返回值, 不抛出异常
*/
public static void test2(){
// 队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("d")); // 返回false 不抛出异常
System.out.println("=============");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll()); // 返回nul 不抛出异常
} /**
* 等待, 阻塞(一直阻塞)
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// 队列没有位置, 会一直等待, 一直阻塞
// blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); // 没有这个元素, 一直阻塞
}
/**
* 等待, 阻塞(等待超时)
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d", 2, TimeUnit.SECONDS); // 超时退出
System.out.println("========");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
blockingQueue.poll(2, TimeUnit.SECONDS); //超时退出
}
SynchoronousQueue
没有元素
进去一个元素, 必须等待取出来之后, 才能再往里面添加一个元素
package com.JUC.bq;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
* 和其他的BlockingQueue 不一样, SynchronousQueue不存储元素
* put一个元素,必须take出来后, 才能再put进去
*/
public class SynQueueDemo {
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
queue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
queue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
queue.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"T2").start();
}
}
12.线程池(重点)
三大方法,7大参数,4种拒绝策略
程序的运行本质:占用系统资源! 优化资源的使用=>池化技术
线程池, 连接池, 内存池, 对象池///…创建,销毁.十分浪费资源
池化技术:事先准备好一些资源,需要用就拿出来,用完归还.
默认大小:2
1.最小的值
2.最大的值
线程池的好处:
1.降低资源消耗
2.提高响应速度
3.方便管理
线程复用,可以控制最大并发数
三大方法:
package com.JUC.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 工具类, 3大方法
// 使用了线程池之后, 使用线程池创建线程
public class Demo01 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个固定线程池的大小
ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩式线程池
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
// 线程池用完后, 记得关闭
}
}
源码分析:
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>());
}
//本质:ThreadPoolExecutor()
// 注意,这里有7个参数,
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
所以, 我们可以看出, 如果直接使用Executors提供的方法, 可能会在编写程序中导致OOM, 那么如果用更底层的ThreadPoolExecutor, 可以了解到底层运行的方式, 因此, 我们一般使用ThreadPoolExecutor创建线程池.
阻塞队列:
线程池创建:
四种拒绝策略 7大参数
package com.JUC.pool;
import java.util.concurrent.*;
public class Demo02 {
public static void main(String[] args) {
// 自定义线程池, 工作
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
3,
//2个初始线程数, 5个最大线程数, 存活时间数为3
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3), // 这是阻塞队列 容量为3
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); // 拒绝策略
// 最大线程如何定义----
// 1.CPU密集型 n核n个线程! 保证效率最大化. 从代码获取这个核心数
System.out.println(Runtime.getRuntime().availableProcessors());
// 2.IO密集型
// new ThreadPoolExecutor.AbortPolicy() 满了,还有人进来,不处理并抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() 原路返回
// new ThreadPoolExecutor.DiscardPolicy() 队列满了, 丢掉任务, 但不抛出异常
// new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了, 尝试与前面线程竞争, 再丢掉任务, 但不抛出异常
try {
// 最大承载; Max+Deq = 5+3 = 8
for (int i = 0; i < 50; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
// 线程池用完后, 记得关闭
}
}
小结和策略:
问题:最大线程池如何定义----(调优任务)
1.CPU密集型 n核n个线程! 保证效率最大化. 从代码获取这个核心数
2.IO密集型 程序有n个大型任务, IO占用资源, 要设置大于等于n的线程数
13.四大函数式接口(必须掌握)
新时代程序员:
1.lambda表达式
2.链式编程(函数式编程)
3.函数式接口
4.stream流式计算
函数式接口: 只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 泛型, 枚举, 反射
// java里特别多, 简化编程模型, 在新版本的框架底层大量应用.
// e.g.foreach里的消费者函数式接口
四大函数式接口:Consumer Function Predicate Supplier
Function:
package com.JUC.functional;
import java.util.function.Function;
/**
* Function 函数型接口
* 因此可以用Lambda表达式简化
* 有一个输入, 就有一个输出
*/
public class functionDemo {
public static void main(String[] args) {
// 工具类:输出输入的类
// Function function = new Function<String, String>(){
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function function = (str)->{return str;};
System.out.println(function.apply("asd"));
}
}
Predicate:
package com.JUC.functional;
import java.util.function.Predicate;
/**
* 断定型接口:有一个输入, 返回布尔值
*/
public class predicateDemo {
public static void main(String[] args) {
// 判断字符串是否为空
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
Predicate<String> predicate = (str)->{
return str.isEmpty();
};
System.out.println(predicate.test("ads"));
}
}
Consumer:
package com.JUC.functional;
import com.JUC.pc.C;
import java.util.function.Consumer;
/*
* 消费型接口: 只有输入, 没有返回值
*/
public class consumerDemo {
public static void main(String[] args) {
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer<String> consumer = (str)->{
System.out.println(str);
};
consumer.accept("adfljk");
}
}
Supplier:
package com.JUC.functional;
import java.util.function.Supplier;
/**
* 供给型接口:没有参数,只有输出
*/
public class supplierDemo {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "ok";
// }
// };
Supplier<String> supplier = ()->{return "OK";};
System.out.println(supplier.get());
}
}
14.Stream流式计算
什么是?
存储+计算
本质是存储:集合, MySQL
计算:交给流Stream.
15.ForkJoin 分支合并
什么是?
并行执行任务!提高效率.大数据量~
大数据:Map Reduce(大任务拆分成小任务)
特点: 工作窃取 即能者多劳, 不让线程等待,
维护的都是双端队列
16.异步回调
future设计的初衷:对将来的某个事件的结果进行建模
package com.JUC.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* 异步调用
*
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 发起一个请求 Void<=>void
// 没有返回值的异步回调
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
// });
//
// System.out.println("111");
//
// // 等待completableFuture的结果前, 可以先输出111
// completableFuture.get(); // 获取阻塞进行结果
// 有返回值的异步回调
// ajax, 成功和失败的回调
// 如果返回的是错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Void");
// 刻意报错
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t->" + t); // 正常返回结果
System.out.println("u->" + u); // 错误信息 java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> {
e.printStackTrace();
return 233;
}).get());
/**
* success Code 200
* error Code 404 500
*/
}
}
17.JMM
请你谈谈你对Volatile的理解
Volatile是java虚拟机提供轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
JMM(Java Memory Model, java内存模型, 概念,约定,不存在的东西)
关于JMM的同步约定:
1.线程解锁前, 必须把共享变量立刻刷回主存
2.线程加锁前,必须读取主存中的最新值到工作内存中!
3.加锁和解锁是同一把锁
线程 工作内存 主内存
8种操作:
问题:
Java虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许有例外)。
八大原子操作:
lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
如果要把一个变量从主内存拷贝到工作内存,那就要按顺序执行read和load操作,如果要把变量从工作内存同步回主内存,就要按顺序执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,但不要求是连续执行。也就是说read与load之间、store与write之间是可插入其他指令的,如对主内存中的变量a、b进行访问时,一种可能出现的顺序是read a、read b、load b、load a。
同时Java内存模型还规定了在执行上述八种原子操作时必须满足如下规则:
1.不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者工作内存发起回写了但主内存不接受的情况出现。
2.不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
3.不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
4.一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use、store操作之前,必须先执行assign和load操作。
5.一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
6.如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作以初始化变量的值。
7.如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
8.对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
问题:程序不知道主内存的值已经被修改, 所以需要volatile来解决.
18.Volatile
原子性: 不可分割
线程A在执行任务时, 不能被打扰, 也不能被分割, 要么同时成功, 要么同时失败.
package com.JUC.volatileTest;
// volatile 不保证原子性
public class JMMDemo02 {
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
// 理论上结果为2w
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){ // java 默认运行线程main gc
Thread.yield(); // 线程礼让
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
如果不加lock和Synchronized, 怎么保证原子性:
使用原子类, 解决原子性问题.
为什么它这么高级?
package com.JUC.volatileTest;
import java.util.concurrent.atomic.AtomicInteger;
// volatile 不保证原子性
public class JMMDemo02 {
private volatile static AtomicInteger num = new AtomicInteger(0);
public static void add(){
// num++; // 不是一个原子性操作
num.getAndIncrement(); // 自增1. 使用了CAS原理
}
public static void main(String[] args) {
// 理论上结果为2w
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){ // java 默认运行线程main gc
Thread.yield(); // 线程礼让
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
这些类的底层都和操作系统挂钩.在内存中修改值! Unsafe是一个很特殊的存在.
什么是指令重排:你写的程序计算机并不是按照写的执行.
源代码–>编译器优化重排–>指令并行可能会重排–>内存系统可能会重排–>执行
处理器在进行指令重排时,要考虑:数据间依赖性
int x = 1; // 1
int y = 2; // 2
y = x + 5; // 3
y = x * x; // 4
// what we expect: 1234
// but it may be: 2134 1324
// try to think: it could be 4213? impossible!
可能造成影响的结果: abxy这四个值都默认是0
线程A | 线程B |
x=a | y=b |
n=1 | a=2 |
正常的结果: x=0;y=0; 但是可能由于指令重排 | |
线程A | 线程B |
– | – |
b=1 | a=2 |
x=a | y=b |
volatile可以避免指令重排
内存屏障. CPU指令. 作用:
1.保证特定的操作的执行顺序!
2.可以保证某些变量的内存可见性(利用这些特性, volatile就实现了可见性)
18.单例模式
饿汉式, DCL饿汉式,深究~
package com.JUC.singlemod;
public class Hungry {
// 可能造成空间浪费
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
package com.JUC.singlemod;
// 懒汉式单例
// 道高一尺魔高一丈
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan lazyMan; // 避免多线程下指令重排
public static LazyMan getInstance(){
// 加锁 双重检测锁模式的懒汉式单例<=>DCL懒汉式
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); // 不是一个原子性操作, 加锁解决
/**
* 1. 分配一个内存空间
* 2. 执行构造方法,初始化对象
* 3. 把对象指向空间
*
* 123 or 132
*/
}
}
}
return lazyMan; // 此时lazyMan可能还没有完成构造
}
// 不加锁下单线程下ok, 但多线程并发有问题
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
package com.JUC.singlemod;
// 静态内部类实现
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
// 反射! 可以破坏
public static void main(String[] args) {
}
}
package com.JUC.singlemod;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
// enum是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
// NoSuchMethodException: com.JUC.singlemod.EnumSingle.<init>()
System.out.println(instance1);
System.out.println(instance2);
}
}
枚举的最终反编译源码:
20.深入理解CAS(CompareAndSwap)
研究底层,有所突破.
什么是CAS?
比较并交换
Unsafe类
CAS:比较当前工作内存中的值和主内存的值, 如果这个值是期望的, 那么就执行操作, 如果不是就一直循环.
CAS:e,c,
缺点:
1.循环耗时
2.一次性只能保证一个共享变量的原子性
3.ABA问题
CAS:ABA问题(线程间CAS产生)
package com.JUC.CAS;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2023);
// 对于平时写的SQL: 乐观锁!
// 期望和更新
// public final boolean compareAndSet(int expect, int update)
// 如果我的期望值达到了,那么就更新,否则就不更新 CAS是CPU的并发原语.
// ============欺骗线程=============
System.out.println(atomicInteger.compareAndSet(2023, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2023));
System.out.println(atomicInteger.get());
// ============乐观线程============
System.out.println(atomicInteger.compareAndSet(2023, 6666));
System.out.println(atomicInteger.get());
}
}
21.原子引用:带版本号的原子操作!
解决ABA问题,引入原子引用 对应思想:乐观锁
乐观锁:
package com.JUC.CAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
// Integer 注意,如果泛型是包装类, 注意对象的引用问题
// 在业务操作上, 这里面比较的都是一个个对象
public class CASDemo {
// CAS compareAndSet 比较并交换
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2023);
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = atomicInteger.getStamp(); // 获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// version
System.out.println(atomicInteger.compareAndSet(1, 2,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));// 版本号+1
System.out.println("a2=>"+atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(2, 1,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));// 版本号+1
System.out.println("a3=>"+atomicInteger.getStamp());
}, "a").start();
// 乐观锁的原理相同
new Thread(()->{
int stamp = atomicInteger.getStamp(); // 获得版本号
System.out.println("b1=>"+atomicInteger.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicInteger.compareAndSet(1, 5,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));// 版本号+1
System.out.println("b2=>"+atomicInteger.getStamp());
}, "b").start();
}
}
注意:
Integer使用了对象缓存机制, 默认范围是-128-127, 推荐使用静态工厂方法valueOf获取对象实例, 而不是new, 因为valueOf使用缓存, 而new一定会创建新的对象分配新的内存空间
22.可重入锁
1.公平锁, 非公平锁
公平锁: 非常公平, 不能插队, 必须先来后到
非公平锁:不公平, 可以插队, 3s, 3h(默认都是非公平.)
2.可重入锁<=>递归锁
Synchronized
package com.JUC.lock;
// synchronized
public class Demo01 {
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");
}
}
Lock
package com.JUC.lock;
import javax.swing.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// synchronized
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
}, "A").start();
new Thread(()->{
phone.sms();
}, "B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
lock.lock();
// lock 锁必须配对, 否则就会死锁
try {
System.out.println(Thread.currentThread().getName()+" sms");
call(); // 这里也有锁
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" call");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
3.自旋锁 spinlock
package com.JUC.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock lock = new ReentrantLock();
// lock.lock();
// lock.unlock();
// 底层使用的自旋锁CAS
spinlock lock = new spinlock();
new Thread(()->{
lock.mylock(); // 注意T1并没有自旋, 把null置换成了thread
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.myunlock();
}
}, "T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.mylock(); // 由于T1获得了锁, T2此时CAS为False, T2陷入自旋
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.myunlock();
}
}, "T2").start();
}
}
4.死锁.
死锁测试, 怎么排除死锁
package com.JUC.lock;
import java.util.concurrent.TimeUnit;
public class DeadLockDemo {
public static void main(String[] args) {
String A = "A";
String B = "B";
new Thread(new MyThread(A, B), "T1").start();
new Thread(new MyThread(B, A), "T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "lock: "+lockA+"=>get "+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "lock: "+lockB+"=>get "+lockA);
}
}
}
}
解决问题
1.使用jps定位进程号. jps -l
2.使用jstack 锁定进程号找到死锁问题.jstack -PID
面试或工作中, 排查问题:
1.日志 9
2.堆栈信息 1
23.小结