首页 >> 大全

JUC相关知识详解

2023-11-24 大全 33 作者:考证青年

JUC 1、JMM (java 内存模型) 1.1、什么是JMM?

​ JMM 本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式

1.2、JMM关于同步的规定: 线程解锁前,必须把共享变量的值刷新回主内存线程加锁前,必须读取主内存的最新值到自己的工作内存加锁解锁是同一把锁

1.3、JMM原理的理解:

​ 由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

1.4、JMM产生的并发问题: 2、关键字 2.1、什么是?

关键字是Java提供的一种轻量级同步机制。它能够保证可见性和有序性,但是不能保证原子性。

保证可见性

class MyData{// int number=0;volatile int number=0;AtomicInteger atomicInteger=new AtomicInteger();public void setTo60(){this.number=60;}//此时number前面已经加了volatile,但是不保证原子性public void addPlusPlus(){number++;}public void addAtomic(){atomicInteger.getAndIncrement();}
}//volatile可以保证可见性,及时通知其它线程主物理内存的值已被修改
private static void volatileVisibilityDemo() {System.out.println("可见性测试");MyData myData=new MyData();//资源类//启动一个线程操作共享数据new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t come in");try {TimeUnit.SECONDS.sleep(3);myData.setTo60();System.out.println(Thread.currentThread().getName()+"\t update number value: "+myData.number);}catch (InterruptedException e){e.printStackTrace();}},"AAA").start();while (myData.number==0){//main线程持有共享数据的拷贝,一直为0}System.out.println(Thread.currentThread().getName()+"\t mission is over. main get number value: "+myData.number);
}

不能保证原子性代码:

比如一条++的操作,会形成3条指令

getfield        //读
iconst_1	//++常量1
iadd		//加操作
putfield	//写操作

假设有3个线程,分别执行++,都先从主内存中拿到最开始的值,=0,然后三个线程分别进行操作。假设线程0执行完毕,=1,也立刻通知到了其它线程,但是此时线程1、2已经拿到了=0,所以结果就是写覆盖,线程1、2将变成1。

解决的方式就是:

对()方法加锁。使用java.util..类。 2.2、

是什么,为什么能解决原子性问题? 原子整型类,依赖于CAS ,具体见下面部分。

2.3、指令重排序?

指令重排序出现的原因?

计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做一些优化,对没有产生数据依赖性的指令进行重新排序。

指令重排序会造成的后果?

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

案例1:

int a,b,x,y = 0两个线程同时进行操作线程1				线程2
x = a;			y = b;
b = 1;			a = 2;线程1进行的操作: 将a赋值于x,再讲1赋值于b,
线程2进行的操作: 将b赋值于y,再讲2赋值于a,因为线程1和线程2中的操作,都没有数据依赖的关系,有发生指令重排序的结果可能是,
结果可能是:x = 0; y = 0;b=1;a=2如果发生了指令重排序线程1				线程2				
b = 1;			a = 2;
x = a;			y = b;线程1进行的操作: 将1值于b,讲a值于x
线程2进行的操作: 将2于a,讲b值于y结果可能是:x=2,y=1,a=2,b=1

案例2:


public class ReSortSeqDemo{int a = 0;boolean flag = false;public void method01(){a = 1;//语句1flag = true;//语句2}public void method02(){if(flag){a = a + 5; //语句3}System.out.println("retValue: " + a);//可能是6或1或5或0}}

这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。

是如何实现禁止指令重排序?

主要是内存屏障,是一组CPU指令,用于实现对内存操作的顺序限制,

内存屏障有两个作用:

如果你的字段是,Java内存模型将在写操作前插入一个屏障指令,写操作后插入一个屏障指令,在读操作前插入一个屏障指令,在读操作前后插入屏障.

写操作:

读操作:

2.4、使用 做过哪些?

单例模式 DCL:双端检索机制

public class SingletonDemo{private SingletonDemo(){}private volatile static SingletonDemo instance = null;public static SingletonDemo getInstance() {if(instance == null) {synchronized(SingletonDemo.class){if(instance == null){instance = new SingletonDemo();       }}}return instance;}
}

使用 关键字的原因?

= null;

首先创建一个对象分为以下3步完成

在多线程的环境下,步骤2和步骤3不存在数据依赖关系,编辑器或者处理器会产生指令重排序的可能,也就是说当步骤2和步骤3发生重排序的时候

会导致这个对象刚分配完地址,对象还未初始化完成就返回,导致返回的数据为null,产生线程安全的问题。

3、CAS 3.1、什么是CAS?

CAS是指 And Swap,比较并交换,是一种很重要的同步思想,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值

3.2、CAS的底层原理?

类+CAS思想[自旋锁]

3.3、类

类是CAS的核心类 由于Java 方法无法直接访问底层 ,需要通过本地()方法来访问,相当于一个后门,基于该类可以直接操作特定的内存数据.类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作依赖于类的方法.另外类中所有的方法都是修饰的,也就是说类中的方法都是直接调用操作底层资源执行响应的任务

    /*** Atomically increments by one the current value.** @return the previous value*/public final int getAndIncrement() {// this指的是当前对象,valueOffset指的是当前对象的内存偏移量,可以根据内存偏移地址获取数据return unsafe.getAndAddInt(this, valueOffset, 1);}// volatile保证了数据的可见性private volatile int value;

的()是一个本地方法。

.()源码解释:

假设线程A和线程B两个线程同时执行操作(分别跑在不同CPU上) :

里面的value原始值为3,即主内存中的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。线程A通过(var1, var2)拿到value值3,这时线程A被挂起。线程B也通过(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行方法比较内存值也为3,成功修改内存值为4,线程B结束。这时线程A恢复,执行方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值己经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。线程A重新获取value值,因为变量value被修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行进行比较替换,直到成功。 3.5、面试题 讲一讲,为什么要用CAS,而不是?

主要是基于CAS思想,CAS核心类是类,类中的方法都是修饰的,通过该类可以直接操作特定内存的数据,,它是一条CPU并发原语。在类中,调用该类的方法,JVM会帮我们实现出该方法汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作。使用的是锁机制,并发性比较差,而使用并发原语保证了原子性,这个是依赖于硬件的,是原子性的。并发性比较高

3.6、CAS的缺点

// ursafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4){int var5;do {var5 = this.getIntVolatile(var1, var2);// 如果一直比较并交换失败 就会继续尝试,一直长时间不成功,会给cpu造成很大的开销,而且 只能操作一个对象,锁可以操作多个对象}while(!this.compareAndSwapInt(varl, var2, var5,var5 + var4));return var5;
}

1. 循环时间长开销很大
2. 只能保证一个共享变量的原子性
3. ABA问题的产生

3.7、ABA问题

ABA问题是什么?

​ 所谓ABA问题,就是比较并交换的循环,存在一个时间差,由于这个时间差导致数据变化产生的问题。

比如t1,t2两个线程都需要对某一个共享变量value做操作,初始值为A,但是由于t1的执行的时间长,t2将value的值更改为B后,再次更改为A,这是t1才到达,看到值为A,于是就操作更新成功了,但是不知道这个A中间发生了变化,这就是ABA问题。

什么是原子引用?

的作用是封装自定义的对象,让对这个对象做的操作都是原子性的。

解决ABA问题的方法?

​ 使用 ce 带有版本号机制的原子引用,他在进行CAS操作的时候,不仅要比较值,还要比较版本号,如果版本号不一致,就更新失败,反之亦然。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;public class ABADemo {/*** 普通的原子引用包装类*/static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);// 传递两个值,一个是初始值,一个是初始版本号static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {System.out.println("============以下是ABA问题的产生==========");new Thread(() -> {// 把100 改成 101 然后在改成100,也就是ABAatomicReference.compareAndSet(100, 101);atomicReference.compareAndSet(101, 100);}, "t1").start();new Thread(() -> {try {// 睡眠一秒,保证t1线程,完成了ABA操作TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 把100 改成 101 然后在改成100,也就是ABASystem.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());}, "t2").start();/try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}System.out.println("============以下是ABA问题的解决==========");new Thread(() -> {// 获取版本号int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);// 暂停t3一秒钟try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 传入4个值,期望值,更新值,期望版本号,更新版本号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());}, "t3").start();new Thread(() -> {// 获取版本号int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);// 暂停t4 3秒钟,保证t3线程也进行一次ABA问题try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:"+ atomicStampedReference.getStamp());System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值" + atomicStampedReference.getReference());}, "t4").start();}
}

执行结果

============以下是ABA问题的产生==========
true	2019
============以下是ABA问题的解决==========
t3	 第一次版本号1
t4	 第一次版本号1
t3	 第二次版本号2
t3	 第三次版本号3
t4	 修改成功否:false	 当前最新实际版本号:3
t4	 当前实际最新值100

4、集合类的线程不安全 4.1、

相关知识是什么_知识相关理念_

不是线程安全类,在多线程同时写的情况下,会抛出java.util.`异常。

private static void listNotSafe() {List<String> list=new ArrayList<>();for (int i = 1; i <= 30; i++) {new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(Thread.currentThread().getName() + "\t" + list);}, String.valueOf(i)).start();}
}

解决方法:

使用(所有方法加,太重)。使用.()转换成线程安全类。使用java..(推荐)。

: 这是JUC的类,通过写时复制来实现读写分离。

容器即写时复制的容器。待一个容器添加元素的时候,不直接往当前容器[]添加,而是先将当前容器[]进行copy,复制出一个新的容器[] ,然后新的容器[ ] 里添加元素,添加完元素之后,再将原容器的引用指向新的容器 ()。

这样做的好处是可以对容器进行并发的读,而不需要加锁(区别于和.()),因为当前容器不会添加任何元素。所以容器也是一种读写分离的思想,读和写不同的容器。

4.2、

的底层数据结构是什么?.put()需要传两个参数,而.add()`只传一个参数,这是为什么?

的底层结构是,实际上.add()就是调用的.put(),add的元素就是key,Value是一个对象。

跟类似,和都不是线程安全的,与之对应的有这个线程安全类。

的底层结构实际上就是一个数组。

4.3、

也是线程不全的,是线程安全的。

4、java锁 4.1、公平锁/非公平锁

公平锁:就是多个线程按照申请锁的顺序来获取锁,类似排队,先到先得。

非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁。

公平锁/非公平锁的区别:

​ 公平锁就是很公平,在并发坏境中.每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁.否则就会加入到等待队列中.以后会按照FIFO的规则从队列中取到自己。

​ 非公平锁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式在队列中进行排队。

​ 非公平锁的优点是吞吐量比公平锁更大。

和默认都是非公平锁。在构造的时候传入true`则是公平锁

4.2、可重入锁/递归锁

可重入锁又叫递归锁,也就是同一个线程在外层方法获得锁时,进入内层方法会自动获取锁。

可重入锁可以避免死锁的问题。

/就是一个典型的可重入锁。

4.3、自旋锁

自旋锁,就是尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取。自己在那儿一直循环获取,就像“自旋”一样。

好处是减少线程切换的上下文开销,

缺点是会消耗CPU。

CAS底层的就是自旋锁思想。

自旋锁代码验证

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;public class SpinLockDemo {// 现在的泛型装的是Thread,原子引用线程AtomicReference<Thread>  atomicReference = new AtomicReference<>();public void myLock() {// 获取当前进来的线程Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName() + "\t come in ");// 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋while(!atomicReference.compareAndSet(null, thread)) {//摸鱼}}public void myUnLock() {// 获取当前进来的线程Thread thread = Thread.currentThread();// 自己用完了后,把atomicReference变成nullatomicReference.compareAndSet(thread, null);System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");}public static void main(String[] args) {SpinLockDemo spinLockDemo = new SpinLockDemo();// 启动t1线程,开始操作new Thread(() -> {// 开始占有锁spinLockDemo.myLock();try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}// 开始释放锁spinLockDemo.myUnLock();}, "t1").start();// 让main线程暂停1秒,使得t1线程,先执行try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 1秒后,启动t2线程,开始占用这个锁new Thread(() -> {// 开始占有锁spinLockDemo.myLock();// 开始释放锁spinLockDemo.myUnLock();}, "t2").start();}
}

4.4、读写锁/独占/共享锁

独占锁:指该锁一次只能被一个线程所持有。对和而言都是独占锁。

共享锁:指该锁可被多个线程所持有。

读写锁:读是共享锁,写是独占锁 ,读的共享锁可保证并发读是非常高效的,写的独占锁保证数据的原子性。

ck

代码演示

package com.lun.concurrency;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;class MyCache2 {private volatile Map<String, Object> map = new HashMap<>();private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();public void put(String key, Object value) {// 创建一个写锁rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);try {// 模拟网络拥堵,延迟0.3秒TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}map.put(key, value);System.out.println(Thread.currentThread().getName() + "\t 写入完成");} catch (Exception e) {e.printStackTrace();} finally {// 写锁 释放rwLock.writeLock().unlock();}}public void get(String key) {// 读锁rwLock.readLock().lock();try {System.out.println(Thread.currentThread().getName() + "\t 正在读取:");try {// 模拟网络拥堵,延迟0.3秒TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}Object value = map.get(key);System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);} catch (Exception e) {e.printStackTrace();} finally {// 读锁释放rwLock.readLock().unlock();}}public void clean() {map.clear();}}public class ReadWriteWithLockDemo {public static void main(String[] args) {MyCache2 myCache = new MyCache2();// 线程操作资源类,5个线程写for (int i = 1; i <= 5; i++) {// lambda表达式内部必须是finalfinal int tempInt = i;new Thread(() -> {myCache.put(tempInt + "", tempInt +  "");}, String.valueOf(i)).start();}// 线程操作资源类, 5个线程读for (int i = 1; i <= 5; i++) {// lambda表达式内部必须是finalfinal int tempInt = i;new Thread(() -> {myCache.get(tempInt + "");}, String.valueOf(i)).start();}}
}

4.5、多线程面试题

题目: 多线程之间按顺序调用 实现A->B->C三个线程启动,要求如下: AA 打印5次 BB打印10次 CC 打印15次 循环10次

package com.example.test.test;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/* ** @program: testQueue* @description* @author: swq* @create: 2021-04-06 10:07**/
public class ThreadTest {// 题目 多线程之间按顺序调用 实现A->B->C三个线程启动,要求如下// AA 打印5次  BB打印10次  CC 打印15次  循环10次// 资源类static class ShareData {// 标识位private int number = 1;// 锁private Lock lock = new ReentrantLock();//条件private Condition c1 = lock.newCondition();private Condition c2 = lock.newCondition();private Condition c3 = lock.newCondition();/*** 根据标识位输出次数** @param count* @param inputNumber* @param doCondition* @param needCondition*/private void doContent(int count, int inputNumber, Condition doCondition, Condition needCondition) {try {// 加锁lock.lock();// 阻塞while (this.number != inputNumber) {doCondition.await();}// 干活for (int i = 1; i <= count; i++) {System.out.println(Thread.currentThread().getName() + "\t" + i);}if (number == 3) {number = 1;} else {number = number + 1;}// 唤醒needCondition.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}public static void main(String[] args) {ShareData shareData = new ShareData();new Thread(() -> {for (int i = 0; i < 10; i++) {shareData.doContent(5, 1, shareData.c1, shareData.c2);}}, "AAA").start();new Thread(() -> {for (int i = 0; i < 10; i++) {shareData.doContent(10, 2, shareData.c2, shareData.c3);}}, "BBB").start();new Thread(() -> {for (int i = 0; i < 10; i++) {shareData.doContent(15, 3, shareData.c3, shareData.c1);}}, "CCC").start();}}

4.6、和Lock的区别:

关键字和java.util..locks.Lock都能加锁,两者有什么区别呢?

原始构成:sync是JVM层面的,底层通过和来实现的。Lock是JDK API层面的。(sync一个enter会有两个exit,一个是正常退出,一个是异常退出)使用方法:sync不需要手动释放锁,而Lock需要手动释放。是否可中断:sync不可中断,除非抛出异常或者正常运行完成。Lock是可中断的,通过调用()方法。是否为公平锁:sync只能是非公平锁,而Lock既能是公平锁,又能是非公平锁。绑定多个条件:sync不能,只能随机唤醒。而Lock可以通过来绑定多个条件,精确唤醒。

和Lock的区别?

属于JVM层面,属于java的关键字,(底层是通过对象来完成,其实wait/等方法也依赖于对象 只能在同步块或者方法中才能调用 wait/ 等方法)

Lock是具体类(java.util..locks.Lock)是api层面的锁

使用lock的好处?

5、// 5.1、

一般被称为门栓计数器,`内部维护了一个计数器,只有当计数器==0时,某些线程才会停止阻塞,开始执行。

主要有两个方法,()来让计数器-1,await()来让线程阻塞。当count==0`时,阻塞线程自动唤醒。

当一个或多个线程调用await()时,调用线程会被阻塞。其它线程调用()会将计数器减1(调用方法的线程不会阻塞),当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。

5.2、

的字面意思就是可循环()使用的屏障(),它要求做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过的await方法

与的区别:可重复多次,而只能是一次。

5.3、

信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制

正常的锁(.locks或锁)在任何时刻都只允许一个任务访问一项资源,而 允许n个任务同时访问这个资源

6、阻塞队列 6.1、什么是阻塞队列?

首先它是一个队列,当阻塞队列为空时,获取(take)操作是阻塞的;当阻塞队列为满时,添加(put)操作是阻塞的。

什么是阻塞?:所谓阻塞,在某些情况下余挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒。

阻塞队列的好处:阻塞队列不用手动控制什么时候该被阻塞,什么时候该被唤醒,简化了操作。

阻塞队列的种类:

需要注意的是虽然是有界的,但有个巨坑,其默认大小是.,高达21亿,一般情况下内存早爆了(在线程池的有体现)

的核心方法

方法类型抛出异常特殊性阻塞超时

插入

_相关知识是什么_知识相关理念

add(e)

offer(e)

put(e)

offer(e,time,unit)

移除

()

poll()

take()

poll(time,unit)

检查

()

peek()

不可用

不可用

6.2、阻塞队列生产者消费者版本

package com.example.test.test;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;class ShareData {private volatile Boolean FLAG = true;private AtomicInteger atomicInteger = new AtomicInteger();private BlockingQueue<String> blockingQueue = null;public ShareData(BlockingQueue<String> blockingQueue) {this.blockingQueue = blockingQueue;System.out.println(blockingQueue.getClass().getName() + "\t");}public void myProducer() {String data = "";boolean result = false;while (FLAG) {try {data = atomicInteger.incrementAndGet() + "";result = blockingQueue.offer(data, 2, TimeUnit.SECONDS);if (result) {System.out.println(Thread.currentThread().getName() + "生产者 生产" + atomicInteger + "成功");TimeUnit.SECONDS.sleep(1);} else {System.out.println(Thread.currentThread().getName() + "生产者 生产" + atomicInteger + "失败");}} catch (InterruptedException e) {e.printStackTrace();}}}public void myConsumer() {String result = "";while (FLAG) {try {result = blockingQueue.poll(2, TimeUnit.SECONDS);if (result == null || "".equals(result)) {FLAG = false;System.out.println(Thread.currentThread().getName() + "消费者 消费 超过2秒 结束运行");return;} else {System.out.println(Thread.currentThread().getName() + "消费者 消费" + result + "成功");}} catch (InterruptedException e) {e.printStackTrace();}}}public void stop() {FLAG = false;System.out.println(Thread.currentThread().getName() + "叫停运行");}}public class ThreadTest02 {public static void main(String[] args) {ShareData shareData = new ShareData(new ArrayBlockingQueue<>(5));new Thread(() -> {shareData.myProducer();}, "AAA").start();new Thread(() -> {shareData.myConsumer();}, "BBB").start();try {TimeUnit.SECONDS.sleep(5);shareData.stop();} catch (InterruptedException e) {e.printStackTrace();}}
}

7、线程池 7.1、什么是线程池?

线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务

7.2、为什么要使用线程池? 降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性。使用线程池可以进行统一的分配,调优和监控。 7.3、线程池的创建方式

使用创建线程池

​ :使用实现,定长线程池。

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}

​ tor:使用实现,一池只有一个线程。

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}

​ :使用实现,变长线程池。

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

自定义线程池

package com.song.gulimall.product.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.*;/* ** @program: gulimall* @description* @author: swq* @create: 2021-03-24 23:15**/
@Configuration
public class MyThreadPoolConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {return new ThreadPoolExecutor(pool.getCoreSize(),pool.getMaxSize(),pool.getKeepAliveTime(),TimeUnit.SECONDS,new LinkedBlockingDeque<>(100000),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}
}package com.song.gulimall.product.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/* ** @program: gulimall* @description* @author: swq* @create: 2021-03-24 23:20**/
@ConfigurationProperties(prefix = "spring.thread")
@Component
@Data
public class ThreadPoolConfigProperties {private Integer coreSize;private Integer maxSize;private Integer keepAliveTime;}

总结:

项目必须使用自定义的线程池,和:允许的请求队列长度为.,可能会堆积大量的请求,从而导致OOM。和:允许的创建线程数量为.,可能会创建大量的线程,从而导致OOM。 7.4、

创建线程池的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;}

7.5、线程池的拒绝策略

什么是线程池的拒绝策略?

等待队列也已经排满了,再也塞不下新任务了,同时线程池中的max线程也达到了,无法继续为新任务服务,阻止新任务进入的一种方式。

7.6、线程池的底层原理

架构:

流程图:

)]

解释:

在创建了线程池后,等待提交过来的任务请求。当调用()方法添加一个请求任务时,线程池会做如下判断:

如果正在运行的线程数量小于,那么马上创建线程运行这个任务;

如果正在运行的线程数量大于或等于,那么将这个任务放入队列;

如果这时候队列满了且正在运行的线程数量还小于,那么还是要创建非核心线程立刻运行这个任务;

如果队列满了且正在运行的线程数量大于或等于,那么线程池会启动饱和拒绝策略来执行。

当一个线程完成任务时,它会从队列中取下一个任务来执行。

当一个线程无事可做超过一定的时间()时,线程池会判断:

如果当前运行的线程数大于,那么这个线程就被停掉,所以线程池的所有任务完成后它最终会收缩到的大小

总结:

​ 当向线程池提交一个任务后,如果当前的线程数小于核心线程数(),那么就会立即创建线程执行任务,如果当前线程数大于核心线程数,而且阻塞队列容量未满的情况下,会放入到阻塞队列中,如果阻塞队列也满了,但是当前的线程运行数小于最大线程数,那么就会进行扩容,创建非核心线程来立即执行这个任务,如果阻塞队列满了,当前的运行线程数也达到了最大值,那么线程池就会启动拒绝策略。如果有线程的闲置时间超过线程的最大存活时间,那么就会关闭这个线程。

7.7、线程池的参数(最大线程数)

如何查看机器的逻辑处理器个数

System.out.println(Runtime.getRuntime().availableProcessors());

CPU 密集型

IO 密集型

8、死锁 8.1、什么是死锁?

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象

8.2、产生死锁主要原因 8.3、发生死锁的四个条件 互斥条件,线程使用的资源至少有一个不能共享的。至少有一个线程必须持有一个资源且正在等待获取一个当前被别的线程持有的资源。资源不能被抢占。循环等待。 8.4、如何解决死锁问题

破坏发生死锁的四个条件其中之一即可

8.5、手写死锁

package com.example.test.test;import java.util.concurrent.TimeUnit;class LockData implements Runnable {private String lockA;private String lockB;public LockData(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA) {System.out.println(Thread.currentThread().getName() + "持有 lockA 想要获取 lockB");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB) {System.out.println(Thread.currentThread().getName() + "持有 lockB 想要获取 lockA");}}}
}public class DeadLockDemo {public static void main(String[] args) {String lockA = "lockA";String lockB = "lockB";new Thread(new LockData(lockA, lockB), "AAA").start();new Thread(new LockData(lockB, lockA), "BBB").start();}
}

8.6、怎么诊断死锁 在控制台 jps -l 命令定位进程号 进程号 找到死锁查看

主要原因

8.3、发生死锁的四个条件 互斥条件,线程使用的资源至少有一个不能共享的。至少有一个线程必须持有一个资源且正在等待获取一个当前被别的线程持有的资源。资源不能被抢占。循环等待。 8.4、如何解决死锁问题

破坏发生死锁的四个条件其中之一即可

8.5、手写死锁

package com.example.test.test;import java.util.concurrent.TimeUnit;class LockData implements Runnable {private String lockA;private String lockB;public LockData(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA) {System.out.println(Thread.currentThread().getName() + "持有 lockA 想要获取 lockB");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB) {System.out.println(Thread.currentThread().getName() + "持有 lockB 想要获取 lockA");}}}
}public class DeadLockDemo {public static void main(String[] args) {String lockA = "lockA";String lockB = "lockB";new Thread(new LockData(lockA, lockB), "AAA").start();new Thread(new LockData(lockB, lockA), "BBB").start();}
}

8.6、怎么诊断死锁 在控制台 jps -l 命令定位进程号 进程号 找到死锁查看

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了