1、Volatile
1.1什么是volatile
volatile是java虚拟机提供的轻量级的同步机制
1.2volatile的特性
1.2.1volatile保证可见性
1.什么是JMM(java内存模型)
JMM本身是一种抽象的概念并不真实存在
,它描述的是一组规则或者是规范,通过这组规范定义了程序中的各个变量的(包括实例字段、静态字段和构成数组对象的元素)访问方式。
JMM关于同步的规定:
- 线程解锁前,必须把共享变量的值刷新回
主内存
- 线程加锁前,必须读取主内存的最新值到自己的
工作内存
- 加锁解锁是同一把锁
内存的读取速度:硬盘<内存<cpu(计算)
三个线程同时去操作age这个属性,首先都会对age=25进行变量拷贝,在各自线程的工作内存进行操作,如果t1线程对age做了修改,而t2、t3并不知道age做了修改,为了让t2、t3与主内存的数据保持一致,这就有了volatile。
2、可见性代码验证
不可见:
public class ThreadDemo001 {
public static void main(String[] args) {
MyData myData = new MyData();
//第一个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t come in");
//暂停线程3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3s钟以后将number改为60
myData.addTo60();
System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);
},"AAA").start();
//第二个线程,main线程
while (myData.number==0){
}
System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number over: "+myData.number);
}
}
class MyData{
int number=0;
public void addTo60(){
this.number=60;
}
}
这个时候呢,主线程一直感知不到线程1对number的修改,所以一直再等待number的改变而不会停止。
可见性:
public class ThreadDemo001 {
public static void main(String[] args) {
MyData myData = new MyData();
//第一个线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t come in");
//暂停线程3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3s钟以后将number改为60
myData.addTo60();
System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);
},"AAA").start();
//第二个线程,main线程
while (myData.number==0){
}
System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number over: "+myData.number);
}
}
class MyData{
volatile int number=0;
public void addTo60(){
this.number=60;
}
}
当对变量number加了volatile后,代码可见性体现,主线程执行。
1.2.2volatile不保证原子性
原子性:不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以加塞或者被分割,需要整体完整要么同时成功,要么同时失败。
1、代码验证
public class ThreadDemo001
{
public static void main(String[] args)
{
MyData myData = new MyData();
//新建20个线程
for (int i=1;i<=20;i++){
new Thread(()->{
//每个线程执行1000次
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
},String.valueOf(i)).start();
}
//等待上面20个线程都执行完成之后,再用main线程取得最终结果 2:main+gc
while (Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t final number"+myData.number);
}
}
class MyData {
volatile int number=0;
public void addTo60(){
this.number=60;
}
public void addPlusPlus(){
number++;
}
}
每一次运行结果都不一样,结果都是小于20000
2、为什么
如图所示t1、t2、t3三个线程同时对number进行+1,突然t2在本地加完(number=2)还没放回主内存挂掉了,紧接着t3执行了+1,number由1->2,
3、解决
- synchronized
- Atomic
package com.ljh;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @auth 刘佳浩
* @create 2020-12-16-22:59
* volatile可见性代码验证、不保证原子性
*/
public class ThreadDemo001
{
public static void main(String[] args)
{
MyData myData = new MyData();
//新建20个线程
for (int i=1;i<=20;i++){
new Thread(()->{
//每个线程执行1000次
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
myData.addAtomic();
}
},String.valueOf(i)).start();
}
//等待上面20个线程都执行完成之后,再用main线程取得最终结果 2:main+gc
while (Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t int type,final number"+myData.number);
System.out.println(Thread.currentThread().getName()+"\t atomic type,final number"+myData.atomicInteger);
}
}
class MyData {
volatile int number=0;
public void addTo60(){
this.number=60;
}
public void addPlusPlus(){
number++;
}
AtomicInteger atomicInteger=new AtomicInteger();
public void addAtomic(){
atomicInteger.getAndIncrement();
}
}
结果如下:
1.2.3volatile禁止指令重排(有序性)
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排
,一般分为以下3种:
- 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致
- 处理器在进行重排序时必须要考虑指令之间的
数据依赖性
- 多线程环境种线程交替执行,由于编译器优化重排的存在,多个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
案例一
public void mysort(){
int x=3; //1
int y=4; //2
x=x+5; //3
y=x*x; //4
}
执行顺序的可能:1234 、 2134 、 1324
那么问题来了4重排之后可以第一条执行吗?
不可以,因为上面讲到,处理器在进行重排序时必须要考虑指令之间的数据依赖性
案例二
int a,b,x,y=0;
线程1 | 线程2 |
---|---|
x=a | y=b |
b=1 | a=2 |
x=0,y=0 |
如果编译器对这段程序代码执行重排优化后,可能出现下列情况:
线程1 | 线程2 |
---|---|
b=1 | a=2 |
x=a | y=b |
x=2,y=1 |
案例三
public class ReSortSeqDemo {
int a=0;
Boolean flag=false;
public void method1(){
a=1; //1
flag=true; //2
}
public void method2(){
if (flag){
a=a+5;
System.out.println("*****Value:"+a);
}
}
}
1.3单例模式
你在哪些地方用到过volatile?
单例模式
最初学过的单线程的单例模式,在多线程下,有很大的问题:
public class SingletonDemo {
public static SingletonDemo instance=null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
}
public static SingletonDemo getInstance(){
if (instance==null){
instance=new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
for (int i=1;i<=20;i++){
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
1 我是构造方法SingletonDemo()
2 我是构造方法SingletonDemo()
2 我是构造方法SingletonDemo()
3 我是构造方法SingletonDemo()
1 我是构造方法SingletonDemo()
DCL(Double Check Lock双端检锁机制):
public class SingletonDemo {
public static volatile SingletonDemo instance=null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
}
public static SingletonDemo getInstance(){
if (instance==null){
synchronized (SingletonDemo.class){
if (instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i=1;i<=20;i++){
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
2、CAS
2.1什么是CAS?
compare and swap比较并交换
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t value:"+atomicInteger);
System.out.println(atomicInteger.compareAndSet(5, 2021)+"\t value:"+atomicInteger);
}
}
true value:2020
false value:2020
2.2底层原理
我们之前用到过
atomicInteger.getAndIncrement();
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
这里的this
指的是当前对象,valueOffset
指的是该变量值在内存中的偏移地址,即内存地址,1为固定+1;
这里的unsafe
类是什么?
upsafe是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在与sum.misc包中
,其内部方法操作可以像C的指针一样直接操作内存,所以说CAS依赖与Unsafe类。注意Unsaf类中的所有方法都是native修饰的,也就是说,Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务
2.3总结
CAS的全称为Compare-And-Swap,它是一条CPU并发原语
。他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,否则,不改,这个过程是原子的。
CAS并发原语体现在JAVA语言中就是sum.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令
。这是一种完全依赖与硬件
的功能,通过他实现了原子操作。再次强调,由于CAS是一种系统原语,原语是属于操作系统范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {//var1:this var2:valueOffset var4:1
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//将var5与当前内存的值进行比较,相同更新var5+var4并且返回true,不同,继续取值并且比较,直到更新完成。
return var5;
}
2.4简单总结
什么是CAS?
CAS(CompareAndSwap)比较当前工作内存中的值和主内存的值,如果相同执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。
CAS应用
CAS有3个操作数,内存值V,旧的预期值A,要修改的值B,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
2.5CAS缺点
- 循环时间长,开销很大。通过上面源码可知,getAndAddInt()方法执行时,有个do while。如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能给CPU带来很大的开销。
- 只能保证一个共享变量的原子操作。通过源码可知,cas只对当前变量(this)起作用。
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
- ABA问题。即狸猫换太子,A->B->A。线程t1 、t2同时去修改某个值A,t1慢,t2快,t2将A->B->A,t1再去compareAndSwap时发现内存指V和预期值A相同,就会执行相关操作。尽管t1线程操作成功,但不代表这个过程是没有问题的。
public class ABADemo {
public static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").join();//注意这里是用到了join()方法,会等待当前线程执行完之后,才会执行其它线程。
new Thread(()->{
System.out.println(atomicReference.compareAndSet(100, 2021)+"\t"+atomicReference.get());
},"t2").start();
}
}
运行结果:
true 2021
2.6ABA问题的解决
public class ResolveABADemo {
public static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号" + atomicStampedReference.getStamp());
}, "t1").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp+ 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + b + "\t第四次版本号" + atomicStampedReference.getStamp());
}, "t2").start();
}
}
t2 第一次版本号1
t1 第二次版本号2
t1 第三次版本号3
t2 修改成功否:false 第四次版本号3
3、集合类不安全
3.1ArrayList
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
运行之后报了异常java.util.ConcurrentModificationException
3.1.1解决方案:
- 改用vector,
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
通过下面源码可知,vector对整个方法加了锁
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
- 给ArrayList加锁
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i=1;i<=30;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
- CopyOnWriteArrayList写时复制
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
3.2HashSet
public class HashSetDemo {
public static void main(String[] args) {
Set<String> list = new HashSet<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
运行之后报了异常java.util.ConcurrentModificationException
3.2.1解决方案
-
Collections.synchronizedSet(new HashSet<>())
-
new CopyOnWriteArraySet<>()
3.2.2底层原理
HashSet底层是HashMap,当我们添加元素时
HashSet<String> strings = new HashSet<String>;
strings.add("a");
那么这个value去哪了?看下源码
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
点开PRESENT发现:
private static final Object PRESENT = new Object();
3.3HashMap
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
for (int i=1;i<=30;i++){
new Thread(()->{
hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
System.out.println(hashMap);
},String.valueOf(i)).start();
}
}
}
运行之后报了异常java.util.ConcurrentModificationException
3.3.1解决方案
- ConcurrentHashMap
- Collections.synchronizedMap(new HashMap<>())
4、锁
4.1公平锁和非公平锁
公平锁:排队,先来后到。类似与排队打饭。
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
4.2可重入锁
可重入锁又名递归锁
,指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。
ReentrantLock/Synchronized就是典型的可重入锁。可重入锁最大的作用是避免死锁。
4.2.1Synchronized
public class SynchronizedDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"t1").start();
new Thread(()->{
phone.sendSms();
},"t2").start();
}
}
public class Phone {
public synchronized void sendSms(){//外部锁
System.out.println(Thread.currentThread().getId()+"\t invoked sendSms()");
sendEmail();//内部锁
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getId()+"\t ##invoked sendEmail()");
}
}
运行结果:
11 invoked sendSms()
11 ##invoked sendEmail()
12 invoked sendSms()
12 ##invoked sendEmail()
4.2.2ReentrantLock
public class Phone implements Runnable {
Lock lock=new ReentrantLock();
@Override
public void run() {
get();
}
public void get(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t invoked get()");
set();//内部锁
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void set(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t invoked set()");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone=new Phone();
Thread t1 = new Thread(phone,"t1");
Thread t2 = new Thread(phone,"t2");
t1.start();
t2.start();
}
}
t1 invoked get()
t1 invoked set()
t2 invoked get()
t2 invoked set()
面试真题
:
我在get()方法里面又加了一把锁lock.lock();lock.unlock();现在总共有两把锁,结果怎样?
public class Phone implements Runnable {
Lock lock=new ReentrantLock();
@Override
public void run() {
get();
}
public void get(){
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t invoked get()");
set();//内部锁
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
lock.unlock();
}
}
public void set(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t invoked set()");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
t1 invoked get()
t1 invoked set()
t2 invoked get()
t2 invoked set()
4.3自旋锁
自旋锁(spinlock)是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
,这样的好处是减少线程的上下文切换,缺点是循环会消耗cpu。下面的源码是cas学习时的代码。
public final int getAndAddInt(Object var1, long var2, int var4) {//var1:this var2:valueOffset var4:1
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//将var5与当前内存的值进行比较,相同更新var5+var4并且返回true,不同,继续取值并且比较,直到更新完成。
return var5;
}
代码验证:
public class SpinLockDemo {
//原子引用线程
AtomicReference<Thread> atomicReference=new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in O(∩_∩)O");
while (!atomicReference.compareAndSet(null,thread)){
};
}
public void unLock(){
Thread thread=Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+"\t invoked unLock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unLock();
},"AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unLock();
},"BB").start();
}
}
AA come in O(∩_∩)O
BB come in O(∩_∩)O
AA invoked unLock()
BB invoked unLock()
AA先进来,AA需要等待5s才能释放,1s之后,BB进来等待AA锁的释放,5s之后呢,AA释放锁,BB在1s之后释放锁。
4.4独占锁(写锁)/共享锁(读锁)/互斥锁
独占锁:指该锁一次只能被一个线程持有。对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可被多个线程所持有。对ReentrantReadWriteLock而言,它的读写,写读,写写的过程是互斥的。
4.4.1代码验证读写锁
写:
写的过程是独占+原子
Befare
public class MyCache {
private volatile Map<String,Object> map=new HashMap<>();
private ReentrantReadWriteLock rwLook=new ReentrantReadWriteLock();
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"\t 正在写入"+key);
try {
TimeUnit.MICROSECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"\t 写入完成");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"\t 正在读取");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
}
}
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i=1;i<=5;i++){
int finalI = i;
new Thread(()->{
myCache.put(finalI+"", finalI+"");
},String.valueOf(i)).start();
}
for (int i=1;i<=5;i++){
int finalI = i;
new Thread(()->{
myCache.get(finalI+"");
},String.valueOf(i)).start();
}
}
}
运行结果:
1 正在写入1
2 正在写入2
3 正在写入3
4 正在写入4
2 写入完成
1 写入完成
3 写入完成
5 正在写入5
1 正在读取
4 写入完成
5 写入完成
2 正在读取
3 正在读取
5 正在读取
4 正在读取
1 读取完成:1
2 读取完成:2
3 读取完成:3
5 读取完成:5
4 读取完成:4
After:加了读写锁
public class MyCache {
private volatile Map<String,Object> map=new HashMap<>();
private ReentrantReadWriteLock rwLook=new ReentrantReadWriteLock();
public void put(String key,Object value){
rwLook.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t 正在写入"+key);
try {
TimeUnit.MICROSECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"\t 写入完成");
}catch (Exception e){
e.printStackTrace();
}finally {
rwLook.writeLock().unlock();
}
}
public void get(String key){
rwLook.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t 正在读取");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
}catch (Exception e){
e.printStackTrace();
}finally {
rwLook.writeLock().unlock();
}
}
}
运行结果:
1 正在写入1
1 写入完成
2 正在写入2
2 写入完成
3 正在写入3
3 写入完成
4 正在写入4
4 写入完成
5 正在写入5
5 写入完成
1 正在读取
1 读取完成:1
2 正在读取
2 读取完成:2
3 正在读取
3 读取完成:3
4 正在读取
4 读取完成:4
5 正在读取
5 读取完成:5
4.5CountDownLatch(闭锁)
4.5.1初识CountDownLatch
让一些线程阻塞,直到另一些线程完成一系列操作之后才被唤醒
before
public class CountDownLatchDemo {
public static void main(String[] args) {
for (int i=1;i<=10;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 上完自习离开教室~~~");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName()+"\t 学生都走完了,班长来锁门~~~~");
}
}
1 上完自习离开教室~~~
2 上完自习离开教室~~~
main 学生都走完了,班长来锁门~~~~
3 上完自习离开教室~~~
4 上完自习离开教室~~~
5 上完自习离开教室~~~
6 上完自习离开教室~~~
9 上完自习离开教室~~~
7 上完自习离开教室~~~
10 上完自习离开教室~~~
8 上完自习离开教室~~~
after
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i=1;i<=10;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 上完自习离开教室~~~");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();//等待减到0才能往下走
System.out.println(Thread.currentThread().getName()+"\t 学生都走完了,班长来锁门~~~~");
}
}
1 上完自习离开教室~~~
2 上完自习离开教室~~~
3 上完自习离开教室~~~
4 上完自习离开教室~~~
5 上完自习离开教室~~~
6 上完自习离开教室~~~
9 上完自习离开教室~~~
10 上完自习离开教室~~~
7 上完自习离开教室~~~
8 上完自习离开教室~~~
main 学生都走完了,班长来锁门~~~~
4.5.2CountDownLatch与Enum结合的妙用
public enum CountEnum {
ONE(1,"齐"),TWO(2,"楚"),THREE(3,"韩"),FOUR(4,"魏"),FIVE(5,"赵"),SIX(6,"燕");
private Integer retCode;
private String retMessage;
//遍历,才能确定你要用哪一个值
public static CountEnum forEach_CountEnum(int index){
CountEnum[] myArray = CountEnum.values();
for (CountEnum element : myArray) {
if (index==element.retCode){
return element;
}
}
return null;
}
CountEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
}
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i=1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 国,被灭掉~~~");
countDownLatch.countDown();
},CountEnum.forEach_CountEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t #####秦国,一统华夏!!!");
}
}
齐 国,被灭掉~~~
楚 国,被灭掉~~~
韩 国,被灭掉~~~
赵 国,被灭掉~~~
燕 国,被灭掉~~~
魏 国,被灭掉~~~
main #####秦国,一统华夏!!!
4.6CyclicBarrier
CyclicBarrier的字面意思是可循环使用的屏障。它要做的事情就是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
说人话,集齐七颗龙珠才能召唤神龙。
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("***集齐七颗龙珠,召唤神龙~~~");
});
for (int i=1;i<=7;i++){
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"集齐第"+ finalI +"颗龙珠~~~");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
1集齐第1颗龙珠~~~
2集齐第2颗龙珠~~~
3集齐第3颗龙珠~~~
4集齐第4颗龙珠~~~
5集齐第5颗龙珠~~~
6集齐第6颗龙珠~~~
7集齐第7颗龙珠~~~
***集齐七颗龙珠,召唤神龙~~~
CyclicBarrier
与``CountDownLatch`的用法有些类似,最大的区别就是,CyclicBarrier是做加法的,CountDownLatch是来做减法的。
4.7Semaphore
Semaphore(信号量)主要有两个目的,一个是用于多个共享资源的互斥使用
,另一个用于并发线程数的控制
。
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//3个车位
for (int i=1;i<=6;i++){//6个线程来抢占这个车位
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t 抢占车位");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t 停车3s,开走");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
1 抢占车位
2 抢占车位
3 抢占车位
2 停车3s,开走
1 停车3s,开走
4 抢占车位
3 停车3s,开走
5 抢占车位
6 抢占车位
4 停车3s,开走
6 停车3s,开走
5 停车3s,开走
5、阻塞队列
5.1BlookingQueue的概念
- 当阻塞队列是空时,从队列中
获取
元素的操作将会被阻塞,直到有线程往队列中添加元素 - 当阻塞队列时满时,从队列中
添加
元素的操作将会被阻塞,直到有线程从队列中获取元素
5.2BlockingQueue的优点
在多线程领域,所谓阻塞,就是在某些情况下会``挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被
唤醒`。
有了BlockingQueue之后,我们就不需要关心什么时候阻塞线程(挂起),什么时候需要唤醒,因为这一切BlockingQueue都给你一手包办了。在concurrent包发布以前,在多线程的环境下,我们每个程序员都必须要自己去控制这些细节,尤其是还要兼顾效率和线程安全,而这样会给我们的程序带来不小的复杂度。
5.3BlockingQueue的实现类
ArrayBlockingQueue
:由数组结构组成的有界阻塞队列。LinkedBlockingQueue
:由链表结构组成的有界阻塞队列(但是默认值为Integer.MAX_VALUE ,21亿)- PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的延迟无边界阻塞队列
SynchronousQueue
:不存储元素
的阻塞队列,也即单个元素的队列,生产一个消费一个,你不消费我不生产- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
5.4BlockingQueue的核心方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
检查 | element | peek() | 不可用 | 不可用 |
-
抛出异常:当阻塞队列满时,再往队列里面add()时,会抛出
java.lang.IllegalStateException: Queue full
当阻塞队列空时,再去队列里面remove()时,会抛出
java.util.NoSuchElementException
-
特殊值:插入方法,成功true,失败false
移除方法,成功返回出队列的元素,队列里面没有就返回null
-
阻塞:当阻塞队列满时,生产者线程继续往队列里面put元素,队列会一直阻塞生产线程直到put数据成功或响应中断退出
当阻塞队列空时,消费者线程视图从队列里面take元素,队列会一直阻塞消费者线程直到队列可用
-
超时:当阻塞队列满时,队列会阻塞生产者线程指定的时间,超时后,生产者线程自动退出。
5.5阻塞队列的用处
- 生产者消费者模式
- 线程池
- 消息中间件
5.6生产者消费者模式
1、线程 操作 资源类
2、判断 干活 通知
3、防止虚假唤醒
题目:一个初始值为0的变量,两个线程对其交替操作,一个加一一个减一,来5轮
- 传统方式
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for (int i = 0; i <= 5; i++) {
shareData.increase();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i <= 5; i++) {
shareData.desc();
}
},"BB").start();
}
}
class ShareData{
private int number=0;
private Lock myLock=new ReentrantLock();
final Condition condition = myLock.newCondition();
public void increase(){
myLock.lock();
try {
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
public void desc(){
myLock.lock();
try {
while (number==0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
}
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
AA 1
BB 0
Process finished with exit code 0
- 阻塞队列模式
public class shareData{
private volatile boolean FLAG=true;
AtomicInteger atomicInteger= new AtomicInteger();
BlockingQueue<String> blockingQueue=null;
public shareData(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void product() throws InterruptedException {
String data=null;
boolean retValue=false;
while (FLAG){
data=atomicInteger.incrementAndGet()+"";
retValue = blockingQueue.offer(data, 2, 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 consume() throws InterruptedException {
String result=null;
while (FLAG){
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (result==null || result.equalsIgnoreCase("")){
FLAG=false;
System.out.println(Thread.currentThread().getName()+"\t 超过2s没有取到蛋糕,消费结束");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName()+"\t 消费队列"+result+"成功");
}
}
public void stop(){
this.FLAG=false;
}
}
public class ProdConsumer_BlockingQueueDemo {
public static void main(String[] args) {
shareData shareData = new shareData(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println("生产者线程启动。。。。");
try {
shareData.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"prod").start();
new Thread(()->{
System.out.println("消费者线程启动。。。。");
try {
shareData.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"consume").start();
//停止线程5s
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println();
System.out.println();
System.out.println("5s时间到,大老板叫停,活动结束");
shareData.stop();
}
}
生产者线程启动。。。。
消费者线程启动。。。。
prod 插入队列1成功
consume 消费队列1成功
prod 插入队列2成功
consume 消费队列2成功
prod 插入队列3成功
consume 消费队列3成功
prod 插入队列4成功
consume 消费队列4成功
prod 插入队列5成功
consume 消费队列5成功
5s时间到,大老板叫停,活动结束
prod 大老板叫停了,表示flag=false
consume 超过2s没有取到蛋糕,消费结束
Process finished with exit code 0
5.7详解Condition接口
condition接口可以实现精确唤醒 。
题目:多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次
紧接着
AA打印5次,BB打印10次,CC打印15次
。。。
。。。
总共10轮
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.print5();
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.print10();
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareData.print15();
}
},"CC").start();
}
}
class ShareData{
private int number=0;//A:0 B:1 C:2
private Lock myLock=new ReentrantLock();
final Condition c1 = myLock.newCondition();
final Condition c2 = myLock.newCondition();
final Condition c3 = myLock.newCondition();
public void print5(){
myLock.lock();
try {
while (number!=0){c1.await();}
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=1;
c2.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
public void print10(){
myLock.lock();
try {
while (number!=1){c2.await();}
for (int i =1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=2;
c3.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
public void print15(){
myLock.lock();
try {
while (number!=2){c3.await();}
for (int i =1; i <=15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=0;
c1.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
myLock.unlock();
}
}
}
AA 1
AA 2
AA 3
AA 4
AA 5
BB 1
BB 2
BB 3
BB 4
BB 5
BB 6
BB 7
BB 8
BB 9
BB 10
CC 1
CC 2
CC 3
CC 4
CC 5
CC 6
CC 7
CC 8
CC 9
CC 10
CC 11
CC 12
CC 13
CC 14
CC 15
AA 1
AA 2
AA 3
AA 4
AA 5
BB 1
BB 2
BB 3
BB 4
BB 5
BB 6
BB 7
BB 8
BB 9
BB 10
CC 1
CC 2
CC 3
CC 4
CC 5
CC 6
CC 7
CC 8
CC 9
CC 10
CC 11
CC 12
CC 13
CC 14
CC 15
···
···
···
6、Callable
6.1java实现多线程的方式:
- 继承Thread类
- 实现Runnable接口
实现Callable接口
- 线程池
6.2Callable和Runnable的区别
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return null;
}
}
class RunnableDemo implements Runnable{
@Override
public void run() {
}
}
- Callable可以抛出异常
- Callable有返回值
- 实现的方法不一样,run() call()
6.3Callable的使用
public class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"*******Callable come in");
return 10;
}
}
class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> integerFutureTask = new FutureTask<>(new MyThread());
FutureTask<Integer> integerFutureTask2 = new FutureTask<>(new MyThread());
Thread t1 = new Thread(integerFutureTask,"A");
Thread t2 = new Thread(integerFutureTask2,"B");
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName()+"\t ****");
int result2=10;
Integer result1 = integerFutureTask.get();
System.out.println("result:"+(result1+result2));//获取返回值
}
}
main ****
A*******Callable come in
B*******Callable come in
result:20
7、线程池
7.1为什么要用线程池
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列
,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的数量排队等候,
等其它线程执行完毕,再从队列中取出任务来执行。主要特点:线程复用:控制最大的并发数;管理线程。
7.2线程池的使用
-
Executors.newFixedThreadPool(int)
-
固定线程数量的线程池,可以控制最大并发数,超出的线程会在队列中等待
底层源码:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
代码示例:
public class ThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程 //模拟10个用户来办理"业务,每个用户就是一个请求线程 try { for (int i = 0; i < 10; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName() +"\t 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }
pool-1-thread-1 办理业务 pool-1-thread-4 办理业务 pool-1-thread-3 办理业务 pool-1-thread-4 办理业务 pool-1-thread-4 办理业务 pool-1-thread-2 办理业务 pool-1-thread-2 办理业务 pool-1-thread-4 办理业务 pool-1-thread-3 办理业务 pool-1-thread-5 办理业务
-
-
Executors.newSingleThreadExecutor()
-
一池一线程,创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照舒徐执行。
底层源码:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
public class ThreadPoolDemo { public static void main(String[] args) { //ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程 ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程 //模拟10个用户来办理"业务,每个用户就是一个请求线程 try { for (int i = 0; i < 10; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName() +"\t 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }
pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务
-
-
Executors.newCachedThreadPool()
-
一池N线程,创建一个
可缓存的线程池
,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程。使用了SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就会销毁线程。底层:public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
代码示例:
public class ThreadPoolDemo { public static void main(String[] args) { //ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程 //ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程 ExecutorService threadPool = Executors.newCachedThreadPool();// //模拟10个用户来办理"业务,每个用户就是一个请求线程 try { for (int i = 0; i < 10; i++) { threadPool.execute(()->{ System.out.println(Thread.currentThread().getName() +"\t 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }
pool-1-thread-1 办理业务 pool-1-thread-2 办理业务 pool-1-thread-3 办理业务 pool-1-thread-4 办理业务 pool-1-thread-6 办理业务 pool-1-thread-7 办理业务 pool-1-thread-8 办理业务 pool-1-thread-9 办理业务 pool-1-thread-10 办理业务 pool-1-thread-5 办理业务
-
7.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;
}
-
int corePoolSize
核心线程数(一直存在,除非设置了(allowCoreThreadTimeOut));线程池创建好以后就准备就绪的线程数量,就等待来接收异步任务去执行。好比与Thread thread=new Thread(); 而没有调用start()。 -
int maximumPoolSize
最大可执行线程数量,控制资源 -
long keepAliveTime
存活时间 如果当前正在运行的线程数量大于corePoolSize数量,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁,只剩下corePoolSize -
TimeUnit unit
keepAliveTime时间单位 -
BlockingQueue<Runnable> workQueue
阻塞队列。被提交,但是尚未执行的任务。 -
ThreadFactory threadFactory
线程的创建工厂 -
RejectedExecutionHandler handler
如果队列满了,按照指定的拒绝策略,拒绝执行任务案例:5个人到银行办理业务,银行有两个窗口,这里的5个人,是没有超过最大可执行线程数量的(5),所以说只会使用指定了的核心线程数(2),当我们把5个人改为10个人之后,就会直接使用最大可执行线程(5)的数量来执行,超过的(5)其中会有指定了的阻塞队列的数量(3),在阻塞区等候,剩下的2个会根据拒绝策略来执行相关的操作,我们这里使用的是超过的直接丢弃策略。
public class ThreadPoolDemo1 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
100L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i <= 5; i++) {//顾客数
int finalI = i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"号窗口,"+"服务顾客"+ finalI);
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
7.4线程池的底层原理
原理图:
这个原理图可以结合上面去银行办理业务那个例子来理解。
线程池的主要处理流程
:
-
在创建了线程之后,等待提交过来的任务请求。
-
当调用execute()方法添加一个任务请求时,线程池会做如下判断
2.1如果正在 运行的线程数量小于corePoolSize,马上创建线程执行这个任务。
2.2如果正在运行的线程数量大于或者等于corePoolSize,那么将这个任务
放入队列
。 2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻来运行这个任务。
2.4如果队列满了且正在运行的线程数量大于或者等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
-
当一个线程完成任务时,它会从队列中取下一个任务来执行。
-
当一个线程无事可做超过指定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。
如果线程池的所有任务完成后它最终会收缩到corePoolSize的大小
7.5线程池的拒绝策略
- AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
- CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
- DiscardOldestPolicy:抛弃队列中
等待最久
的任务,然后把当前任务加入队列中再次尝试提交当前任务 - DiscardPolicy:直接丢弃
超出的
任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
以上拒绝策略均实现了RejectedExceutionHandler接口。
7.6线程池那么多怎么选
一个都不选,阿里巴巴开发手册。
7.7合理配置线程数
System.out.println(Runtime.getRuntime().availableProcessors());
CPU密集型:任务配置尽可能少的线程数量,一般公式:CPU核数+1个线程的线程池。
IO密集型:由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2
8、死锁编码及定位分析
8.1什么是死锁
死锁是指两个或以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,那它们就无法推进下去
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new HoldLockThread(lockA,lockB),"ThreadAAA").start();
new Thread(new HoldLockThread(lockB,lockA),"ThreadBBB").start();
}
}
public class HoldLockThread implements Runnable{
private String lockA;
private String lockB;
public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t 尝试获得:"+lockB);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 尝试获得:"+lockA);
}
}
}
}
8.2死锁定位分析
-jps -l 定位
jstack +线程id 查看故障报告
9、JVM
9.1什么是GC Roots
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。譬如各个线程被调用的方法堆栈中使用到的参数、局部变量表、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的应用类型静态变量
- 方法区中常量引用的对象,譬如字符串常量池(string pool)里的引用
- 本地方法栈中Native引用的对象
9.2JVM的参数类型
XX参数
9.2.1Boolean类型:
公式
:-XX:+或者 -某个属性值
+表示开启
-表示关闭
public class GcDemo {
public static void main(String[] args) {
System.out.println("###hello!!!");
try {
TimeUnit.SECONDS.sleep( Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
是否打印GC细节
:
9.2.2KV设值类型
公式
:-XX属性key=属性值value
其它更多的类型可以使用 jinfo -flags +线程id查看
-XX:CICompilerCount=3
-XX:InitialHeapSize=268435456
-XX:MaxHeapSize=4263510016
-XX:MaxNewSize=1420820480
-XX:MetaspaceSize=1073741824
-XX:MinHeapDeltaBytes=524288
-XX:NewSize=89128960
-XX:OldSize=179306496
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:+UseFastUnorderedTimeStamps
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
Command line: -XX:MetaspaceSize=1024m //人工设置
9.2.3题外话
-Xms是属于哪一种?-XX:InitialHeapSize
-Xmx是属于哪一种?-XX:MaxHeapSize
9.3JVM默认值
java -XX:+PrintFlagsInitial 用来查看初始默认值
java -XX:+PrintFlagsFinal -version 用来查看修改更新
带冒号的是JVM或者认为修改过的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8AdKWqb-1608452293195)(%E5%A4%A7%E5%8E%82%E9%9D%A2%E8%AF%95%E9%A2%98%E7%AC%AC%E4%BA%8C%E5%AD%A3.assets/image-20201219140403775.png)]
java -XX:+PrintCommandLineFlags -version
9.4JVM常用参数设置
- -Xms 初始内存大小,默认为物理内存的1/64,等价于==-XX:InitialHeapSize==
- -Xmx 最大分配内存,默认为物理内存的1/4,等价于==-XX:MaxHeapSize==
- -Xss 设置单个栈的大小,一般默认为512k~1024k ,等价于==-XX:ThreadStackSize==
- -Xmn 设置年轻代的大小
- -XX:MetaspaceSize 设置元空间大小,元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间和永久代最大的区别在于:
元空间并不在虚拟内存中,而是使用直接内存(Direct Memory)
,因此默认情况下,元空间的大小仅受本地内存的限制。元空间的出厂默认值:-XX:MetaspaceSize=21m