1. 进程和线程
- 进程:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个基本单位。例如:打开一个 .exe文件就是一个进程、打开360安全软件就是一个进程
- 线程
- 线程是进程的一个实体,是进程中执行运算的最小单位。
- java默认有两个线程:main 和 GC(垃圾回收)
2. 并行和并发
-
并发:(多个线程操作同一个资源)
CPU一核,模拟出来多条线程。 -
并行:(多个人一起行走)
CPU多核,多个线程可以同时执行。
并发编程的本质:充分利用CPU的资源
3. Synchronized和Lock
以卖票为例,多个线程买票,需要排队获取锁,保证线程安全。代码如下:
1. Synchronized
public class SaleTestDemo {public static void main(String[] args) {//并发,多个线程操作同一个资源类Ticket ticket = new Ticket();//Runnable 函数式接口 jdk1.8 Lambda 表达式new Thread(()->{for (int i = 0; i < 60; i++) {ticket.sale();}},"A").start();new Thread(()->{for (int i = 0; i < 60; i++) {ticket.sale();}},"B").start();new Thread(()->{for (int i = 0; i < 60; i++) {ticket.sale();}},"C").start();}
}
//资源类 OOP
class Ticket {//属性 方法private int number = 30;//卖票的方式//synchronized 本质: 队列 锁public synchronized void sale(){if (number > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);}}
}
2. Lock
class Ticket2 {//属性 方法private int number = 30;Lock lock = new ReentrantLock(true); //默认:false 非公平锁//卖票的方式public void sale(){lock.lock(); //加锁lock.tryLock(); //尝试获取锁try {//业务代码}} catch (Exception e) {e.printStackTrace();}finally {lock.unlock(); //解锁}}
}
new ReentrantLock(true); //默认:false 非公平锁// 可重入锁(普通锁)
new ReentrantLock
// 读锁
ReentrantReadWriteLock.ReadLock
// 写锁
ReentrantReadWriteLock.WriteLock
3. Sychronized和Lock的区别
Sychronized | lock |
---|---|
内置Java关键字 | 一个Java类 |
无法获取锁的状态 | 可以判断锁的状态 |
会自动释放锁 | 必须手动释放锁,如果不放,会死锁 |
线程1获得锁,线程2必须等待(傻瓜等待) | lock锁不一定一直等待下去(可以手动设置) |
可重入锁,非公平锁,不可中断 | 可重入锁,非公平锁(可以设置,默认非公),可以中断 |
适合少量代码同步 | 适合大量代码同步 |
4. 锁
1. 公平锁:
是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
例如:
Lock lock = new ReentrantLock(true);
2. 非公平锁
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的情况下,有可能会造成优先级反转或者饥饿现(先抢,抢不到再排队)
//Sychronized
//Lock lock = new ReentrantLock();
3. 可重入锁(递归锁)
线程可以进入任何一个它已经拥有的锁所同步着的代码块。
指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
在一个同步方法里面在访问另外一个同步方法,自动获取锁
- synchronized
package lock;
//Synchorized
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(); //这里也有锁(sms锁 里面的call锁)}public synchronized void call(){System.out.println(Thread.currentThread().getName() + "call");}
}
- Lock
lock锁必须配对,否则就会死在里面
package lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;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锁必须配对否则就会死在里面try {System.out.println(Thread.currentThread().getName() + "sms");call(); //这里也有锁(sms锁 里面的call锁)} catch (Exception e) {e.printStackTrace();}finally {lock.unlock();}}public synchronized void call(){lock.lock();//第二把锁try {System.out.println(Thread.currentThread().getName() + "call");} catch (Exception e) {e.printStackTrace();}finally {lock.unlock();}}
}
4. 自旋锁
是指尝试获取锁的线程不会立即阳寒,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
package com.demo.designmode.test;import ch.qos.logback.core.util.TimeUtil;import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;/*** @Author: jxm* @Description: 自学自旋锁* @Date: 2022/4/2 10:11* @Version: 1.0*/
public class JUCTest1 {/*** compare的使用:CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。*///原子引用AtomicReference<Thread> reference = new AtomicReference<>();public void myLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"\t come in .........");//第一次进来内存值就是nullwhile (!reference.compareAndSet(null,thread)){}}public void myUnLock(){Thread thread = Thread.currentThread();reference.compareAndSet(thread,null);System.out.println(Thread.currentThread().getName()+"\t invoked myUnLock");}public static void main(String[] args){JUCTest jucTest = new JUCTest();new Thread(()->{jucTest.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}finally {jucTest.myUnLock();}},"A").start();new Thread(()->{jucTest.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}finally {jucTest.myUnLock();}},"B").start();new Thread(()->{jucTest.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}finally {jucTest.myUnLock();}},"C").start();}}
5. 独占锁(写锁)
独占锁:指该锁一次只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁
6. 共享锁(读锁)
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
7. 读写锁
package com.demo.designmode.test;import ch.qos.logback.core.util.TimeUtil;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** @Author: jxm* @Description:* 读-读能共存* 读-写不能共存* 写-写不能共存* 写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断* @Date: 2022/4/2 10:11* @Version: 1.0*/
public class JUCTest2 {public static void main(String[] args){MyCache cache = new MyCache();for (int i = 1; i < 6; i++) {final int tempInt = i;new Thread(()->{cache.put(tempInt + "",tempInt + "");},String.valueOf(i)).start();}for (int i = 1; i < 6; i++) {final int tempInt = i;new Thread(()->{cache.get(tempInt + "");},String.valueOf(i)).start();}}}/*** 资源类*/
class MyCache{private volatile Map<String,Object> map = new HashMap();private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();public void put(String key,Object value){try {readWriteLock.writeLock().lock();System.out.println(Thread.currentThread().getName()+"\t 正在写入" + key);try {TimeUnit.MICROSECONDS.sleep(300);map.put(key,value);System.out.println(Thread.currentThread().getName()+"\t 写入完成" + key);} catch (InterruptedException e) {e.printStackTrace();}} catch (Exception e) {e.printStackTrace();}finally {readWriteLock.writeLock().unlock();}}public void get(String key){try {readWriteLock.readLock().lock();System.out.println(Thread.currentThread().getName()+"\t 正在读取");try {TimeUnit.MICROSECONDS.sleep(300);Object value = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 读取完成" + value);} catch (InterruptedException e) {e.printStackTrace();}} catch (Exception e) {e.printStackTrace();}finally {readWriteLock.readLock().unlock();}}
}
5. 常用辅助类
1. CountDownLatch
倒计时(做减法)
- 让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒
- CountDownlatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞。其它线程调用countDown方法会将计数器减1(调用countDown 方法的线程不会阻塞),当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。
public static void main(String[] args) throws Exception{CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <=6; i++){new Thread(() -> {System.out.println(Thread.currentThread().getName()+"\t国,被灭");countDownLatch.countDown();},CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();}countDownLatch.await();System.out.println(Thread.currentThread().getName()+"\t"+"秦帝国,一统华夏");}//枚举 枚举类似数据库(k,v)
enum CountryEnum{ONE(1,"齐"),TWO(2,"楚"),THREE(3,"燕"),FOUR(4,"赵"),FIVE(5,"魏"),SIX(6,"韩");@Getter private Integer retCode;@Getter private String retMessage;CountryEnum(Integer retCode, String retMessage){this.retCode = retCode;this.retMessage = retMessage;}public static CountryEnum forEach_CountryEnum(int index){CountryEnum[] myArray = CountryEnum.values();for (CountryEnum element : myArray){if(index == element.getRetCode()){return element;}}return null;}}
2. CyclicBarrier
介绍:集齐七颗龙珠,召唤神龙(做加法)
CyclicBarrier的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过cyclicBarrier的await()方法。
public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {System.out.println("召唤神龙");});for (int i = 1;i <=7; i++) {final int tempInt = i;new Thread(() -> {System.out.println(Thread.currentThread().getName() + "\t收集到第:" + tempInt + "龙珠");try{cyclicBarrier.await();} catch(InterruptedException e){e.printStackTrace();} catch(BrokenBarrierException e){e.printStackTrace();}},String.valueOf(i)).start();}}
3. Semaphore
信号量 例子:抢停车位
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
public static void main(String[] args){Semaphore semaphore = new Semaphore(3); //模拟三个停车位for (int i = 1; i <= 6; i++) {new Thread(()-> {try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+"\t抢到车位了");Thread.sleep(3);System.out.println(Thread.currentThread().getName()+"离开车位");} catch (InterruptedException e) {e.printStackTrace();}finally {semaphore.release();}},String.valueOf(i)).start();}}
6. 阻塞队列(blockingQueue)
- 介绍
-
队列:先进先出 栈:先进后出
-
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
-
当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。
-
试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程从列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增
- 有什么好处?
-
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒
-
为什么需要BlockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了 -
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而
这会给我们的程序带来不小的复杂度。
-
架构
BlockingQueue 父接口 Queue 父接口 collection
已知实现类 | 描述 |
---|---|
ArrayBlockingQueue | 由数组结构组成的有界阻塞队列。 |
LinkedBlockingQueue | 由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列。 |
DelayQueue | 使用优先级队列实现的延迟无界阻塞队列。 |
SynchronousQueue | 不存储元素的阻塞队列,也即单个元素的队列。 |
LinkedTransferQueue | 由链表结构组成的无界阻塞队列。 |
LinkedBlockingDeque | 由链表结构组成的双向阻塞队列 |
- 核心方法
方法类型 | 抛出异常 | 有返回值,不抛出异常(特殊值) | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查队首元素 | element() | pee() | - | - |
抛出异常 | 当阻塞队列满时,再往队列里add插入元素会抛IIlegalStateException:Queue full 当阻塞队列空时,再往队列里remove移除元素会抛NoSuchElementException |
特殊值 | 插入方法,成功ture失败false 移除方法,成功返回出队列的元素,队列里面没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出。 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程会退出 |
ArrayBlockingQueue
package queue;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;public class Test {//Collection//list//Setpublic static void main(String[] args) {try {test4();} catch (InterruptedException e) {e.printStackTrace();}}/*** 抛出异常*/public static void test1(){//队列的大小ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.add("A"));System.out.println(arrayBlockingQueue.add("B"));System.out.println(arrayBlockingQueue.add("C"));//IllegalStateException 抛出异常//System.out.println(arrayBlockingQueue.add("D"));System.out.println("---------------");System.out.println(arrayBlockingQueue.element());//查看队首的元素System.out.println("---------------");System.out.println(arrayBlockingQueue.remove());//谁先进谁先出System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());//NoSuchElementException//System.out.println(arrayBlockingQueue.remove());}/*** 不抛出异常*/public static void test2(){ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3);System.out.println(arrayBlockingQueue.offer("A"));System.out.println(arrayBlockingQueue.offer("B"));System.out.println(arrayBlockingQueue.offer("C"));//trueSystem.out.println("-----------------");System.out.println(arrayBlockingQueue.peek());//System.out.println(arrayBlockingQueue.offer("D"));//false 不抛出异常 返回一个boolean值System.out.println("-----------------");System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());//依旧是先进先出System.out.println(arrayBlockingQueue.poll());//None 也不抛出异常}/*** 等待,阻塞(一直阻塞)*/public static void test3() throws InterruptedException {ArrayBlockingQueue arrayBlockingQueue =new ArrayBlockingQueue(3);//一直阻塞arrayBlockingQueue.put("A");arrayBlockingQueue.put("B");arrayBlockingQueue.put("C");//arrayBlockingQueue.put("D");//队列没有位置了,他会一直等待System.out.println("----------------");System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());//System.out.println(arrayBlockingQueue.take());}/*** 等待,(超时)*/public static void test4() throws InterruptedException {ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3);arrayBlockingQueue.offer("a");arrayBlockingQueue.offer("b");arrayBlockingQueue.offer("c");//arrayBlockingQueue.offer("d", 2,TimeUnit.SECONDS);//超时等待两秒arrayBlockingQueue.poll();arrayBlockingQueue.poll();arrayBlockingQueue.poll();arrayBlockingQueue.poll(2,TimeUnit.SECONDS);}
}
SynchronousQueue
package queue;import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;/*** 同步队列* 和其他的BlockingQueue不一样,SynchronousQueue 不存储元素* put了一个元素,必须先take取出来,否则不能取出值*/
public class SynchronousQueueDemo {public static void main(String[] args) {BlockingQueue<String> blockingDeque = new SynchronousQueue<>();//同步队列new Thread(()->{try {System.out.println(Thread.currentThread().getName() + "PUT 1");blockingDeque.put("1");System.out.println(Thread.currentThread().getName() + "PUT 2");blockingDeque.put("2");System.out.println(Thread.currentThread().getName() + "PUT 3");blockingDeque.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"T1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "=>" + blockingDeque.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "=>" + blockingDeque.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "=>" +blockingDeque.take());} catch (InterruptedException e) {e.printStackTrace();}},"T2").start();}
}
7. 生产者消费者问题
1. Sychronized 版本
package demo02;import sun.awt.windows.ThemeReader;/**
线程之间的通信问题:生产者和消费者问题
线程交替执行:A B操作同一个变量 num = 0
A num+1
B num-1*/
public class SYProducers {public static void main(String[] args) {Date date = new Date();//执行+1new Thread(()->{for (int i =0; i<10;i++){try {date.increment();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();//执行-1new Thread(()->{for (int i =0;i<10;i++){try {date.decrement();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}
}//判断等待,业务,通知
class Date{//数字 资源类private int number = 0;//+1public synchronized void increment() throws InterruptedException {while (number !=0){//等待this.wait();}number++;//通知其他线程,我+1完毕System.out.println(Thread.currentThread().getName() +"=>" + number);this.notifyAll();}//-1public synchronized void decrement() throws InterruptedException {while (number == 0){//等待this.wait();}number --;//通知其他线程,我-1完毕System.out.println(Thread.currentThread().getName() +"=>" + number);this.notifyAll();}}
2. Lock 版本
package com.demo.springcache.utils;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @Author: jxm* @Description:* @Date: 2022/4/4 14:56* @Version: 1.0*/
public class ProductAndConsumerTest {public static void main(String[] args) {ShareData shareData = new ShareData();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.increment();} catch (Exception e) {e.printStackTrace();}}},"A").start();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.decrement();} catch (Exception e) {e.printStackTrace();}}},"B").start();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.increment();} catch (Exception e) {e.printStackTrace();}}},"C").start();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.decrement();} catch (Exception e) {e.printStackTrace();}}},"D").start();}
}//资源类
class ShareData{private int number = 0;private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();//生产public void increment() throws Exception{lock.lock();try {while (number != 0){//等待,不能生产condition.await();}number ++;System.out.println(Thread.currentThread().getName()+"\t"+number);//通知其他线程condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}//消费public void decrement() throws Exception{lock.lock();try {while (number == 0){//等待,不能生产condition.await();}number --;System.out.println(Thread.currentThread().getName()+"\t"+number);//通知其他线程condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}
3. 虚假唤醒问题
package com.demo.springcache.utils;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @Author: jxm* @Description:* @Date: 2022/4/4 14:56* @Version: 1.0*/
public class ProductAndConsumerTest {public static void main(String[] args) {ShareData shareData = new ShareData();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.increment();} catch (Exception e) {e.printStackTrace();}}},"A").start();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.decrement();} catch (Exception e) {e.printStackTrace();}}},"B").start();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.increment();} catch (Exception e) {e.printStackTrace();}}},"C").start();new Thread(()->{for (int i = 0; i < 5; i++) {try {shareData.decrement();} catch (Exception e) {e.printStackTrace();}}},"D").start();}
}/*假设 number此时等于1,即已经被生产了产品如果这里用的是if判断,如果此时A,C两个生产者线程争夺increment()方法执行权假设A拿到执行权,经过判断number!=0成立,则A.wait()开始等待(wait()会释放锁),然后C试图去执行生产方法,但依然判断number!=0成立,则C.wait()开始等待(wait()会释放锁)碰巧这时候消费者线程线程B/D去消费了一个产品,使number=0然后,B/D消费完后调用this.notifyAll();这时候2个等待中的生产者线程继续生产产品,而此时number++ 执行了2次同理,重复上述过程,生产者线程继续wait()等待,消费者调用this.notifyAll();然后生产者继续超前生产,最终导致‘产能过剩’,即number大于1if(number != 0){// 等待condition.await();}
*/
//资源类
class ShareData{private int number = 0;private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();//生产public void increment() throws Exception{lock.lock();try {if (number != 0){ //导致虚假唤醒//等待,不能生产condition.await();}number ++;System.out.println(Thread.currentThread().getName()+"\t"+number);//通知其他线程condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}//消费public void decrement() throws Exception{lock.lock();try {if (number == 0){ //导致虚假唤醒//等待,不能生产condition.await();}number --;System.out.println(Thread.currentThread().getName()+"\t"+number);//通知其他线程condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}
4. 阻塞队列 版本
有点问题:
package com.demo.springcache.test;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;/*** @Author: jxm* @Description:* @Date: 2022/4/4 21:41* @Version: 1.0*/
public class BlockingQueueTest {public static void main(String[] args) {MyResource myResource = new MyResource(new ArrayBlockingQueue<String>(10));new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t生产线程启动");try {myResource.myProd();} catch (Exception e) {e.printStackTrace();}},"Prod").start();new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t消费线程启动");try {myResource.myConsumer();System.out.println("-------------");} catch (Exception e) {e.printStackTrace();}},"Consumer").start();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("5停止线程--------,活动结束");try {myResource.stop();} catch (Exception e) {e.printStackTrace();}}}class MyResource{//默认开启,进行生产+消费private volatile boolean FLAG = true;//原子引用private AtomicInteger atomicInteger = new AtomicInteger();BlockingQueue<String> blockingQueue = null;public MyResource(BlockingQueue<String> blockingQueue){this.blockingQueue = blockingQueue;System.out.println(Thread.currentThread().getName()+"\t");}public void myProd() throws Exception{String data = null;boolean retValue;while(FLAG){data = atomicInteger.incrementAndGet() + "";retValue = blockingQueue.offer(data,2L,TimeUnit.SECONDS);if(retValue){System.out.println(Thread.currentThread().getName()+"\t"+"插入队列"+data+"成功");}else{System.out.println(Thread.currentThread().getName()+"\t"+"插入队列"+data+"失败");}TimeUnit.SECONDS.sleep(1);}System.out.println(Thread.currentThread().getName()+"\t"+"生产动作结束,表示FLAG = false");}public void myConsumer() throws Exception{String result = null;while (FLAG){result = blockingQueue.poll(2L,TimeUnit.SECONDS);if(null == result || result.equalsIgnoreCase("")){FLAG = false;System.out.println(Thread.currentThread().getName()+"\t"+"消费超时,退出");System.out.println();return;}System.out.println(Thread.currentThread().getName()+"\t"+"消费"+result+"成功");}}//停止线程public void stop() throws Exception{this.FLAG = false;}
}
5. 唤醒区别(wait()/notifyAll() 和 Condition的 await()/signalAll())
-
锁绑定多个条件Condition
synchronized没有
ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。 -
Condition 精准的通知和唤醒线程
package demo02;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 data3 = new Data3();new Thread(()->{for (int i =0; i< 10; i++){data3.printA();}},"A").start();new Thread(()->{for (int i =0; i< 10; i++){data3.printB();}},"B").start();new Thread(()->{for (int i =0; i< 10; i++){data3.printC();}},"C").start();} } class Data3{//资源lockprivate Lock lock = new ReentrantLock();private Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();private int number = 1;public void printA(){lock.lock();try{while(number != 1){//等待condition1.await();}System.out.println(Thread.currentThread().getName() + " => A");//唤醒指定的Bcondition2.signal();number = 2;}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");condition3.signal();number = 3;}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");condition1.signal();number = 1;}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}//生产线:下单->支付操作->交易->物流 }
8. Callable 和Runnable
1、都是接口
2、Callable 有返回结果,Runnable无返回结果
3、Callable 可以抛出异常,Runnable无法抛出异常
4、方法不同
- Callable 是 java.util 包下 concurrent 下的接口,有返回值,可以抛出被检查的异常
- Runable 是 java.lang 包下的接口,没有返回值,不可以抛出被检查的异常
- 二者调用的方法不同,run()/ call()
如何使用Callable :
package com.demo.springcache.test;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println(Thread.currentThread().getName()+"-----------");return 1024;}
}/*** @Author: jxm* @Description:* @Date: 2022/4/4 22:41* @Version: 1.0*/
public class CallableTest {public static void main(String[] args) throws Exception{FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());//启动两个线程Thread thread = new Thread(futureTask,"a");Thread thread = new Thread(futureTask,"a");//获取Callable的返回结果 // get方法可能会产生阻塞,把它放到最后,后者通过异步通信System.out.println(futureTask.get());}
}
1、有缓存
2、结果可能需要等待,会阻塞!
补充创建线程的其他两种方式:
1、直接继承Thread类,然后重写run方法
public class ThreadDemo {public static void main(String[] args) {MyThread a = new MyThread();a.start();MyThread b = new MyThread();b.start();}
}class MyThread extends Thread{public void run(){}
}
2、实现Runnable接口
public class RunnableDemo {public static void main(String[] args){MyThread2 thread2 = new MyThread2();new Thread(thread2,"a").start();new Thread(thread2,"b").start();}}class MyThread2 implements Runnable {//实现Runnable接口@Overridepublic void run() {}}
参考:https://blog.csdn.net/qq_36908872/article/details/115180401
9. 线程池
1. 概念
线程池:3大方法,7大参数,4种策略
池化技术:
- 程序的运行,本质:占用系统的资源!优化资源的使用! ==> 引进了一种技术池化池
- 线程池、连接池、内存池、对象池…
- 池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。
池化技术的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程可以复用、可以控制最大并发数、管理线程
2. 三大方法
-
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
它的构造源码如下:public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>()); }
-
newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
它的构造源码如下:public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); }
-
newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
它的构造源码如下:
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory)); }
分析:
【强制】线程池不允许使用Executors去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALU 可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool : 允许的创建线程数量为Integer.MAX VALUE,可能会创建大量的线程,从而导致OOM。
3. 七大参数
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;}
手动创建线程池:
package com.demo.springcache.test;import java.util.concurrent.*;/*** @Author: jxm* @Description:* @Date: 2022/4/5 1:13* @Version: 1.0*/
public class ThreadExecutorTest {public static void main(String[] args) {ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(), //默认工厂new ThreadPoolExecutor.AbortPolicy()); //银行满了,但是还有人进来,不处理这个人,并抛出异常//总线程数量= 最大线程数量 + 阻塞队列数量,超过抛出异常//9 > 5 + 3try {for (int i = 1; i <= 9; i++) {executor.execute(()->{System.out.println(Thread.currentThread().getName()+"-OK");});}}catch (Exception e){e.printStackTrace();}finally {executor.shutdown();}}
}
异常信息:
pool-1-thread-1-OK
java.util.concurrent.RejectedExecutionException: Task com.demo.springcache.test.ThreadExecutorTest$$Lambda$1/1717159510@7591083d rejected from java.util.concurrent.ThreadPoolExecutor@77a567e1[Running, pool size = 5, active threads = 4, queued tasks = 0, completed tasks = 4]
pool-1-thread-2-OK
pool-1-thread-3-OK
pool-1-thread-5-OK
pool-1-thread-4-OK
pool-1-thread-2-OK
pool-1-thread-3-OK
pool-1-thread-1-OKat java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)at com.demo.springcache.test.ThreadExecutorTest.main(ThreadExecutorTest.java:23)
4. 四种策略
- new ThreadPoolExecutor.AbortPolicy():银行满了,还有人进来,不处理这个人的,抛出异常
- new ThreadPoolExecutor.CallerRunsPolicy():哪来的去哪里!比如你爸爸 让你去通知妈妈洗衣服,妈妈拒绝,让你回去通知爸爸洗
- new ThreadPoolExecutor.DiscardPolicy():队列满了,丢掉任务,不会抛出异常!
- new ThreadPoolExecutor.DiscardOldestPolicy():队列满了,尝试去和最早的竞争,如果竞争失败的话,也不会抛出异常!
10. ForkJoin
11. CompletableFuture
12. Volatile
1、volatile是Java虚拟机提供的轻量级的同步机制
2、三大特性:保证可见性、不保证原子性、禁止指令重排
13、JMM
JVM(Java虚拟机)
JMM(java内存模型)
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并 不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。JMM关于同步的规定:
1、线程解锁前,必须把共享变量的值刷新回主内存
2、线程加锁前,必须读取主内存的最新值到自己的工作内存
3、加锁解锁是同一把锁由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2whx7MWf-1677725484026)(https://jxm-picture.oss-cn-chengdu.aliyuncs.com/study/20221012/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE22%E5%B9%B410%E6%9C%8812%E6%97%A51415_1.png)]
特性:
1、可见性
2、原子性
3、有序性
1.可见性
package volatileT;import java.util.concurrent.TimeUnit;public class JMMDemo {//不加 volatile程序就会死循环!//加了 volatile可以保证可见性private volatile static int num = 0;public static void main(String[] args) {new Thread(()->{//线程1 对主内存的变化不知道的while(num == 0){}}).start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}num = 1;System.out.println(num);}
}
2.不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败
package com.demo.juc.volatildemo;import java.util.concurrent.atomic.AtomicInteger;/*** @Author: jxm* @Description:* @Date: 2022/10/12 14:55* @Version: 1.0*/
public class Test02 {private volatile static int num = 0;public static void add(){num++;}//理论结果因为:20000public static void main(String[] args) {for (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {add();}}).start();}//java中有两个线程是默认执行的一个是main一个GCwhile (Thread.activeCount()>2){Thread.yield();}System.out.println(Thread.currentThread().getName()+"num:"+num);}
}
加同步锁保证原子性synchronized
package com.demo.juc.volatildemo;
public class Test02 {private volatile static int num = 0;//加同步锁保证原子性public synchronized static void add(){num++;}//理论结果因为:20000public static void main(String[] args) {for (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {add();}}).start();}//java中有两个线程是默认执行的一个是main一个GCwhile (Thread.activeCount()>2){Thread.yield();}System.out.println(Thread.currentThread().getName()+"num:"+num);}
}
使用原子类来解决原子性问题
package com.demo.juc.volatildemo;import java.util.concurrent.atomic.AtomicInteger;public class Test02 {private volatile static AtomicInteger num = new AtomicInteger();public static void add(){num.getAndIncrement(); //+1方法 用底层的CAS}//理论结果因为:20000public static void main(String[] args) {for (int i = 0; i < 20; i++) {new Thread(()->{for (int j = 0; j < 1000; j++) {add();}}).start();}//java中有两个线程是默认执行的一个是main一个GCwhile (Thread.activeCount()>2){Thread.yield();}System.out.println(Thread.currentThread().getName()+"num:"+num);}
}
这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!
3.禁止指令重排
什么是指令重排?:我们写的程序,计算机并不是按照你写的那样去执行的。
源代码 —> 编译器优化的重排 —> 指令并行也可能会重排 —> 内存系统也会重排 ——> 执行
处理器在执行指令重排的时候,会考虑:数据之间的依赖性
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
我们所期望的:1234 ,但是可能执行的时候会变成 2134 或者 1324,但是不可能是 4123!
示例:
前提:a b x y 这四个值默认都是 0:
可能造成影响得到不同的结果:
线程A | 线程B |
---|---|
x = a | y = b |
b =1 | a = 2 |
正常的结果:x = 0,y = 0;
但是由于指定重排可能执行顺序发生变化,出现以下结果:
线程A | 线程B |
---|---|
b = 1 | a = 2 |
x = a | y = b |
指令重排导致的异常结果:x = 2,y= 1;
volatile 可以避免指令重排:
内存屏障是一个CPU指令。作用:
1.保持特定操作的执行顺序!
2.可以保证某些变量的内存可见性(利用volatile实现了可见性)
Volatile是可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
volatile 内存屏障在单例模式中使用的最多!
14、单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
//懒汉式单例模式
private LazySingleton(){}private volatile static LazySingleton singleton;//lazyMan必须加上volatile防止指令重排private LazySingleton(){}public static LazySingleton getInstance(){if(singleton == null){synchronized (LazySingleton.class){if(singleton == null){singleton = new LazySingleton(); //不是一个原子性操作/*** 1.分配内存空间* 2.执行构造方法,初始化对象* 3.把这个对象指向这个空间** 期望顺序是:123* 特殊情况下实际执行:132 ===> 此时 A 线程没有问题* 若额外加一个 B 线程* 此时lazyMan还没有完成构造*/}}}return singleton;}
15、CAS
public class CasDemo01 {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2021);//原子类//如果和期望的值相同就更新,CAS是CPU的并发原语System.out.println(atomicInteger.compareAndSet(2022,2023));System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(2021,2022));System.out.println(atomicInteger.get());//CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!}
}//结果:
false
2021
true
2022
AtomicInteger原子引用类,底层使用CAS(乐观锁)
源码:
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {//获取内存地址中的值var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //内存操作,效率很高return var5;
}
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
1.循环会耗时
2.一次性只能保证一个共享变量的原子性
3.存在ABA问题
CAS : ABA 问题(狸猫换太子)
public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2020);//原子类//对我们写平时写的sql:乐观锁//如果和期望的值相同就更新,CAS是CPU的并发原语//*******捣乱的线程**********System.out.println(atomicInteger.compareAndSet(2020, 2021));System.out.println(atomicInteger.get()); // 2021System.out.println(atomicInteger.compareAndSet(2021, 2020));System.out.println(atomicInteger.get()); // 2021//********期望的线程*********System.out.println(atomicInteger.compareAndSet(2020, 2021));System.out.println(atomicInteger.get());//2021}
如何解决这个问题?原子引用
解决ABA问题:原子引用,带版本号的原子操作
**注意:Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;**每一次的2020都是一个新的值
package cas;import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;public class CASDemo02 {//比较并交换public static void main(String[] args) {//AtomicInteger atomicInteger = new AtomicInteger(2020);//原子类//Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(11,1);//初始值,版本号时间戳new Thread(()->{int stamp = atomicInteger.getStamp();//获得版本号System.out.println("A = >" + stamp);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//最后两个参数,拿到最新的版本号,把版本号+1System.out.println(atomicInteger.compareAndSet(11, 22, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));System.out.println("A2 = >"+atomicInteger.getStamp());//把这个值该回去System.out.println(atomicInteger.compareAndSet(22, 11, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));System.out.println("A3 = >"+atomicInteger.getStamp());},"a").start();new Thread(()->{int stamp = atomicInteger.getStamp();//获得版本号System.out.println("B = >" + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicInteger.compareAndSet(11, 66, stamp, stamp + 1));System.out.println("b2 = >" +atomicInteger.getStamp());},"b").start();}
}
16、死锁
17、死锁排查分析
1、概念
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-woyLeIz9-1677725484028)(https://jxm-picture.oss-cn-chengdu.aliyuncs.com/study/20221013/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE22%E5%B9%B410%E6%9C%8813%E6%97%A50934_1.png)]
2、产生死锁的主要原因
2.1 系统资源不足
2.2 进程推进的顺序不合适
2.3 资源分配不当
示例:
package com.demo.juc.thread;import java.util.concurrent.TimeUnit;public class DeadlockDemo {public static void main(String[] args) {String lockA = "lockA";String lockB = "lockB";new Thread(new MyThread3(lockA,lockB),"T1").start();new Thread(new MyThread3(lockB,lockA),"T2").start();}
}class MyThread3 implements Runnable{private String lockA;private String lockB;public MyThread3(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA){//lockA想拿BSystem.out.println(Thread.currentThread().getName() + "lock"+ lockA + "=> get" + lockB);try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB){//B想拿ASystem.out.println(Thread.currentThread().getName() + "lock" + lockB + "=> get" + lockA);}}}
}
运行结果:
T2locklockB=> getlockA
T1locklockA=> getlockB
排查问题:死锁
解决问题:
1.使用jps定位进程号 jps-l
E:\workSpace\idea\testWork\springboot-demo\demo-juc\src\main\java\com\demo\juc\thread>jps -l
2960 sun.tools.jps.Jps
17748
3556 org.jetbrains.idea.maven.server.RemoteMavenServer36
9364 com.demo.juc.thread.DeadlockDemo
4652 org.jetbrains.jps.cmdline.Launcher
2.使用jstack
查看进程信息,进程号来找到死锁信息
E:\workSpace\idea\testWork\springboot-demo\demo-juc\src\main\java\com\demo\juc\thread>jstack 9364
Found one Java-level deadlock:
=============================
"T2":waiting to lock monitor 0x0000025920ec5d08 (object 0x000000076c38bee0, a java.lang.String),which is held by "T1"
"T1":waiting to lock monitor 0x0000025920ec84e8 (object 0x000000076c38bf18, a java.lang.String),which is held by "T2"Java stack information for the threads listed above:
===================================================
"T2":at com.demo.juc.thread.MyThread3.run(DeadlockDemo.java:37)- waiting to lock <0x000000076c38bee0> (a java.lang.String) //等待- locked <0x000000076c38bf18> (a java.lang.String) //锁at java.lang.Thread.run(Thread.java:748)
"T1":at com.demo.juc.thread.MyThread3.run(DeadlockDemo.java:37)- waiting to lock <0x000000076c38bf18> (a java.lang.String) //等待- locked <0x000000076c38bee0> (a java.lang.String) //锁at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.E:\workSpace\idea\testWork\springboot-demo\demo-juc\src\main\java\com\demo\juc\thread>
总结:
面试、工作中!排查问题:
1.日志
2.堆栈信息
知识点来源狂神说、尚硅谷juc教程视频
尚硅谷Java大厂面试题第2季学习笔记——jvm部分
尚硅谷Java大厂面试题第2季学习笔记——Java引用(强软弱虚)
尚硅谷Java大厂面试题第2季学习笔记——垃圾收集器部分