文章目录
1.juc 编程
源码+官方文档 面试高频
java.util.concurrent 工具包
2.进程和线程
进程和线程
进程:一个程序 xx.exe
一个进程可以包含多个线程,至少包含一个
java 默认有 2个线程; main 、GC
线程:开了一个进程 Typora,写字,自动保存(线程负责的)
对于java 而言:Thread、Runnable、Callable
java真的 可以开启线程吗? 不能
线程本质是竞争cpu使用权
java 没有 调用硬件的权限,本地方法 native 底层是c++ 可以
并发编程:并发、并行
并发(多线程操作同一个资源)
cpu一核,模拟出来多条线程, 快速交替
并行(多个人一起行走)
cpu多核,多线程可以同时执行,线程池
获取电脑核数 代码:
Runtime.getRuntime().availableProcessors()
或查看 此电脑--右键--设备管理器--cpu ,数个数
并发编程的本质,充分利用CPU资源
线程有几种状态
Thread 有枚举类 State
Thread.State
/* @since 1.5
* @see #getState
*/
public enum State {
// 新建
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待(过期不候)
TIMED_WAITING,
// 死亡
TERMINATED
}
wait/sleep的区别
-
来自不同的类
wait --> object
sleep-->Thread
-
关于锁的释放
wait会释放锁、sleep睡觉了,抱着锁睡觉,不会释放
-
使用范围是不同的
wait 必须在同步代码块中,必须要 notify
sleep 可以再任何地方睡觉
3.Lock锁(重点)
传统 synchronized
// 基本的卖票例子
public class saleTickets {
public static void main (String[] args) {
Ticket tk = new Ticket(); // 创建共享资源类
// 多线程操作同一资源,把资源类丢入线程
new Thread(()->{ for (int i=0;i<50;i++) tk.sale(); },"A窗口").start();
new Thread(()->{ for (int i=0;i<50;i++) tk.sale(); },"B窗口").start();
new Thread(()->{ for (int i=0;i<50;i++) tk.sale(); },"C窗口").start();
}
}
// 资源OOP 编程,不直接 implement Rannable
class Ticket{
private int number =30;
// synchronized 本质; 锁、队列
// (synchronized 一个线程使用,别的线程会阻塞,直到线程使用完后,别的再进行竞争cpu使用权)
public synchronized void sale() {
if (number >0){
System.out.println(Thread.currentThread().getName()+"==》正在卖出第 "+number--+" 张票,还剩"+number+"张票");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
lock 接口
包 java.util.concurrent.locks --> interface lock ---->实现类:
ReentrantLock(可重入锁,普通锁)
ReentrantReadWriteLock.ReadLock (读锁)
ReentrantReadWriteLock.WriteLock (写锁)
ReentrantLock
公平锁十分公平:可以先来后到 ,不管来的早的占多长时间 (3s 3h)
非公平锁 :十分不公平,可以插队 (默认是非公平锁)
以下是构造方法:
public class saleTickets2 {
public static void main (String[] args) {
Ticket2 tk2 = new Ticket2(); // 创建共享资源类
// 多线程操作同一资源,把资源类丢入线程
new Thread(()->{ for (int i=0;i<50;i++) tk2.sale(); },"A窗口").start();
new Thread(()->{ for (int i=0;i<50;i++) tk2.sale(); },"B窗口").start();
new Thread(()->{ for (int i=0;i<50;i++) tk2.sale(); },"C窗口").start();
}
}
// 资源OOP 编程,不直接 implement Rannable
class Ticket2{
private int number =30;
// Lock三部曲
// 1、 new ReentrantLock();
// 2、 lock.lock(); // 加锁 ,获取cpu使用权
// 3、 finally=> lock.unlock(); // 解锁,释放cpu使用权(必须写在finally中)
Lock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (number >0){
System.out.println(Thread.currentThread().getName()+"==》正在卖出第 "+number--+" 张票,还剩"+number+"张票");
}
// 经过的线程休眠500ms,拉长占用时间,
// 让别的线程在本线程同步代码执行完后,有更多机会拿到 cpu 使用权
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
synchronized 和 lock 区别(自动档与手动档的区别)
- Synchronized 内置的Java关键字, Lock 是一个Java类
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
- Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);
- Lock锁就不一定会等待下去(lock.tryLock(); 尝试获取锁的方法);
-
Synchronized 可重入锁,不可以中断的,非公平;
Lock ,可重入锁,可以 判断锁,非公平(可以自己设置);
-
Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
锁是什么,如何判断锁的是谁!(八锁问题)
4.生产者和消费者模式问题
面试必问:单例模式、排序算法、生产者和消费者、死锁
生产者和消费者问题 Synchronized 版
public class ProandCus {
// A,B线程 交替执行 + 1 、-1 操作
// A+1 B-1
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{ for(int i=0;i<10;i++) data.increment(); },"生产者A").start();
new Thread(()->{ for(int i=0;i<10;i++) data.decrement(); },"消费者B").start();
}
}
class Data { // 创建资源类
private int num =0;
// 加 1 操作
public synchronized void increment() {
if(num !=0){ // 是 0 才行,否则等待
try {
this.wait(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName()+"===>"+num);
this.notifyAll(); // 唤醒其他线程 + 1完毕
}
// 减去 1操作
public synchronized void decrement() {
if(num == 0){ // 是 1 才行,否则等待
try {
this.wait();// 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName()+"===>"+num);
this.notifyAll();// 唤醒其他线程 -1完毕
}
}
如果有 A B C D 四个线程?
问题存在,A B C D 4 个线程! 虚假唤醒 java.lang.object --> wait
if 改为 while 判断:
- if 判断只判断一次,有可能多个线程同时进去、等待应该出现在循环中。
public class ProandCus {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{ for(int i=0;i<10;i++) data.increment(); },"生产者A").start();
new Thread(()->{ for(int i=0;i<10;i++) data.decrement(); },"消费者B").start();
new Thread(()->{ for(int i=0;i<10;i++) data.increment(); },"生产者C").start();
new Thread(()->{ for(int i=0;i<10;i++) data.decrement(); },"消费者D").start();
}
}
class Data { // 创建资源类
private int num =0;
// 加 1 操作
public synchronized void increment() {
while(num !=0){ // 是 0 才行,否则等待
try {
this.wait();// 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName()+"===>"+num);
this.notifyAll(); // 唤醒其他线程 + 1完毕
}
// 减去 1操作
public synchronized void decrement() {
while(num == 0){ // 是 1 才行,否则等待
try {
this.wait();// 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName()+"===>"+num);
this.notifyAll();// 唤醒其他线程 -1完毕
}
}
JUC版的生产者和消费者问题
public class ProandCus2 {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{ for(int i=0;i<10;i++) data2.increment(); },"生产者A").start();
new Thread(()->{ for(int i=0;i<10;i++) data2.decrement(); },"消费者B").start();
new Thread(()->{ for(int i=0;i<10;i++) data2.increment(); },"生产者C").start();
new Thread(()->{ for(int i=0;i<10;i++) data2.decrement(); },"消费者D").start();
}
}
class Data2 { // 创建资源类
private int num =0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 加 1 操作
public void increment() {
lock.lock(); // 上锁
try {
while (num !=0){ // 是 0 才行,否则等待
condition.await();// 等待
}
num++;
System.out.println(Thread.currentThread().getName()+"===>"+num);
condition.signalAll(); // 唤醒其他线程 + 1完毕
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
// 减去 1操作
public void decrement() {
lock.lock(); // 上锁
try {
while (num ==0){ // 不是 0 才行,否则等待
condition.await();// 等待
}
num--;
System.out.println(Thread.currentThread().getName()+"===>"+num);
condition.signalAll(); // 唤醒其他线程 -1完毕
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}
任何一个新的技术,绝对不是仅仅覆盖原来的就技术,一定有优势和补充。
condition 精准的通知和唤醒
public class ProandCus3 {
// 1-A 2-B 3-C
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{ for(int i=0;i<10;i++) data3.print1(); },"A").start();
new Thread(()->{ for(int i=0;i<10;i++) data3.print2(); },"B").start();
new Thread(()->{ for(int i=0;i<10;i++) data3.print3(); },"C").start();
}
}
class Data3 {
Lock lock = new ReentrantLock();
// 创建三个监听器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int num =1;
public void print1() {
lock.lock(); // 上锁
try {
while (num !=1){ // 是 1 才行,否则等待
condition1.await();// 等待
}
num = 2;
System.out.println(Thread.currentThread().getName()+"===>"+num);
condition2.signal(); // 唤醒2线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
public void print2() {
lock.lock();
try {
while (num != 2){ // 是 2 才行,否则等待
condition2.await(); // 等待
}
num = 3;
System.out.println(Thread.currentThread().getName()+"===>"+num);
condition3.signal();// 唤醒3线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print3() {
lock.lock();
try {
while (num != 3){ // 是 3 才行,否则等待
condition3.await(); // 等待
}
num = 1;
System.out.println(Thread.currentThread().getName()+"===>"+num);
condition1.signal();// 唤醒1线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5. 八锁现象
判断锁的是谁! 对象、class
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
* 1、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话
*/
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
//锁的存在
new Thread(()->{ phone.sendSms();},"A").start();
try {
TimeUnit.SECONDS.sleep(1); // JUC的休眠方式
} catch (InterruptedException e) {e.printStackTrace();
}
new Thread(()->{phone.doCall();},"B").start();}
}
class Phone{
// synchronized 锁的对象是方法的调用者!
// 两个方法用的是同一个锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void doCall(){
System.out.println("打电话");
}
}
======================================================
import java.util.concurrent.TimeUnit;
/**
* 3、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
*/
public class Test02 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(()->{ phone1.sendSms();},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone2.doCall();},"B").start();
}
}
class Phone2 {
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void doCall() {
System.out.println("打电话");
}
}
=======================================================
import java.util.concurrent.TimeUnit;
/**
* 4、 增加了一个普通方法后!先执行发短信还是Hello? 普通方法
*/
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone3 phone = new Phone3();
//锁的存在
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone.hello(); },"B").start();
}
}
class Phone3{
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void doCall(){
System.out.println("打电话");
}
// 这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
=====================================================
import java.util.concurrent.TimeUnit;
/**
* 5、增加两个静态的同步方法,只有一个对象,先打印? 发短信->打电话
* 6、两个对象!增加两个静态的同步方法, 先打印 发短信->打电话
*/
public class Test04 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(() -> { phone1.sendSms(); }, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> { phone2.doCall(); }, "B").start();
}
}
// Phone4唯一的一个 Class 对象
class Phone4{
// synchronized 锁的对象是方法的调用者!
// static 静态方法
// 类一加载就有了!锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void doCall(){
System.out.println("打电话");
}
}
===========================================
import java.util.concurrent.TimeUnit;
/**
* 7、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
* 8、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
*/
public class Test05 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone5 phone1 = new Phone5();
Phone5 phone2 = new Phone5();
//锁的存在
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone2.doCall(); },"B").start();
}
}
// Phone5唯一的一个 Class 对象
class Phone5{
// 静态的同步方法 锁的是 Class 类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通的同步方法 锁的调用者
public synchronized void doCall(){
System.out.println("打电话");
}
}
小结
synchronzied 锁指向被调用者~(跟同步方法加不加 static 有很大关系)
new 出来的对象,调用同步方法 --> 锁对象是:this 具体的一个new出来的对象
new 出来的对象,调用加static的 方法 --> 锁对象是: class 唯一的一个模板
6.集合类不安全
list 集合
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
报错:java.util.ConcurrentModificationException并发修改异常。
并发争取修改导致,一个线程正在写,一个线程过来争抢,导致线程写的过程被其他线程打断,导致数据不一致。
- 解决方案
public static void main(String[] args) {
//1.
// List<String> list = new Vector<>();
//2.
// List<String> list = Collections.synchronizedList(new ArrayList<>());
//3.
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
第一种:使用List arrayList = new Vector<>();它的底层使用了synchronized加锁,但是并发速度下降
第二种:使用List arrayList = Collections.synchronizedList(new ArrayList()); 使用 Collections工具类,线程同步方法。
第三种:JUC方式,使用List arrayList = new CopyOnWriteArrayList<>();写时复制,效率最高
这样写时复制实现了读写分离,我们不需要在读的时候加锁(之前读需要加锁是因为读写不能同时进行,但一旦给读加了锁,那么读也不能同时进行,就降低了并发效率)。
set集合
public static void main(String[] args) {
// 解决方案
//1.变成线程安全的
// Set<String> hashSet = Collections.synchronizedSet(new HashSet<>());
//2. JUC方式
Set<String> hashSet = new CopyOnWriteArraySet<>();
//(它的底层还是 new CopyOnWriteArrayList<>();)
for (int i = 0; i <30 ; i++) {
new Thread(()->{
hashSet.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(hashSet);
},String.valueOf(i)).start();
}
}
-
解决方案
第一种:Set hashSet = Collections.synchronizedSet(new HashSet<>());
第二种:Set hashSet = new CopyOnWriteArraySet();(它的底层还是 new CopyOnWriteArrayList<>();)
hashSet 底层是什么? HashSet的底层就是HashMap
public HashSet() {
map = new HashMap<>();
}
====================
// add set本质就是 map key 是无法重复的
//使用HashSet的add方法,依然是调用HashMap的底层put方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
====================
private static final Object PRESENT = new Object();// 是个固定值
map 集合
基本回顾: 1.map 在工作中不用 hashMap
2.new HashMap<>(); 默认等价于什么? new HashMap<>(16,0.75);
public static void main(String[] args) {
//Map<String,String> hashMap = new HashMap<>();
// 解决方案
//1.变成线程安全的
//Map<String,String> hashMap = Collections.synchronizedMap(new HashMap<>());
//2.JUC方式
Map<String,String> hashMap = new ConcurrentHashMap<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
hashMap.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(hashMap);
},String.valueOf(i)).start();
}
}
-
解决方案:
第一种:Map<String,String> hashMap = Collections.synchronizedMap(new HashMap<>());
第二种:Map<String,String> hashMap = new ConcurrentHashMap<>(); //无论读取还是写入,都会进行加锁,当并发级别特别高,线程之间在任何操作上都会进行等待,效率低。
-
ConcurrentHashMap是面试中常问的问题
采用分段锁技术,其中Segment继承于ReentrantLock。不会像HashTable(线程安全) 那样不管是put还是get操作都需要做同步处理,理论上ConcurrentHashMap支持CurrentLevel(Segment数组数量)的线程并发。每当一个线程占用锁访问一个Segment时,不会影响其他的Segment。
7.Callable(实现多线程的第三种方式)
Callable与 Runnable
java.util.concurrent.Callabel, Callable是一个接口,类似于Runnable,两者都是为那些实例可能被另一个线程执行的类设计的。
java.lang.Runnable, 是一个接口。其实例由线程执行。该类必须定义一个无参数的方法称为run。Runnable不会返回结果,也不会抛出异常。run方法的异常只能在内部消化不能上抛。
结论:
- Callable可以有返回值
- Callable可以抛出异常
- 方法不同call()/run()
- Callable与Future结合,实现利用Future来跟踪异步计算的结果
Callable 如何启动
new Thread().start 中参数只能是 Runable,不能是callable;callable通过 Runable 使用Thread,通过适配类 futrueTask。
①futrueTask 是Runable 的实现类
②futrueTask 可以调用callable
public class CallableTest {
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>( Callable )).start();
//new Thread().start();
// 怎么启动Callable?
public static void main(String[] args) throws Exception {
Mythread mythread = new Mythread();
FutureTask futureTask = new FutureTask(mythread);//适配类
new Thread(futureTask,"线程A").start();
//call ()只打印一次,结果会被缓存,提高效率
new Thread(futureTask,"线程B").start();
//方法中使用耗时操作,get这个方法可能会产生阻塞,把它放到最后 或 使用异步通信
Integer result = (Integer) futureTask.get();
System.out.println(result);
}
}
class Mythread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 耗时操作...
System.out.println("这是callable的 call()方法");
return 1024;
}
}
小结:
1.Callable 有缓存
2.结果可能需要等待,会阻塞。
异步计算:
用不同的线程去做不同的事,最后再进行数据汇总并返回结果。是分布式计算的一种实现方式。
8.常用辅助类(高并发必会)
8.1 .CountDownLatch 减法计数器
java.util.CountDownLatch 倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
public class CountDownLatchTest {
// 减法计数器
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <7 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"出去");
countDownLatch.countDown(); // 数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,再往下执行代码
System.out.println("关门");
}
}
总结
countDownLatch.countDown();
// 数量-1
countDownLatch.await();
// 等待计数器归零,再往下执行代码。
每次有线程调用 countDown() -1,假设计数器变为0,countDownLatch.await();
就会被唤醒继续执行。
8.2.CyclicBarrier 加法计数器 (栅栏,循环屏障)
是一个同步辅助类,允许一组线程相互等待直到所有线程到达一个公共的屏障点。在程序中有固定数量的线程,这些线程有时候必须等待彼此,这种情况下,使用CyclicBarrier。能阻塞一组线程直到某个事件的发生。它与闭锁的区别关键在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
例子:长途汽车站提供长途客运服务。当等待坐车的乘客到达20人时,汽车站就会发出一辆长途汽车,让这20个乘客上车走人。等到下次等待的乘客又到达20人是,汽车站就会又发出一辆长途汽车。
public class CyclicBarrierTest {
public static void main(String[] args) {
// 出生葫芦娃的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(8,()->{
System.out.println("八个葫芦娃破壳出生!");
});
for (int i = 1; i < 9; i++) {
//lambda 不能操作到 i,要通过 final 变量
final int temp =i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"葫芦藤正在结出第"+temp+"个葫芦~");
try {
cyclicBarrier.await(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3 Semaphore 信号量;
也是一个线程同步的辅助类,一个计数信号量,可以维护当前访问自身的线程个数,并提供了同步机制。semaphore可以控制同时访问资源的线程个数,而不是访问某些资源。例如,实现一个文件允许的并发访问数。
主要有两个目的:
- 用于多个共享资源的互斥使用
- 用于并发线程数的控制 (限流!)
Semaphore可以达到复用,有减,有加,一直使用。
6车-3个停车位,占住车位的开走了,才能进新的车
public class SemaphoreTest {
public static void main(String[] args) {
// 线程数量-->停车位!限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i < 7; i++) {
new Thread(()->{
try {
semaphore.acquire(); // 获得
System.out.println(Thread.currentThread().getName()+"抢到车位!");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放
}
},String.valueOf(i)).start();
}
}
}
总结:
semaphore.acquire();
获得 ,会将当前的信号量释放 -1,假设如果已经满了,等待,等到被释放为止。
semaphore.release();
释放,会将当前的信号量释放 +1 ,然后唤醒等待的线程。
9.读写锁 ReadWriteLock
public class ReadWriteLockTest {
/**
*独占锁(写锁) 一次只能被一个线程占有
*共享锁(读锁) 多个线程可以同时占有
*ReadWriteLock
* 读--读 可以共存
* 写--读 不能共存
* 写--写 不能共存
*/
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
// 写入
for (int i = 1; i <=30 ; i++) {
final int temp =i;
new Thread(()->{
myCache.writeMethod(temp+"",temp);
},String.valueOf(i)).start();
}
// 读取
for (int i = 1; i <=30 ; i++) {
final int temp =i;
new Thread(()->{
myCache.readMethod(temp+"");
},String.valueOf(i)).start();
}
}
}
// 普通缓存类
class MyCache{
private volatile Map<String,Object> map = new HashMap();
public void writeMethod(String key,Object value){
System.out.println(Thread.currentThread().getName()+"正在写入...");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完毕");
}
public void readMethod(String key){
System.out.println(Thread.currentThread().getName()+"正在读取...");
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完毕");
}
}
// 读写锁缓存类
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap();
// 读写锁:更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写入时,只希望同时只有一个线程
public void writeMethod(String key,Object value){
readWriteLock.writeLock().lock();// 上写锁
try {
System.out.println(Thread.currentThread().getName()+"正在写入...");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock(); // 释放写锁
}
}
// 读时,希望所有线程都可以读。
public void readMethod(String key){
readWriteLock.readLock().lock(); // 上读锁
try {
System.out.println(Thread.currentThread().getName()+"正在读取...");
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();// 释放读锁
}
}
}
多线程同时读一个资源类没有问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源,就不应该有其他线程对该资源进行读或写
10.BlockingQueue 阻塞队列
是一个支持两个操作的队列:不得不阻塞
- 读:在队列为空时,必须阻塞等待生产。
- 写:当队列满时,就必须阻塞等待。
常用于生产者和消费者的场景,生产者—>往队列里添加元素,消费者—>从队列里拿取元素。
阻塞队列的用处:
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。我们不用担心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都做了。
(参考:https://zhuanlan.zhihu.com/p/268678714)
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:由链表结构组成的有界阻塞队列
- SynchronousQueue:不存储元素的阻塞队列,即单个元素队列
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列
- LinkedTransferQueue:由列表组成的无界阻塞队列
- LinkedBlockingDeque:由链表组成的双向阻塞队列
BlockingQueue 是Collection的一个子类
什么情况下我们会使用阻塞队列?多线程并发处理、线程池
学会使用队列:添加、移除
四组API:
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待(死等) | 超时等待(不死等) |
---|---|---|---|---|
添加 | add(e) | offer(e) | put(e) | offer(e,time,timeUnit) |
移除 | remove()或remove(e) | poll() | take() | poll(time,timeUnit) |
检测队首元素 | element | peek | -不可用 | -不可用 |
public class BlockingQueueTest {
public static void main(String[] args) {
Test1();
}
/**
* 抛出异常:
*/
public static void Test1(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为3
System.out.println(blockingQueue.add("A")); // true
System.out.println(blockingQueue.add("B")); // true
System.out.println(blockingQueue.add("C")); // true
//System.out.println(blockingQueue.add("D"));// java.lang.IllegalStateException: Queue full
System.out.println(blockingQueue.element());// 队首 A
System.out.println(blockingQueue.remove());// remove方法移除队首元素
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());// 没有了java.util.NoSuchElementException
System.out.println(blockingQueue.remove("E")); // 删除不存在 元素false
}
}
抛出异常总结:
- add(e):当队列容量已满,再添加元素就会抛出IllegalStateException:Queue full异常
- remove():当队列没有元素,再移除元素,则抛出NoSuchElementException;
如果删除一个队列中不存在的元素,不会抛出异常,返回false - element():如果队列为空,则抛出NoSuchElementException,若不为空,则返回第一个元素。
/**
* 有返回值,没有异常
*/
public static void Test2(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为3
System.out.println(blockingQueue.offer("A")); // true
System.out.println(blockingQueue.offer("B")); // true
System.out.println(blockingQueue.offer("C")); // true
System.out.println(blockingQueue.offer("D"));// 超出队列 false
System.out.println(blockingQueue.peek());// 队首 A
System.out.println(blockingQueue.poll());// poll方法移除队首元素
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());// 队列里没有了 null,不抛异常
}
有返回值,不抛出异常总结:
- offer(e):如果可以的话,将元素添加到队列,返回true;
如果队列已满,则不添加,返回false.不阻塞执行线程 - poll():从队列中返回第一个元素,如果没有返回null
- peek():从队列中返回第一个元素,如果没有返回null
/**
* 等待阻塞
*/
public static void Test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为3
blockingQueue.put("A");
blockingQueue.put("B");
blockingQueue.put("C");
// blockingQueue.put("D");// 超了容量 一直等待
System.out.println(blockingQueue.peek());// 队首 A
System.out.println(blockingQueue.take());// take方法移除队首元素
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());// 阻塞,一直等待
}
}
等待阻塞总结:
- put(e):把元素添加到阻塞队列中,如果阻塞队列没有空间,则调用此方法的线程被阻断直到队列有空间再继续
- take():如果取到则返回,如果没有取到,则一直阻塞,直到队列不为空
/**
* 超时等待
*/
public static void Test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);// 容量为3
System.out.println(blockingQueue.offer("A"));// true
System.out.println(blockingQueue.offer("B"));// true
System.out.println(blockingQueue.offer("C"));// true
// 等待2退出
System.out.println(blockingQueue.offer("D", 2, TimeUnit.SECONDS)); // false
System.out.println(blockingQueue.peek());// 队首 A
System.out.println(blockingQueue.poll());// poll方法移除队首元素
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));// 阻塞只等待2s
}
}
超时等待总结:
- offer(e,time,unit):将指定的元素插入到此队列中,等待指定的等待时间。
- poll(time):检索并删除此队列的头,等待指定的等待时间。
SynchronousQueue 同步队列
没有容量,进去一个元素,必须等待取出来之后,才能再往里放另外的元素。
方式: put 、take
public class SynchronousQueueTest {
public static void main(String[] args) {
SynchronousQueue synchronousQueue = new SynchronousQueue();// 没有容量
new Thread(()->{
try {
synchronousQueue.put("A");
System.out.println(Thread.currentThread().getName()+"存放了 A");
synchronousQueue.put("B");
System.out.println(Thread.currentThread().getName()+"存放了 B");
synchronousQueue.put("C");
System.out.println(Thread.currentThread().getName()+"存放了 C");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程一").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出 "+ synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出 "+ synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出 "+ synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程二").start();
}
}
总结:
没有容量,一个生产线程,当它生产产品(put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞。等待一个消费线程调用take操作,take操作会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递)。这样的一个过程称为一次配对过程(也可以先take,后put 原理相同)。
11.线程池(重点)
线程池:三大方法、7大参数、4种拒绝策略
程序运行的本质:占用系统的资源! 优化资源的使用!=>池化技术
池化技术:通过预先创建好多个线程,放在池中,这样可以在需要使用线程的时候直接获取,用完之后放回池中,避免多次重复创建、销毁带来的开销。(创建、销毁,十分浪费资源)
线程池、连接池、内存池、对象池…
线程池的好处:
1、 降低资源的消耗
2、 提高响应的速度
3、 方便管理。
线程复用、可以控制最大并发数、管理线程
阿里巴巴开发手册
三大方法
Executors.newSingleThreadExecutor();
Executors.newFixedThreadPool(5);
newCachedThreadPool();
public class ExecutorTest {
// Executors 工具类、3大方法
public static void main(String[] args) {
// 单个线程
// ExecutorService threadpool = Executors.newSingleThreadExecutor();
// 创建一个固定的线程池大小
// ExecutorService threadpool = Executors.newFixedThreadPool(5);
// 可伸缩的线程池,遇强则强,遇弱则弱
ExecutorService threadpool = Executors.newCachedThreadPool();
// 最大承载:Deque + max
for (int i = 1; i <=5 ; i++) {
threadpool.execute(()->{
try {
System.out.println(Thread.currentThread().getName()+"---执行");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadpool.shutdown();
}
});
}
}
}
7大参数
三大方法源码分析:
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>());
}
// @Native public static final int MAX_VALUE = 0x7fffffff; 略为 21 亿
=======================================================================================
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;
}
4种拒绝策略
-
new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常。
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize。 -
new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!–》 main线程
-
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
-
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
银行排队例子
自定义线程池
public class MyPool {
public static void main(String[] args) {
// 最大承载:max + Deque 5 + 3
ExecutorService threadpool = new ThreadPoolExecutor(2,
5,
2,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),// 一般默认不改
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
for (int i = 1; i <=9 ; i++) {
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadpool.shutdown();
}
}
}
如何设置线程池的大小(CPU密集型和IO密集型)
CPU密集型和IO密集型:
1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
// 获取cpu 的核数
int max = Runtime.getRuntime().availableProcessors();
ExecutorService service =new ThreadPoolExecutor(
2,
max, // cpu 的核数
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
2、I/O密集型:
I/O密集型就是判断我们程序中十分耗I/O的线程数量,设置要大于十分消耗I/O线程数量;是最大I/O数的一倍到两倍之间。
12.四大函数式接口(重点)
新时代程序员必会:lambda表达式,链式编程、函数式接口、Stream流式计算。
函数式接口,只有一个方法的接口
Function函数式接口
有一个输入参数,有一个输出
只要是函数型接口,都可以用 lambda表达式简化
public class funcTest {
public static void main(String[] args) {
// Function<String,String> function = new Function<String,String>() {
// @Override
// public String apply(String str) {
// return o+"函数式";
// }
// };
Function<String,String> function=(str)->{return str+"函数式";};
System.out.println(function.apply("传入"));
}
}
Predicate 断定型接口
有一个输入参数,返回值只能是 布尔值!
public class PredicateTest{
public static void main(String[] args) {
// Predicate<String> p = new Predicate<String>() {
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
Predicate<String> p= (str)->{
return str.isEmpty();
};
System.out.println(p.test("传入字符串"));
}
}
Consumer 消费型接口
Consumer 消费型接口: 只有输入,没有返回值
public class ConsumerTest {
public static void main(String[] args) {
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s+"传进来了");
// }
// };
//
Consumer<String> consumer = (str)->{System.out.println(str+"传进来了");};
consumer.accept("big");
}
}
Supplier 供给型接口
Supplier 供给型接口 没有参数,只有返回值
public class SupplierTest {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier() {
// @Override
// public Object get() {
// return "输出字符串!";
// }
// };
Supplier<String> supplier =()->{return "输出字符串!";};
System.out.println(supplier.get());
}
}
13.strem流式计算(并行流)
大数据:存储 + 计算
存储:集合、MySQL 本质就是存储东西的;
计算:都应该交给流来操作!
大数据scala、框架底层也用得到。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
=============================================
public class StreamTest {
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输出一个用户!
*/
public static void main(String[] args) {
User user1 = new User(1,"a",21);
User user2 = new User(2,"b",22);
User user3 = new User(3,"c",23);
User user4 = new User(4,"d",24);
User user5 = new User(5,"e",25);
User user6 = new User(6,"f",26);
User user7 = new User(7,"g",27);
User user8 = new User(8,"h",28);
List<User> arrayList = Arrays.asList(user1,user2,user3,user4,user5,user6,user7,user8);
// 计算交给Stream流
// lambda表达式、链式编程、函数式接口、Stream流式计算
arrayList.stream().filter((user)->{return user.getId()%2 ==0;})
.filter((user)->{return user.getAge()>23;})
.map((user)->{return user.getName().toUpperCase();})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach((user)->{ System.out.println(user);});
// “函数式接口 变量名 = 类实例::方法名” 的方式对该方法进行引用
// forEach((user)->{System.out.println(user);}); 可以写为 forEach( System.out::println;)
}
}
14.forkjoin
什么是forkjoin
一个线程拆分成多个子任务。
Forkjoin 在JDK1.7 ,并行执行任务!提高效率。在大数据量时使用。
大数据中:MapReduce 核心思想->把大任务拆分为小任务!
forkjoin特点:工作窃取
实现原理:双端队列!从上面和下面都可以拿到任务去执行!
如何使用forkjoin
1.通过forkjoinpool来执行
2.计算任务 execute(ForkJoinTask<?> task)
3.计算类要去继承RecursiveTask;重写 compute() 方法
public class FrokjoinData extends RecursiveTask<Long> {
private Long start;
private Long end;
// 临界值,小于临界值,采用普通计算;大于临界值采用 Frokjoin
private Long temp =10000L;
public FrokjoinData(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end -start) < temp){
Long sum =0L;
for (Long i = start; i < end; i++) {
sum += i;
}
return sum;
}else {
// 使用ForkJoin 分而治之 计算
//计算平均值,从平均值处拆分任务
Long middle = (start+end)/2;
FrokjoinData frokjoinData1 = new FrokjoinData(start,middle);
frokjoinData1.fork();// 拆分任务,把任务押入线程。
FrokjoinData frokjoinData2 = new FrokjoinData(middle,end);
frokjoinData2.fork();// 拆分任务,把任务押入线程。
// 汇总线程结果
Long sumTask =frokjoinData1.join() + frokjoinData2.join();
return sumTask;
}
}
}
测试类:
public class ForkjoinTest {
private static final Long num = 20_0000_0000L;
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
// 使用普通方法
public static void test1(){
Long s = System.currentTimeMillis();
Long sum =0L;
for (int i = 0; i < num; i++) {
sum +=i;
}
System.out.println(sum);
Long e = System.currentTimeMillis();
System.out.println("3000块程序员方法速度:"+(e-s)+"ms");
}
// 使用ForkJoin 方法
public static void test2() throws ExecutionException, InterruptedException {
Long s = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
FrokjoinData data = new FrokjoinData(0L,num);
ForkJoinTask<Long> submit = forkJoinPool.submit(data);
Long sum = submit.get();
System.out.println(sum);
Long e = System.currentTimeMillis();
System.out.println("6000块程序员方法速度:"+(e-s)+"ms");
}
// 使用 Stream 流计算
public static void test3() {
Long s = System.currentTimeMillis();
// 使用一个并行流去计算整个计算,提高效率。
Long sum = LongStream.range(0L,num).parallel().reduce(0,Long::sum);
System.out.println(sum);
Long e = System.currentTimeMillis();
System.out.println("9000块程序员方法速度:"+(e-s)+"ms");
}
}
15.异步调用CompletableFuture
Future 设计的初衷: 对将来的某个事件的结果进行建模
类比: 前端 --> 发送ajax异步请求给后端
平时都使用CompletableFuture
- java.util.concurrent.CompletableFuture
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
}
/**
* 异步调用: CompletableFuture
* // 异步执行
* // 成功回调
* // 失败回调
*/
// 1.没有返回值的 runAsync 异步回调
public static void test1() throws ExecutionException, InterruptedException {
//发起一个请求 void
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" runAsync方法执行->void 返回值");
});
System.out.println("主线程执行...");
completableFuture.get(); // 获取阻塞执行结果
}
/**
* 异步调用: CompletableFuture
* // 异步执行
* // 成功回调
* // 失败回调
*/
// 2.没有返回值的 supplyAsync 异步回调
public static void test2() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+" supplyAsync方法->Integer 返回值");
int i =10/0;
return 1024;
});
Integer res=completableFuture.whenComplete((t, u)->{
System.out.println("正常的返回结果->"+t);
System.out.println("错误的返回结果->"+u);
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 404; // 返回错误结果
}).get();
// 正常的话接收正常值,如果发生了异常,get可以获取到exceptionally返回的值;
System.out.println(res);
}
}
whenComplete: 有两个参数,一个是t 一个是u
T:是代表的 正常返回的结果;
U:是代表的 抛出异常的错误信息;
如果发生了异常,get可以获取到exceptionally返回的值;
16.JMM
请你谈谈你对 Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM
JMM : Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
1、 线程解锁前,必须把共享变量立刻刷回主内存。
2、 线程加锁前,必须读取主存中的 新值到工作内存中!
3、 加锁和解锁是同一把锁
线程的内存分为 工作内存 、主内存
8种操作:
读read ->加载load
-> 使用use->赋值(assign)
-> 写write->存储store
以及 加锁lock、解锁unlock
问题:线程B修改了值,但是线程A不能及时可见(线程B修改了flag=false,线程A还没有写出)
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
- Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
JMM对这八种指令的使用,制定了如下规则:
-
不允许read和load、store和write操作之一单独出现,必须成对使用。即使用了read必须load,使用了store必须write
-
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
-
不允许一个线程将没有assign的数据从工作内存同步回主内存一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
-
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
-
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
-
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题: 程序不知道主内存的值已经被修改过了
public class Test1 {
private 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 = "+num);
}
}
主线程 num=1 ,线程1不知道还在循环,如何保证可见性 volatile
17.volatile
volatile 保证可见性
public class Test1 {
// 如果不加volatile 程序会死循环
// 加了volatile是可以保证可见性的
private volatile static int num = 0;
//private static int num =0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1); // 休眠事件间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println("主线程的num = "+num);
}
}
/*
* 主线程的num = 1
* Process finished with exit code 0
*/
volatile不保证原子性
原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
public class Test2 {
private static volatile int num =0;
private static void add(){
num++; // 不是一个原子性操作,查看 字节码文件就能看出来(cmd javap -c xxx.class)
}
public static void main(String[] args) {
// 正常的结果应该是 30_000000
for (int i = 0; i <30 ; i++) {
new Thread(()->{
for (int j = 0; j <1000000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ // main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"的 num="+num);
}
}
// 结果每次不一样 有 num=11951181
如何保证原子性操作,加锁 : 使用 synchronized 或者 JUC 的 lock 给 add 方法加锁;
如果不用synchronized或者 JUC 的 lock加锁的方式呢?
使用原子类,解决原子性问题,比锁高效很多倍!
public class Test2 {
private static volatile AtomicInteger num = new AtomicInteger(); // 原子类型 数据
private synchronized static void add(){
num.incrementAndGet(); //底层是CAS保证的原子性
}
public static void main(String[] args) {
// 正常的结果应该是 30_000000
for (int i = 0; i <30 ; i++) {
new Thread(()->{
for (int j = 0; j <1000000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ // main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"的 num="+num);
}
}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;(Unsafe方法都是本地方法)
原子类为什么这么高级?
3、禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的。
源代码--》编译器优化重排--》指令并行也可能重排--》内存系统也会重排--》执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成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 |
可能在线程A中会出现,先执行b=1, 然后执行x=a;在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1;
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
(前有开路,后有断后的,谁能加塞)
在哪里用这个内存屏障用得最多呢?单例模式
18.玩转单例模式
1.饿汉式单例:
// 饿汉式单例-- 提前加载好对象可能会浪费内存
public class HungryMan {
private HungryMan(){ // 1.私有化构造器
}
public static final HungryMan HUNGRY_MAN = new HungryMan();//2.直接创建好独有静态对象
public static HungryMan getInstance(){ // 3.抛出对外方法
return HUNGRY_MAN;
}
}
2.懒汉式单例:
// 懒汉式单例-- 用到时再加载对象
public class LazyMan {
private LazyMan() {
}
private static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
懒汉式在多线程下会出问题,考虑 双重检测锁模式 DCL懒汉式:
// 懒汉式单例-- 用到时再加载对象
public class LazyMan {
private LazyMan() {
System.out.println("当前线程是"+Thread.currentThread().getName()+"懒汉开启~");
}
private static volatile LazyMan lazyMan; // volatile , 避免指令重排
// 双重检测锁模式 懒汉式单例 DCL懒汉
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class){ // 加class锁
if (lazyMan == null) {
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}
// 多线程并发下
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i <30 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
lazyMan = new LazyMan();
不是一个原子性操作。
正常执行步骤:1、分配内存空间 2、执行构造方法、初始化对象 3、把这个对象指向这个空间
预期: 1->2->3 实际有可能: A线程 1->2->3 进来B 线程 1->3->2 ,这时LazyMan还没有构造完成。
保证原子性操作上面代码加: volatile ,避免指令重排。
以上方式都不安全,可以通过反射
破坏!
// 懒汉式单例-- 用到时再加载对象
public class LazyMan {
private LazyMan() {
System.out.println("当前线程是"+Thread.currentThread().getName()+"懒汉开启~");
}
private static volatile LazyMan lazyMan;
// 双重检测锁模式 懒汉式单例 DCL懒汉
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class){ // 加class锁
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有权限 private
LazyMan instance2 = constructor.newInstance();
System.out.println(instance); // com.jia.juc.single.LazyMan@8efb846
System.out.println(instance2); // com.jia.juc.single.LazyMan@2a84aee7
// 单例被破坏了
}
}
防止反射破坏,给私有构造器加锁,并抛出异常
// 懒汉式单例-- 用到时再加载对象
public class LazyMan {
private LazyMan() {
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
//System.out.println("当前线程是" + Thread.currentThread().getName() + "懒汉开启~");
}
private static volatile LazyMan lazyMan;
// 双重检测锁模式 懒汉式单例 DCL懒汉
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class){ // 加class锁
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有权限 private
LazyMan instance2 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance2); // 两种方式结果抛出异常
}
}
阻止破坏成功:
但是仍然可以通过如下方式破坏:(都通过反射的方式获取实例)
//多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有权限 private
LazyMan instance2 = constructor.newInstance();
LazyMan instance3 = constructor.newInstance();
System.out.println(instance2); // com.jia.juc.single.LazyMan@8efb846
System.out.println(instance3); // com.jia.juc.single.LazyMan@2a84aee7
// 单例被破坏了
}
再用别的方式防止破坏,设置一个别人不知道的变量:
// 懒汉式单例-- 用到时再加载对象
public class LazyMan {
//定义一个别人不知道的变量
private static boolean flag =false;
private LazyMan() {
synchronized (LazyMan.class) {
if (flag == false) {
flag = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
//System.out.println("当前线程是" + Thread.currentThread().getName() + "懒汉开启~");
}
private static volatile LazyMan lazyMan;
// 双重检测锁模式 懒汉式单例 DCL懒汉
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class){ // 加class锁
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有权限 private
LazyMan instance2 = constructor.newInstance();
LazyMan instance3 = constructor.newInstance();
System.out.println(instance2);
System.out.println(instance3);
}
}
阻止破坏成功
别人不知道的变量,总还是有牛人会知道定义的变量的,还是不安全
//多线程并发
public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// LazyMan instance = LazyMan.getInstance();
Field flag = LazyMan.class.getDeclaredField("flag");// 反射获取变量
flag.setAccessible(true); // 无视私有权限
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true); // 无视私有权限 private
LazyMan instance2 = constructor.newInstance();
flag.set(instance2,false);
LazyMan instance3 = constructor.newInstance();
System.out.println(instance2); // com.jia.juc.single.LazyMan@2a84aee7
System.out.println(instance3); // com.jia.juc.single.LazyMan@a09ee92
// 单例被破坏了
}
}
3.静态内部类单例:
// 静态内部类--单例
public class Holder {
private Holder(){
}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
}
也不安全,因为反射,略
4.枚举类:
使用枚举,我们就可以防止反射破坏单例了。
// 枚举 enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
//报错 java.lang.NoSuchMethodException: com.jia.juc.single.EnumSingle.<init>() 没有无参构造方法
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);// 无参
declaredConstructor.setAccessible(true);
EnumSingle instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
declaredConstructor.newInstance(); 枚举类有禁止反射异常
但是,反编译确实有 无参构造方法
使用jad工具反编译为java(反编译jad.exe 下载: )
反编译源码:
源码骗了我们,用了一个有参构造器
class Test{
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
// 报异常,禁止反射构造实例
// xception in thread "main" java.lang.IllegalArgumentException:
// Cannot reflectively create enum objects
// at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
Constructor<EnumSingle> declaredConstructor =
EnumSingle.class.getDeclaredConstructor(String.class,int.class); // 有参
declaredConstructor.setAccessible(true);
EnumSingle instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
19.深入理解CAS
1.什么是CAS?
进大厂必须要深入研究底层!(连环追问)— 修内功,操作系统,计算机网络原理。
public class CASTest1 {
//CAS : compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
//boolean compareAndSet(int expect, int update)
//期望值、更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
//因为期望值是2021 实际值却变成了2022 所以会修改失败
//CAS 是CPU的并发原语
atomicInteger.getAndIncrement(); // ++操作
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
}
}
2.Unsafe 类
方法分析,csa
总结:CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
- 循环会耗时;
- 一次性只能保证一个共享变量的原子性;
- 它会存在ABA问题
CAS:ABA问题?(狸猫换太子)
线程1:期望值是1,要变成2;
线程2:两个操作:
- 1、期望值是1,变成3
- 2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
public class CASTest1 {
//CAS : compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
//boolean compareAndSet(int expect, int update)
//期望值、更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
// ============== 捣乱的线程 ==================
System.out.println(atomicInteger.compareAndSet(2022, 2021));
System.out.println(atomicInteger.get());
//因为期望值是2021 实际值却变成了2022 所以会修改失败
//CAS 是CPU的并发原语
// ============== 期望的的线程 ==================
System.out.println(atomicInteger.compareAndSet(2021, 8888));
System.out.println(atomicInteger.get());
}
}
20.原子引用
解决ABA 问题:引入原子引用,对应的思想:
乐观锁
!
带版本号 的原子操作!
public class CASTest2 {
/**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
* 正常在业务操作,这里面比较的都是一个个对象
*/
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,20211207);
//两个参数 第一个是数据 第二个是版本号
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("第一次获取版本号"+stamp+"\t值是"+atomicStampedReference.getReference());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改操作时,版本号更新 + 1
atomicStampedReference.compareAndSet(1,2,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("第二次获取版本号"+atomicStampedReference.getStamp()+"\t值是"+atomicStampedReference.getReference());
},"线程A").start();
new Thread(()-> {
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("第三次获取版本号" + stamp+"\t值是"+atomicStampedReference.getReference());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 3,
stamp, stamp + 1));
System.out.println("第四次获取版本号" + stamp+"\t值是"+atomicStampedReference.getReference());
},"线程B").start();
}
}
// 第一次获取版本号20211207 值是1
// 第三次获取版本号20211207 值是1
// 第二次获取版本号20211208 值是2
// false 中间修改了,这里通过版本号已经知道了
// 第四次获取版本号20211207 值是2
注意:
Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;
21.对各种锁的理解
1.公平锁、非公平锁
-
公平锁:非常公平,不能插队,必须先来后到
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); }
2.非公平锁:非常不公平,允许插队,可以改变顺序
/** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
2.可重入锁
拿到外边的锁,就可以拿到里面的锁,自动获得。
Synchonized 可重入锁
public class Test1 { // Synchonized 可重入锁 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() + "==> 发短信"); call(); // 还有一把锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName() + "==> 打电话"); } } //A==> 发短信 //A==> 打电话 //B==> 发短信 //B==> 打电话
Lock 可重入锁
public class Test2 { // Synchonized 可重入锁 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 synchronized void sms(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "==> 发短信"); call(); // 还有一把锁 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public synchronized void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "==> 打电话"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } //A==> 发短信 //A==> 打电话 //B==> 发短信 //B==> 打电话
- lock锁必须配对,相当于lock和 unlock 必须数量相同;
- 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
3.自旋锁
B进程必须等待A进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。
spinlock (Unsafe 类)
// 自旋锁 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; }
自我设计自旋锁
public class SpinlockDemo { //原子操作:泛型为 int 默认 0 //原子操作:泛型为thread 默认 null AtomicReference<Thread> atomicReference = new AtomicReference<>(); //加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"\t myLock "); // 自旋锁 while (!atomicReference.compareAndSet(null,thread)); System.out.println(Thread.currentThread().getName() + "\t ==> 自旋中......"); } // 解锁 public void myUnlock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"\t unLock "); atomicReference.compareAndSet(thread,null); } } class Test{ public static void main(String[] args) throws InterruptedException { // Lock lock = new ReentrantLock(); // lock.lock(); // lock.unlock(); //使用CAS实现自旋锁 SpinlockDemo spinlockDemo = new SpinlockDemo(); new Thread(()->{ try { spinlockDemo.myLock(); TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"A").start(); TimeUnit.SECONDS.sleep(2); new Thread(()->{ try { spinlockDemo.myLock(); TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"B").start(); } } //A myLock //A ==> 自旋中...... //B myLock //A unLock //B ==> 自旋中...... //B unLock
B进程必须等待A进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。
4.死锁
重点在排查死锁,如何解开死锁
public class DeadLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"线程1").start();
new Thread(new MyThread(lockB,lockA),"线程2").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()+lockA+ "===>获取"+lockB);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+lockB+ "===>获取"+lockA);
}
}
}
}
如何解开死锁
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack 进程进程号 找到死锁信息
一般情况信息在最后:
5.悲观锁、乐观锁 略
6.读写锁 略