Java多线程之JUC
JUC
文章目录
- JUC
- 1、什么是JUC?
- 1.1、进程和线程
- 1.2、并发&并行
- 1.3、wait和sleep的区别
- 2、锁(*重点)
- 2.1、synchronized和Lock的区别
- 2.2、生产者&消费者问题
- 2.3、那么锁是什么?如何判断锁的是谁?(8锁现象)
- 3、集合类不安全
- 4、Callable接口
- 5、常用的辅助类(必会)
- 5.1、CountDownLatch(减法计数器)
- 5.2、CyclicBarrier(加法计数器)
- 5.3、semaphore(信号量)
- 6、读写锁(共享锁,排他锁)
- 7、阻塞队列
- 7.1、对阻塞队列的理解
- 7.2、什么情况下我们会使用阻塞队列: 多线程并发处理,线程池!
- 7.3、学会使用队列
- 7.4、SynchronousQueue同步队列
- 8、线程池(*重点)
- 8.1、池化技术
- 8.2、线程池的好处
- 8.1、三大方法
- 8.2、七大参数
- 8.3、手动创建一个线程池
- 8.4、四种拒绝策略(*)
- 8.5、 最大核心线程池大小设置多大合适?(调优)
- 9、四大函数式接口(必需掌握)
- 9.1、什么是函数式接口?(==只有一个方法的接口==)
- 9.2、Function 函数式接口
- 10、Stream流式计算
- 10.1、什么是流式计算?
- 11、ForkJoin(分支合并)
- 11.1、什么是ForkJoin?
- 11.2、ForkJoin的特点:==工作窃取==
- 11.3、怎么使用ForkJoin呢?
- 12、异步回调
- 13、JMM
- 什么是JMM?
- 14、Volatile(轻量级的同步机制)
- 15、彻底玩转单例模式
- 16、深入理解CAS
- 16.1、什么是CAS ?
- 16.2、Unsafe类
- 16.3、ABA问题(狸猫换太子)
- 17、原子引用
- 18、各种锁的理解
- 18.1、乐观锁&悲观锁
- 18.1.1、悲观锁(互斥同步锁)
- 18.1.2、悲观锁主要分为共享锁和排他锁
- 18.1.3、悲观锁有哪些劣势?
- 18.1.3、乐观锁(非互斥同步锁)
- 18.1.4、乐观锁悲观锁对比
- 18.2、公平锁、非公平锁
- 18.3、可重入锁
- 18.4、自旋锁
- 18.5、死锁
1、什么是JUC?
Java.util.concurrent 在并发编程中使用的工具类
1.1、进程和线程
进程:是操作系统中正在运行的应用程序,是程序的集合,是操作系统资源分配的基本单位。一个进程往往可以包含多个线程,至少要1个。
线程:是进程的执行单元或说执行场景,用来执行具体的任务和功能,是CPU调度和分派的基本单位。
进程是操作系统调度和资源分配的最小单位,线程是CPU调度和分派的最小单位。
java默认有几个线程?至少2个:main主线程,GC垃圾回收线程
**java真的可以开启线程吗?**开不了。
它是先通过调用start()方法:public synchronized void start()
该方法里先是把该线程加入到一个线程组中:group.add(this)
然后调用了start0()方法,这是个本地方法,它是调用底层的C++程序
要明白一点,java是无法直接操作硬件的。而是通过这些本地方法去调用底层的c或c++程序来操作硬件。
1.2、并发&并行
并发(多线程操作同一资源)
- CPU一核,模拟出多线程,多个线程间频繁切换,形成并发假象。
并行(同一时间点多个线程同时执行,真正的并发)
- CPU多核,同一时间点多个线程同时执行,各自执行个的。
并发编程的本质:充分利用CPU的资源,提高程序的执行效率
java线程有几个状态?
public enum State {NEW,//新建状态RUNNABLE,//运行状态BLOCKED,//阻塞状态WAITING,//等待(死死地等)TIMED_WAITING,//超时等待(超过一定时间就不等了)TERMINATED;//终止状态}
新建状态(New):新建一个线程对象。
就绪/可运行状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获得CPU并执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep的状态超时、join等待线程终止或者超时、以及I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完成或者因异常退出run方法,该线程结束生命周期。
**wait() 与 notify() **
wait():使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
wait(long,int):对于超时时间更细力度的控制,单位为纳秒。
notify():随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
notifyAll():使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
notify()和notifyAll()的区别:
① notify()随机唤醒等待队列中一个线程,notifyAll()唤醒正在等待队列中全部线程。
② notify()可能引发异常,比如一个生成者多个消费者情况下,消费者消费完随即唤醒一个线程,恰好也是消费线程,就会引发异常。所以建议使用notifyAll()
1.3、wait和sleep的区别
来自不同的类:
wait是Object的方法
sleep是Thread的静态方法
关于锁的释放:
wait会释放锁
sleep不释放锁(抱着锁睡)
使用范围:
wait必须在同步代码块中
sleep可以在任何地方睡
2、锁(*重点)
Java中隐式锁:synchronized;显式锁:Lock
2.1、synchronized和Lock的区别
出身不同:
synchronized是Java中的关键字,是由JVM来维护的。是JVM层面的锁。
Lock是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁。
sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。
而lock是通过调用对应的API方法来获取锁和释放锁的。
概述,可以把sync理解为官二代或者是星二代,从娘胎出来自带光环的。Lock就是我们普通努力上进的人。
使用方式不同
Sync是隐式锁。Lock是显示锁。
所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
在使用Lock的时候,使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合try/finaly语句块来完成。
用生活中的一个case来形容这个不同:官二代和普通人的你在进入机关大院的时候待遇。官二代不需要出示什么证件就可以进入,但是你需要手动出示证件才可以进入。
等待是否可中断
sync是不可中断的。一个线程获得了锁,其他线程,必须傻傻等待该线程释放锁,不能去中断该线程,除非该线程抛出异常或者正常运行完成。其他线程才能获得锁。
Lock可以中断的。中断方式:
1:调用设置超时方法 tryLock(long timeout ,timeUnit unit)
2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
生活中小case来理解这一区别:官二代一般不会做饭。都会去餐厅点餐等待着餐厅出餐。普通人的你既可以去餐厅等待,如果等待时间长的话,你就可以回去自己做饭了。
加锁的时候是否可以公平
java默认的是非公平锁。为什么?因为非公平锁比较公平。通常情况下,非公平锁的吞吐量要比公平锁的吞吐量高。
ReentrantLock实现了Lock接口,加锁和解锁都需要显式写出,注意一定要在适当时候unlock 和synchronized相比,ReentrantLock用起来会复杂一些。在基本的加锁和解锁上,两者是一样的,所以无特殊情况下,推荐使用synchronized。ReentrantLock的优势在于它更灵活、更强大,增加了轮训、超时、中断等高级功能。 ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。公平锁:线程获取锁的顺序和调用lock的顺序一样,FIFO; 非公平锁:线程获取锁的顺序和调用lock的顺序无关,全凭运气。ReentrantLock默认使用非公平锁是基于性能考虑,公平锁为了保证线程规规矩矩地排队,需要增加阻塞和唤醒的时间开销。如果直接插队获取非公平锁,跳过了对队列的处理,速度会更快。
sync:非公平锁。
lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
true:公平锁
false:非公平锁
生活中小case来理解这个区别:官二代一般都不排队,喜欢插队的。普通人的你虽然也喜欢插队。但是如果遇到让排队的情况下,你还是会排队的。
锁绑定多个条件来condition
sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。lock的粒度更细。
从性能比较
从使用锁的方式比较
2.2、生产者&消费者问题
/*
* 生产者消费者模式
* */
public class A {public static void main(String[] args) {Data data = new Data();
// 生产线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increase();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();// 消费线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrease();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}
}//资源类
class Data{private int number = 0;//+1public synchronized void increase() throws InterruptedException {while (number!=0){//这里必须用while循环。来防范虚假唤醒的情况。不能用if!因为一个生产一个消费时if还不会出现问题,但是多个生产,多个消费时,if就会出现问题,因为if只进行一次判断,//等待this.wait();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知来消费this.notify();}//-1public synchronized void decrease() throws InterruptedException {while (number == 0){//等待this.wait();}number--;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知生产this.notify();}
}
/*
* 生产者消费者模式
*想实现的效果就是,A执行完B执行,B执行完C执行,C执行完D执行,D执行完A执行
* */
public class A {public static void main(String[] args) {Data data = new Data();
// 生产线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increaseA();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();// 消费线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decreaseB();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}// 生产线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increaseC();} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();// 消费线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decreaseD();} catch (InterruptedException e) {e.printStackTrace();}}},"D").start();}
}//资源类
class Data{private int number = 0;Lock lock = new ReentrantLock();//如何精准唤醒?就是通过new多个condition监视器,每个condition监视一个线程Condition conditionA = lock.newCondition();Condition conditionB = lock.newCondition();Condition conditionC = lock.newCondition();Condition conditionD = lock.newCondition();public void increaseA() throws InterruptedException {lock.lock();try{while (number!=0){conditionA.await();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知B来消费conditionB.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}public void decreaseB() throws InterruptedException {lock.lock();try{while (number == 0){conditionB.await();}number--;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知C来生产conditionC.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();} }public void increaseC() throws InterruptedException {lock.lock();try{while (number!=0){conditionC.await();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知D来消费conditionD.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}//-1public void decreaseD() throws InterruptedException {lock.lock();try{while (number == 0){conditionD.await();}number--;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知A来生产conditionA.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}
}
2.3、那么锁是什么?如何判断锁的是谁?(8锁现象)
记住一点:在java多线程中,
一个对象一把锁,100个对象100把锁;(synchronized加在方法上锁的是this,即这个对象)
一个类只有一把锁; (synchronized加在静态方法上锁的是类)。
栗子
3、集合类不安全
ArrayList是非线程安全的。在多线程并发的情况会出现问题(比如ConcurrentModificationException并发修改异常),怎么变得安全呢?有一下三种方法:
第三种就是JUC方式,用List list = new CopyOnWriteArrayList();
CopyOnWriteArrayList比Vactor牛在哪?看源码:
- Vactor的add方法中用的是synchronized,而synchronized是重锁,只要有synchronized的方法它的效率就会比较低。
- CopyOnWriteArrayList的add方法中用的是lock锁,并且它是通过写入时赋值的方式添加元素,(即先复制一份老数组,把新元素加进来后再把整个数组set回去的方式添加元素)
写入时复制,这是一种 读写分离 的思想,因为由源码可知它是把原数组复制到一个新数组当中,然后把要添加的元素添加到新数组中,最后再把新数组set回去。当然这个过程进行了加锁以保证写的时候只有一个线程,而写的过程中(即添加元素的过程),其他线程就可以并发地读原来旧的数组,这就实现了读写分离。那么此时并发下就不会出错了。
内部是调用了Arrays.copyOf()方法,而Arrays.copyOf()底层又是调用了System.arraycopy(),这个方法底层调用了一个本地方法,是复制了一个数组。
4、Callable接口
因为Thread只接受Runnable,Callable怎么让Thread接受自己从而启动线程呢?
当然只能通过Runnable,那又怎么和Runnable搭上关系呢?
通过Runnable的实现类FutureTask(相当于适配器类)
public class TestCallable {public static void main(String[] args) throws ExcutionException,InterruptedException{// new Thread(new Runnable()).start();// new Thread(new FutureTask<V>()).start();// new Thread(new FutureTask<V>( Callable )).start();myThread thread = new myThread();FutureTask futureTask = new FutureTask(thread)//适配器类,把Callable接口实现类实例包装一下new Thread(futureTask,"A").start();Integer o = (Integer)futureTask.get();//获取返回值,这个方法可能会阻塞System.out.println(o);}
}
//Callable接口实现类
class myThread implements Callable<Integer>{public Integer call(){//返回类型要与接口泛型一致System.out.println("call...");return 1024;//返回值}
}
- 优点:call方法可以有返回值,call方法可以抛异常,结果有缓存
- 缺点:调用get()方法获取返回值时,可能会产生阻塞,导致程序执行效率降低。
5、常用的辅助类(必会)
5.1、CountDownLatch(减法计数器)
//减法计数器
//CountDownLatch是一种通用的同步工具
public class CountDownLatch_test {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);//设置计数for (int i = 0; i <= 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+" Go out");countDownLatch.countDown();//数量-1},String.valueOf(i)).start();}countDownLatch.await();//等待计数归零,再向下执行(计数没归零之前,任何线程都过不去这道门槛。大家一起过这道门)System.out.println("Close Door");}
}
原理:
countDownlatch.countDown(); //数量 -1
countDownlatch.await(); //等待计数归零,然后再向下执行(相当于一个门闩)
每次有线程调用countDown()时 计数 -1,其他线程阻塞谁也无法通过await这道门闩,当计数器为0 ,所有被阻塞的线程释放,门闩打开程序可以向下执行。
5.2、CyclicBarrier(加法计数器)
//允许一组线程全部等待彼此到达共同障碍点的同步辅助。
public class CyclicBarrier_test {public static void main(String[] args) {//集齐7颗龙珠召唤神龙CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("神龙召唤成功!");//只有计数达到7才会执行该操作});for (int i = 1; i <= 7; i++) {final int temp = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠");try{cyclicBarrier.await();//等待} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}
5.3、semaphore(信号量)
计数信号量。信号量通常用于限制线程数(限流),信号量维持一组许可证,每个线程必须从信号量获取许可证,再去使用项目,当线程完成该项目后,它将返回到池中,并将许可证返回到信号量允许另一个线程获取该项目。
比如有6辆车(线程),目前只有3个车位(信号量)
/*
比如有6辆车(线程),目前只有3个车位(信号量)
只能同时供3辆车停放,其余3辆等待,走一辆进去一辆,交替停车。
* */
public class Semaphore_test {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);//设置信号量个数for (int i = 1; i <=6 ; i++) {new Thread(()->{try {semaphore.acquire();//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();//release()释放}},String.valueOf(i)).start();}}
}
6、读写锁(共享锁,排他锁)
/*
* 独占锁(写锁)一次只能被一个线程占有
* 共享锁(读锁)多个线程可以同时占有
* */
public class ReadWriteLock_test {public static void main(String[] args) {MyCacheLock myCacheLock = new MyCacheLock();for (int i = 1; i <= 5 ; i++){//开启5个线程写入final int temp = i;new Thread(()->{myCacheLock.put(temp+"",temp+"");},String.valueOf(i)).start();}for (int i = 1; i <= 5 ; i++){//开启5个线程读取final int temp = i;new Thread(()->{myCacheLock.get(String.valueOf(temp));},String.valueOf(i)).start();}}
}
//资源类
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){e.printStackTrace();}finally {readWriteLock.writeLock().unlock();//释放写锁}}//取,读所有线程都可以读public void get(String key){readWriteLock.readLock().lock();//加读锁try{System.out.println(Thread.currentThread().getName()+"读取"+key);Object o = map.get(key);//读取System.out.println(Thread.currentThread().getName()+"读取OK");}catch (Exception e){e.printStackTrace();}finally {readWriteLock.readLock().unlock();//释放读锁}}
}
7、阻塞队列
7.1、对阻塞队列的理解
7.2、什么情况下我们会使用阻塞队列: 多线程并发处理,线程池!
7.3、学会使用队列
添加,移除
四组API
方式 | 抛出异常 | 不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put | offer(“a”,2,TimeUnit.SECONDS) |
移除 | remove() | poll | take | poll(2,TimeUnit.SECONDS)) |
检测队首元素 | element() | peek | - | - |
7.4、SynchronousQueue同步队列
没有容量:进去一个元素,必须等待取出来后,才能再往里放一个元素!
put(),take()
/*
* 同步队列和其他BlockingQueue不一样,SynchronousQueue不存过多元素只存一个。
* put进队列了一个元素,必须要take出来,否则不能再put进去。
* */
public class SynchronousQueue_Demo {public static void main(String[] args) {BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列//开两个线程,一个线程存一个线程取。new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" put 1");blockingQueue.put("1");System.out.println(Thread.currentThread().getName()+" put 2");blockingQueue.put("2");System.out.println(Thread.currentThread().getName()+" put 3");blockingQueue.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"T1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(2);//等待3秒System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());TimeUnit.SECONDS.sleep(2);//等待3秒System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());TimeUnit.SECONDS.sleep(2);//等待3秒System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());} catch (InterruptedException e) {e.printStackTrace();}},"T2").start();}
}
8、线程池(*重点)
线程池:三大方法、7大参数、4种拒绝策略
8.1、池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!=>池化技术
线程池,连接池,内存池,常量池,对象池…等等。因为创建、销毁十分浪费资源,所以有了池化技术。
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。
8.2、线程池的好处
- 降低资源的消耗
- 提高响应的速度
- 因为线程放在了池子里,方便管理
线程池就是线程复用、可以控制最大并发数
8.1、三大方法
使用 **Executors 工具类**去创建线程池的三大方法:
public class Demo1 {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 < 10; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"ok");});}} catch (Exception e) {e.printStackTrace();} finally {//线程池用完(程序结束),要关闭线程池threadPool.shutdown();}}
}
8.2、七大参数
三大方法的源码分析:
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,//最大约等于21亿60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}//可见三者底层实际调用的都是ThreadPoolExecute()方法
//ThreadPoolExecute()中有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.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;
}
来看看阿里巴巴关于线程池的规范:
比如银行办理业务。假设1,2窗口是开的,3,4, 5窗口是关的。候客区相当于一个阻塞队列。此时,又来三个人办理业务,而候客区又满了,就会触发3,4,5窗口开放营业接客。
这种情况下:1,2就相当于corePoolSize核心线程池大小;候客区相当于阻塞队列;3,4,5相当于maximumPoolSize最大核心线程池大小;如果这时候再来一个人,就会有拒绝策略来处理他。
8.3、手动创建一个线程池
超过8时就会执行拒绝策略,在这里会抛出异常。
8.4、四种拒绝策略(*)
- AbortPolicy (默认): (银行满了)再有新任务来,不处理,直接抛RejectExecutionException异常阻止系统正常运行。
- CallerRunsPolicy:“调用者运行的一种机制”。该策略不会抛弃任务,也不会抛出异常,而是将新来的任务回退给调用者,让调用者去处理。
- DiscaredPolicy:队列满了,该策略默默地丢弃新来的任务,不予任何处理,也不抛出异常。(如果允许任务丢失,这是最好的一种策略。)
- DiscaredOldestPolicy:队列满了,当新来任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的任务,再尝试把这个新任务添加进去。也不会抛出异常。
自定义拒绝策略
使用RejectedExecutionHandler
接口,重写rejectedExecution方法。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10),Executors.defaultThreadFactory(), new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { //r:请求执行的任务 executor:当前的线程池//打印丢失的任务System.out.println(r.toString() + " is discard");}});
8.5、 最大核心线程池大小设置多大合适?(调优)
两种情况:IO密集型,CPU密集型
CUP密集型
看电脑是几核cpu,java并发是一个线程占用一个CPU,几核CPU就支持几个线程同时执行,这样能充分发挥cpu性能
查看CPU核数也很容易:1、任务管理器->性能:可见我的是12个
2、打开计算机管理->设备管理器:
但是每个用户的电脑情况不一样,我们肯定不能把这个值写死,怎么办呢?通过代码获取:
max = Runtime.getRuntime().availableProcessors();//获取CPU的核数
IO密集型
IO十分耗时又占用资源的!程序中有几个IO任务就相当于有几个大型任务。
所以设置最大核心线程池大小的时候,我们就要大于大型任务的个数,以保证有多余的线程去执行其他的任务。
比如我们程序中有 15 IO任务,那 最大核心线程池大小 > 15
9、四大函数式接口(必需掌握)
新时代java程序员必会:lambda表达式、链式编程、函数式接口、Steam流式计算
9.1、什么是函数式接口?(只有一个方法的接口)
例如比较典型的就是Runnable接口
@FunctionalInterface
public interface Runnable {public abstract void run();
}
//java中函数式接口超级多FunctionalInterface
//它能简化编程模型,在新版本的框架底层大量应用。
//forEach(消费者类型的函数式接口)
9.2、Function 函数式接口
/*
* Function 函数接口,有一个输入参数,返回类型是第二个泛型参数
* */
public class Function_test {public static void main(String[] args) {Function<String,String> function = new Function<String, String>() {//匿名内部类方式实现@Overridepublic String apply(String s) {return s;}};System.out.println(function.apply("abc"));//只要是函数式接口都可以用lambda表达式简化Function<String,String> function2 = (s)->{return s;};System.out.println(function2.apply("abc"));}
}
/*
* Predicate 断定接口,有一个输入参数,返回结果是布尔值
* */
public class Function_test {public static void main(String[] args) {Predicate<String> predicate = new Predicate<String>() {//匿名内部类方式实现@Overridepublic boolean test(String s) {return s.isEmpty();}};System.out.println(predicate.test(""));//True//只要是函数式接口都可以用lambda表达式简化Predicate<String> predicate2 = (s)->{return s.isEmpty();};System.out.println(function2.test(""));}
}
/*
* Consumer 消费型接口,有一个输入参数,无返回类型;
* */Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}};consumer.accept("abcdefg");//只要是函数式接口都可以用lambda表达式简化Consumer<String> consumer2 = (s)->{System.out.println(s);};consumer2.accept("abcdefg");
/*
* Supplier 供给型接口,无输入参数,返回类型指定泛型;
* */Supplier<Integer> supplier = new Supplier<Integer>() {@Overridepublic Integer get() {return 1024;}};System.out.println(supplier.get());//只要是函数式接口都可以用lambda表达式简化Supplier<Integer> supplier2 = ()->{return 1024;};System.out.println(supplier2.get());
可见消费型接口,只消费不返回;供给型接口,只返回不消费。
函数式接口哪里用得到?Stream流式计算!
10、Stream流式计算
10.1、什么是流式计算?
大数据的计算模式主要分为批量计算(batch computing)、流式计算(stream computing)、交互计算(interactive computing)、图计算(graph computing)等。其中,流式计算和批量计算是两种主要的大数据计算模式,分别适用于不同的大数据应用场景。
流数据(或数据流)是指在时间分布和数量上无限的一系列动态数据集合体,数据的价值随着时间的流逝而降低,因此必须实时计算给出秒级响应。流式计算,顾名思义,就是对数据流进行处理,是实时计算。批量计算则统一收集数据,存储到数据库中,然后对数据进行批量处理的数据计算方式。主要体现在以下几个方面:
1、数据时效性不同:流式计算实时、低延迟, 批量计算非实时、高延迟。
2、数据特征不同:流式计算的数据一般是动态的、没有边界的,而批处理的数据一般则是静态数据。
3、应用场景不同:流式计算应用在实时场景,时效性要求比较高的场景,如实时推荐、业务监控…批量计算一般说批处理,应用在实时性要求不高、离线计算的场景下,数据分析、离线报表等。
4、运行方式不同,流式计算的任务持续进行的,批量计算的任务则一次性完成。
大数据:存储+计算
存储:集合、数据库等
计算:交给流来操作!
/*
* 题目要求:一分钟完成此题,只能用一行代码实现!
* 现有5个用户!筛选
* 1、ID必须是偶数!
* 2、年龄必须大于23!
* 3、用户名转为大写字母!
* 4、用户名字母倒着排序!
* 5、只输出一个用户!
* */
public class Demo1 {public static void main(String[] args) {User u1 = new User(1,"zhangsan",21);User u2 = new User(2,"lisi",23);User u3 = new User(3,"wanger",26);User u4 = new User(4,"maliu",25);User u5 = new User(5,"zhaoqi",35);User u6 = new User(6,"zhaoqi",29);//集合就是存储元素的List<User> list = Arrays.asList(u1,u2,u3,u4,u5,u6);//交给Stream流来计算//一个例子涵盖了:lambda表达式、函数式接口、链式编程、Steam流式计算list.stream().filter((u)->{return u.getId()%2==0;})//ID必须是偶数!.filter((u)->{return u.getAge() > 23;})//年龄必须大于23!.map((u)->{return u.getName().toUpperCase();})//用户名转为大写字母!.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//用户名字母倒着排序!.limit(1)//只输出一个用户!.forEach(System.out::println);}
}
//这几个方法的参数分别是哪种类型的函数式接口?
//filter(Predicate断定型的函数式接口),map(Function函数型接口),sorted(Compartor比较型接口),forEach(消费者类型的函数式接口)
11、ForkJoin(分支合并)
11.1、什么是ForkJoin?
并行执行任务!提高效率。必须在大数据量下才会使用!
把大任务拆分为多个子任务。 把子任务的结果合并成最初问题的结果(分治策略)
11.2、ForkJoin的特点:工作窃取
空闲线程主动去执行高负载线程的任务。从而提高效率。
怎么提高效率的?它内部实际上维护的是双端队列。如下图A可以从上面取,B可以从下面取。两端可以同时取。
11.3、怎么使用ForkJoin呢?
ForkJoinPool.execute() 或者 ForkJoinPool.submit()
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
/*
* 计算1~10亿的求和任务
*初级(for循环) ,中级(ForkJoin),高级(Stream并行流)
* 如何使用ForkJoin?
* 1、ForkJoinPool,用它来执行.
* 2、计算任务 ForkJoinPool.execute(ForkJoinTask task)
* 3、计算类要继承ForkJoinTask
* */
public class ForkJoin_Demo {public static void main(String[] args) throws ExecutionException, InterruptedException {test1();test2();test3();}//普通程序员public static void test1(){Long sum = 0L;long begin = System.currentTimeMillis();for (Long i = 1L; i <= 10_0000_0000L; i++) {sum += i;}long end = System.currentTimeMillis();System.out.println("sum= "+sum+" 时间: "+(end-begin));}//中级程序员使用ForkJoinpublic static void test2() throws ExecutionException, InterruptedException {long begin = System.currentTimeMillis();ForkJoinPool forkJoinPool = new ForkJoinPool();ForkJoin_task task = new ForkJoin_task(0L, 10_0000_0000L);//创建一个ForkTask任务ForkJoinTask<Long> submit = forkJoinPool.submit(task);Long sum = submit.get();long end = System.currentTimeMillis();System.out.println("sum= "+sum+" 时间: "+(end-begin));}//高级程序员Stream并行流public static void test3(){long begin = System.currentTimeMillis();//一行代码搞定; rangeClosed是(]左开右闭;parallel并行;reduce累加求和;:: JDK8中的关键字 方法引用Long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);long end = System.currentTimeMillis();System.out.println("sum= "+sum+" 时间: "+(end-begin));}
}//定义一个ForkJoinTask任务
class ForkJoin_task extends RecursiveTask<Long> {private Long start;private Long end;private Long boundary = 10000L;//临界值,超过临界值就分成两个任务public ForkJoin_task(Long start, Long end) {this.start = start;this.end = end;}//计算方法@Overrideprotected Long compute() {if ((end-start) < boundary){//正常使用for循环计算Long sum = 0L;for (Long i = start; i <= end; i++) {sum += i;}return sum;}else{//使用ForkJoin分支合并计算(递归)Long middle = (start+end)/2;//中间值//拆分成两个子任务ForkJoin_task task1 = new ForkJoin_task(start, middle);task1.fork();//把拆分的子任务压入线程的工作任务队列(这个队列是个双端队列)ForkJoin_task task2 = new ForkJoin_task(middle+1, end);task2.fork();//把拆分的子任务压入线程的工作任务队列(这个队列是个双端队列)return task1.join() + task2.join();//合并结果}}
}
12、异步回调
13、JMM
什么是JMM?
8种操作
14、Volatile(轻量级的同步机制)
- 保证可见性
- 禁止指令重排(保证有序性)
- 不保证原子性
深入理解volatile关键字:https://blog.csdn.net/weixin_43587472/article/details/106342353
我们知道synchronized和Lock都能保证原子性,而volatile不能保证原子性。但是有没有什么方法可以既不使用synchronized和Lock
又能保证原子性呢?
使用原子类,解决原子性问题
这些类的底层都是调用的本地方法和操作系统之间挂钩,所以效率很高效!在内存中修改值!
15、彻底玩转单例模式
饿汉式;懒汉式
//饿汉式单例
//浪费空间,无论你用不用类一加载加创建好对象了。
public class Hungry {private Hungry(){}//私有化构造函数private final static Hungry HUNGRY = new Hungry();//私有化实例对象public static Hungry getInstance(){return HUNGRY;}
}
懒汉式:
//懒汉式单例
//需要用到的时候在实例化对象
public class LazyMan {private LazyMan(){}//私有化构造函数private static LazyMan lazyMan;//私有化实例对象public static LazyMan getInstance(){synchronized (LazyMan.class){if (lazyMan == null){lazyMan = new LazyMan();}}return lazyMan;}
DCL懒汉式:双重检测锁模式的懒汉单例
//懒汉式单例
//需要用到的时候在实例化对象
public class LazyMan {private LazyMan(){System.out.println(Thread.currentThread().getName());}//私有化构造函数private volatile static LazyMan lazyMan;//私有化实例对象//双重检测锁模式的懒汉单例 DCL双重锁定检查(Double Check Lock)懒汉式public static LazyMan getInstance(){if (lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null){lazyMan = new LazyMan();//不是原子性操作。/** 这行代码会经过三个步骤:* 1,分配内存空间* 2,执行构造方法初始化对象* 3,把这个对象指向这个空间** 如果指令按照顺序执行倒也无妨,但JVM为了优化指令,提高程序运行效率,允许指令重排序。* 如此,在程序真正运行时以上指令执行顺序可能是这样的:* (a)给instance实例分配内存;* (b)将instance对象指向分配的内存空间;* (c)初始化instance的构造器;* 这时候,当线程一执行(b)完毕,在执行(c)之前,线程二走到第一个if(此时还没开始抢锁),这时候instance判断为非空,* 此时线程二直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化)。* 具体来说就是synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),* 但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)。* 根据以上分析可知,解决这个问题的方法是:禁止指令重排序优化,即使用volatile变量。* */}}}return lazyMan;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{LazyMan.getInstance();}).start();}}
}
静态内部类方式
//静态内部类方式
public class Holder {private Holder(){System.out.println(Thread.currentThread().getName());}public static Holder getInstance(){return InnerClass.HOLDER;}public static class InnerClass{private static final Holder HOLDER = new Holder();//创建实例}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{Holder.getInstance();},String.valueOf(i)).start();}}
}
枚举方式
//枚举本身就是一个单例类
public enum EnumSingle {INSTANCE;public static EnumSingle getInstance(){return INSTANCE;}
}
单例不安全,因为反射可以破坏私有
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//打破私有
LazyMan instance2 = declaredConstructor.newInstance();//反射方式构造实例对象System.out.println(instance1);
System.out.println(instance2);
16、深入理解CAS
16.1、什么是CAS ?
CAS:Compare And Swap,即比较再交换。是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization).
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronized同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS比较与交换的伪代码可以表示为:
do{
备份旧数据;
基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))
在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的 JVM 支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。
由此可见,AtomicInteger.incrementAndGet的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicInteger.incrementAndGet的自增比用synchronized的锁效率倍增。
Java中AtomicInteger中incrementAndGet和getAndIncrement的区别
//从源码分析
public final int getAndIncrement() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return current;}
}public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}
}
通过代码可以看出:
getAndIncrement返回的是当前值;
incrementAndGet返回的是加1后的值。
16.2、Unsafe类
我们知道AtomicInteger有个getAndIncrement方法。该方法底层调用的是Unsafe类的getAndAddInt方法,而getAndAddInt内部是一个自旋锁
CAS:比较当前工作内存中的值和主存中的值,如果这个值是期望值,则执行操作!如果不是会一直循环!
缺点:
1,循环会耗时
2,一次性只能保证一个共享变量的原子性
3,会存在ABA问题
16.3、ABA问题(狸猫换太子)
什么是ABA问题?
假设原来是A,先修改成B,再修改回成A
假如有两个线程A,B。假如A,B都读到了a=1;这时候B先对这个a进行了CAS操作,第一次cas(1,3)第二次cas(3,1)。对于线程A来说,a表面上并没有变,而实际上线程B已经对a做了手脚。
像这样的现象是我们不希望的,我们希望谁动了这个线程一定要告诉我。如何解决这个问题呢?原子引用。
CAS可以引发ABA问题,怎么解决?——>原子引用(AtomicStampedReference)在比较并交换的过程加上时间戳。
17、原子引用
public class ASR {public static void main(String[] args) {AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);new Thread(()->{int stamp = atomicStampedReference.getStamp();//获取版本号(时间戳)System.out.println("a1=>" + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}//执行成功会输出true否则false//执行后时间戳加1System.out.println(atomicStampedReference.compareAndSet(1, 2,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));System.out.println("a2=>" + atomicStampedReference.getStamp());System.out.println(atomicStampedReference.compareAndSet(2, 1,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));System.out.println("a3=>" + atomicStampedReference.getStamp());},"a").start();new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println("b1=>" + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicStampedReference.compareAndSet(1, 6,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));System.out.println("b1=>" + atomicStampedReference.getStamp());},"b").start();}
}
18、各种锁的理解
18.1、乐观锁&悲观锁
18.1.1、悲观锁(互斥同步锁)
顾名思义它是悲观的,它总是假设最坏的情况,当每次要对数据进行修改的时候都认为会发成并发冲突,别人会修改,所以为了避免冲突同时被其他人修改,每次拿数据的时候都会上锁以防止并发。这样别人想拿到这个数据就会阻塞直到释放锁后拿到这个锁。JAVA中synchronized和Lock都是采用的悲观锁。(具有强烈的独占和排他特性)
18.1.2、悲观锁主要分为共享锁和排他锁
共享锁 【Shared lock】又称为读锁,简称S锁。顾名思义,共享锁就是多个线程或事务对于同一数据可以共享一把锁,(相当于对于同一把门,它拥有多个钥匙一样)都能访问到数据,但是只能读不能修改和删除。一个线程或事务对数据A加上锁后,其他线程或事务只能对数据A加共享锁,不能加排它锁。共享锁下,其他线程可以并发读取查询数据,不能修改、增加、删除数据。(资源共享)
排他锁【Exclusive lock】又称为写锁或独占锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果线程T获取了数据A排他锁,线程T就独占数据A,只允许线程T对数据A进行读取和修改,其他线程不能再获取数据A的任何锁,包括共享锁和排他锁,直到线程T释放该锁。(独占资源)
18.1.3、悲观锁有哪些劣势?
- 阻塞、唤醒。因为阻塞唤醒会涉及到排队和时间,从而导致性能下降。(性能劣势)
- 永久阻塞或死锁。比如现在持有锁的线程被永久阻塞了,比如说是无限循环,这会导致这个持有锁的线程永远不会释放锁
- 优先级。阻塞是优先级越高,持有锁的优先级就越低。从而导致优先级翻转。
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
18.1.3、乐观锁(非互斥同步锁)
顾名思义它是乐观的,它采取了更加宽松的加锁机制。它总是假设最好的情况。每次去拿数据的时候都认为别人不会修改,所以不会上锁。但是更新的时候会判断一下在此期间别人有没有去更新这个数据。可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。在Java中java.util.concurrent.atomic包下的原子类就是使用了乐观锁的一种实现方式CAS算法实现的。
18.1.4、乐观锁悲观锁对比
开销对比:
悲观锁的原始开销大于乐观锁。
适用场景:
乐观锁,并发写入少,大量读操作;
悲观锁,并发写入多的情况,临界区代码复杂,竞争激烈等;
18.2、公平锁、非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,先来后到,不能插队!非常公平。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。非常不公平,可以插队!(java默认是非公平锁,因为实际上非公平锁相对公平些)
Lock lock = new ReentrantLock();//默认非公平锁
Lock lock = new ReentrantLock(true);//改成公平锁
18.3、可重入锁
可重入锁(递归锁):**可重入锁指的是可以重复调用、可以递归调用的锁,并且不发生死锁。**在外层使用锁之后,在内层仍然可以使用,获取到外层锁自动获取到内层锁,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。
synchronized版:
Lock版:
补充:什么是AQS?
AQS:AbstractQueuedSynchronizer抽象队列式同步器。是除了java自带的synchronized关键字之外的锁机制。AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包
AQS的核心思想:如果被请求的共享资源空闲,则将当前请求线程设为有效工作线程,并将共享资源设为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。
用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。注意:AQS是自旋锁:在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功。
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物
18.4、自旋锁
什么是自旋锁:
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
自旋锁与互斥锁的异同点:
相同点:其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。
不同点:但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态(即会阻塞)。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。
自定义一个锁
public class ZiXuanSuo {AtomicReference<Thread> atomicReference = new AtomicReference<>();//加锁public void myLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"==>"+"myLock");//自旋锁while (!atomicReference.compareAndSet(null,thread));}//解锁public void myUnLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"==>"+"myUnLock");atomicReference.compareAndSet(thread,null);}
}
测试
public class Test {public static void main(String[] args) {ZiXuanSuo lock = new ZiXuanSuo();new Thread(()->{lock.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();} finally {lock.myUnLock();}},"T1").start();try {TimeUnit.SECONDS.sleep(1);//保证T1先执行完} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{lock.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();} finally {lock.myUnLock();}},"T2").start();}
}
18.5、死锁
死锁是什么?
各自占有自己的锁,又试图去获取对方的锁,这种现象就会造成死锁。
public class TestDeadLock {public static void main(String[] args) {String lockA = "lockA";String lockB = "lockB";new Thread(new myThread(lockA,lockB),"T1").start();new Thread(new myThread(lockB,lockA),"T2").start();}
}
class myThread implements Runnable{private String lock1;private String lock2;public myThread(String lockA, String lockB) {this.lock1 = lock1;this.lock2 = lock2;}@Overridepublic void run() {//死锁就是嵌套锁synchronized(lock1){System.out.println(Thread.currentThread().getName()+"lock:"+lock1+"=>get"+lock2);try{//让线程休眠一下保证线程都各自先拿到一把锁TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}synchronized (lock2){System.out.println(Thread.currentThread().getName()+"lock:"+lock2+"=>get"+lock1);}}}
}
解决死锁问题
在diea终端
- 使用
jps -l
定位进程号
使用
jstack 进程号
查看堆栈信息
面试或者工作中排查问题
- 查看日志
- 查看堆栈信息
Java多线程之JUC相关推荐
- Java多线程之JUC包:Semaphore源码学习笔记
若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...
- JAVA多线程之wait/notify
本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait ...
- Java多线程之Callable、Future和FutureTask
Java多线程之Callable接口 自己想总结一下的,看到一篇总结的更好的博客,就转载了,突然感觉真轻松,哈哈哈哈 文章转载于:Matrix海子:Java并发编程:Callable.Future和F ...
- Java多线程之Synchronized和Lock的区别
Java多线程之Synchronized和Lock的区别 目录: 原始构成 使用方法 等待是否可以中断 加锁是否公平 锁绑定多个条件Condition 小结:Lock相比较Synchronized的优 ...
- Java多线程之CAS缺点
Java多线程之CAS缺点 目录: 循环时间开销很大 只能保证一个共享变量的原子操作 引来ABA问题及解决方案(重点) 1. 循环时间开销很大 通过看源码,我们发现有个do while,如果CAS失败 ...
- Java多线程之CAS深入解析
Java多线程之CAS深入解析 目录: CAS是什么 CAS底层原理Unsafe深入解析 CAS缺点 引子:蚂蚁花呗一面:讲一讲AtomicInteger,为什么要用CAS而不是synchronize ...
- Java多线程之volatile详解
Java多线程之volatile详解 目录: 什么是volatile? JMM内存模型之可见性 volatile三大特性之一:保证可见性 volatile三大特性之二:不保证原子性 volatile三 ...
- Java多线程之Semaphore用法
Java多线程之Semaphore用法 本文目录: Semaphore基本概念 Semaphore使用案例:3个停车位,6辆车去抢,走一辆,抢一个停车位. 1. Semaphore基本概念 在信号量上 ...
- Java多线程之CyclicBarrier用法
Java多线程之CyclicBarrier用法 本文目录 CyclicBarrier的基本概念 CyclicBarrier的案例:集齐7颗龙珠就可以召唤神龙 1. CyclicBarrier的基本概念 ...
最新文章
- java文件用editplus乱码,EditPlus设置编码后,编译时仍然出现乱码
- 面试题整理5 顺时针打印矩阵
- activemq启动wrapper stopped
- 使用mybatis-generator自动生成代码的方法介绍及踩坑
- linux cron源码下载,LINUX计划任务管理_AT与crontab
- TCP/IP 三次握手
- 阿里的easyexcel
- java 权重 分配_一种按权重分配的Java算法
- ExtremeComponents源码解析(一)
- ofd文件怎么打开?怎么转换成pdf格式发票?ofd文件打开教程
- catia相合约束怎么反向_洛丽塔服饰怎么画?教你如何画动漫人物的衣服?
- 亲密关系“恋爱心理学”
- 逆水寒服务器什么时候能维护好,逆水寒更新了什么内容 8月23日官方维护内容及时间一览...
- 论文阅读笔记--Federated Continual Learning with Weighted Inter-client Transfer
- three.js判断两个向量(角度)夹角误差是否小于某个值
- 76篇 ICCV 2019 论文实现代码
- 什么是REST ful?
- python列表替换元素_24_Pandas.DataFrame,Series元素值的替换(replace)
- 常见的服务器操作系统和工作站操作系统
- [笔记]ESP32 踩坑 任务看门狗超时的问题---Task watchdog got triggered