面试第二季
JUC是什么
java并发包
1.谈谈你对volatile的理解
1.volatile是Java虚拟机提供的轻量级的同步机制
1.1保证可见性
1.2不保证原子性
1.3禁止指令重排
2.JMM你谈谈(Java内存模型)
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式.
JMM关于同步规定:
1.线程解锁前,必须把共享变量的值刷新回主内存
2.线程加锁前,必须读取主内存的最新值到自己的工作内存
3.加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,此案成间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
主内存:内存条(硬件)
线程的工作内存:只是cpu的寄存器和高速缓存的抽象描述
共享变量:就是对象这些
JMM的特性:可见性 原子性 有序性
JMM可见性:只要主物理内存的值被一个线程修改了其他线程马上获得通知并且修改
数据的访问顺序:硬盘<内存<缓存cache<cpu
JMM的硬件抽象(缓存cache)
/**
* 1验证volatile的可见性
* 1.1 如果int num = 0,number变量没有添加volatile关键字修饰
* 1.2 添加了volatile,可以解决可见性
*
* 2.验证volatile不保证原子性
* 2.1 原子性指的是什么
* 不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败
* 2.2 如何解决原子性
* 2.2.1 方法加synchronized
* 2.2.2 Atomic
*
*/
public class VolatileDemo {
public static void main(String[] args) {
visibilityByVolatile();//验证volatile的可见性
// atomicByVolatile();//验证volatile不保证原子性
}
/**
* volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
*/
public static void visibilityByVolatile() {
MyData myData = new MyData();
//第一个线程
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
//线程暂停3s
TimeUnit.SECONDS.sleep(3);
myData.addToSixty();
System.out.println(Thread.currentThread().getName() + "\t update value:" + myData.num);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, "thread1").start();
//第二个线程是main线程
while (myData.num == 0) {
//如果myData的num一直为零,main线程一直在这里循环
}
System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myData.num);
}
/**
* volatile不保证原子性
* 以及使用Atomic保证原子性
*/
public static void atomicByVolatile(){
MyData myData = new MyData();
for(int i = 1; i <= 20; i++){
new Thread(() ->{
for(int j = 1; j <= 1000; j++){
myData.addSelf();
myData.atomicAddSelf();
}
},"Thread "+i).start();
}
//等待上面的线程都计算完成后,再用main线程取得最终结果值
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t finally num value is "+myData.num);
System.out.println(Thread.currentThread().getName()+"\t finally atomicnum value is "+myData.atomicInteger);
}
}
class MyData {
int num = 0;
// volatile int num = 0;
public synchronized void addToSixty() {
this.num = 60;
}
public void addSelf(){
num++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void atomicAddSelf(){
atomicInteger.getAndIncrement();
}
}
class ResortSeq{
int a = 0;
boolean flag = false;
public void method01(){
a = 1;
flag = true;
}
public void method02(){
if(flag){
a = a + 5;
System.out.println("\"a\" value is "+a);
}
}
}
数据原子性的解决方法:可以使用synchronized(重型锁)
number三个阶段,先是获取,改变,放回主存
为什么数值少于20000
出现了丢失写值的情况
没有加synchronized线程进来是疯抢资源(getfiled)iadd在线程各自的工作内存里面加1然后返回到主内存里面(正常流程是线程1在自己的工作内存里面加了1然后放到主内存里面第二个线程因为Volatile修饰具有可见性的原因所以会拿到1然后再在自己的工作内存中加1返回2)
如果多个线程都拿到资源0加了1过后都去写,但是一个线程突然被挂起,一个线程写成功,在Volatile修饰通知其他线程,在其他线程还没有反应这个获取到的原数据被改动过来的前提下,挂起的线程被唤醒,也写到主存,这就有多次写的操作,数据丢失,Volatile不保证原子性就是说线程在操作的中有其他线程加塞(有顺序的写,有序原子性)不有序的写
如何解决原子性
AtomicInteger atomicInteger = new AtomicInteger();
public void atomicAddSelf(){
atomicInteger.getAndIncrement();
}
atomic 线程做完工作才可以别的线程才可以抢才可以写覆盖
这是为什么呢?CAS
有序性:
计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3中
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.
处理器在进行重新排序是必须要考虑指令之间的数据依赖性
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测
就是高考的时候先做简单题
指令重排1
public void mySort(){
int x=11;//语句1
int y=12;//语句2
x=x+5;//语句3
y=x*x;//语句4
}
1234
2134
1324
问题:
请问语句4 可以重排后变成第一条码?
存在数据的依赖性 没办法排到第一个
指令重排2
volatile关键字的原理
volatile实现禁止指令重排优化从而避免多线程环境下程序出现乱序执行的现象
先了解一个概念内存屏障又称为内存栅栏是一个CPU的指令它的作用有两个
一是保证特定操作的执行顺序
二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)
线程安全获得保障
CAS
1.比较并交换
/**
* Description
*
* @author veliger@163.com
* @version 1.0
* @date 2019-04-12 9:57
* 1.什么是CAS ? ===> compareAndSet
* 比较并交换
**/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2014)+"\t current"+atomicInteger.get());
}
}
2.CAS底层原理?如果知道,谈谈你对UnSafe的理解
atomicInteger.getAndIncrement();
atomicInteger.getAndIncrement()方法的源代码:
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
印出来一个问题:UnSafe类是什么?
UnSafe
1.UnSafe
是CAS的核心类 由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后面,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的助兴依赖于UNSafe类的方法.
注意UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务
2.变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
3.变量value和volatile修饰,保证了多线程之间的可见性.
CAS是什么
CAS的全称为Compare-And-Swap ,它是一条CPU并发原语.
它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的.
CAS并发原语提现在Java语言中就是sun.miscUnSaffe类中的各个方法.调用UnSafe类中的CAS方法,JVM会帮我实现CAS汇编指令.这是一种完全依赖于硬件 功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许中断,也即是说CAS是一条原子指令,不会造成所谓的数据不一致的问题.
var1 AtomicInteger对象本身.
var2 该对象值的引用地址
var4 需要变动的数值
var5 是用过var1 var2找出内存中绅士的值
用该对象当前的值与var5比较
如果相同,更新var5的值并且返回true
如果不同,继续取值然后比较,直到更新完成
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
过程
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别在不同的CPU上):
1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存.
2.线程A通过getIntVolatile(var1,var2) 拿到value值3,这是线程A被挂起.
3.线程B也通过getIntVolatile(var1,var2) 拿到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存中的值也是3 成功修改内存的值为4 线程B打完收工 一切OK.
4.这是线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的数值和内存中的数字4不一致,说明该值已经被其他线程抢先一步修改了,那A线程修改失败,只能重新来一遍了.
5.线程A重新获取value值,因为变量value是volatile修饰,所以其他线程对他的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt方法进行比较替换,直到成功.
汇编底层
CAS的缺点
1.循环时间长开销很大
2.只能保证一个共享变量的原子性
3.引出来ABA问题???
原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗
CAS–>UnSafe–>CAS底层思想–>ABA–>原子引用更新–>如何-规避ABA问题
ABA狸猫换太子
ABA问题的产生
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
原子引用
AtomicReferenceDemo
/**
* Description:
*
* @author veliger@163.com
* @date 2019-04-12 21:23
**/
@Getter@Setter@AllArgsConstructor@ToString
class User{
private String name;
private int age;
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
User zs = new User("zs", 22);
User ls = new User("ls", 22);
AtomicReference userAtomicReference = new AtomicReference();
userAtomicReference.set(zs);
System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
}
}
相当于自定义的atomicInteger包装一下
时间戳的原子引用
理解原子引用+新增的机制,那就是修改版本号(类似时间戳)
T1 100 1 2019 2
T2 100 1 101 2 100 3
2019的版本号2小于100的3所以修改作废
带时间戳的原子引用
AtomicStampedReference
/**
* Description: ABA问题的解决
*
* @author veliger@163.com
* @date 2019-04-12 21:30
**/
public class ABADemo {
private static AtomicReference atomicReference=new AtomicReference(100);
private static AtomicStampedReference stampedReference=new AtomicStampedReference(100,1);
public static void main(String[] args) {
System.out.println("===以下是ABA问题的产生===");
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
//先暂停1秒 保证完成ABA
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t2").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("===以下是ABA问题的解决===");
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//暂停1秒钟t3线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
},"t3").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//保证线程3完成1次ABA
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp());
System.out.println("最新的值\t"+stampedReference.getReference());
},"t4").start();
}
结果展示
集合不安全的问题
ContainerNotSafeDemo
底层原理
**
* Description: 集合类不安全的问题
*
* @author veliger@163.com
* @date 2019-04-12 22:15
**/
public class ContainerNotSafeDemo {
/**
* 笔记
* 写时复制 copyOnWrite 容器即写时复制的容器 往容器添加元素的时候,不直接往当前容器object[]添加,而是先将当前容器object[]进行
* copy 复制出一个新的object[] newElements 然后向新容器object[] newElements 里面添加元素 添加元素后,
* 再将原容器的引用指向新的容器 setArray(newElements);
* 这样的好处是可以对copyOnWrite容器进行并发的读,而不需要加锁 因为当前容器不会添加任何容器.所以copyOnwrite容器也是一种
* 读写分离的思想,读和写不同的容器.
* public boolean add(E e) {
* final ReentrantLock lock = this.lock;
* lock.lock();
* try {
* Object[] elements = getArray();
* int len = elements.length;
* Object[] newElements = Arrays.copyOf(elements, len + 1);
* newElements[len] = e;
* setArray(newElements);
* return true;
* } finally {
* lock.unlock();
* }
* }
* @param args
*/
public static void main(String[] args) {
List list= new CopyOnWriteArrayList();
for (int i = 1; i 30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(1,8));
System.out.println(list);
},String.valueOf(i)).start();
}
/**
* 1.故障现象
* java.util.ConcurrentModificationException
* 2.导致原因
* 并发争抢修改导致
* 3.解决方案
* 3.1 new Vector()
* 3.2 Collections.synchronizedList(new ArrayList());
* 3.3 new CopyOnWriteArrayList();
*
*
* 4.优化建议
*/
}
add方法没有加线程安全的限定
限制不可以使用vector和Collections工具类解决方案2
ConcurrentModificationException
List线程CopyOnWriteArrayList
set线程CopyOnwriteHashSet
底层是一个CopyOnWriteArrayList,HashSet的底层数据结构是hashmap初始值是16负载因子是0.75的hashmap,怎么解释参数一个是1一个是2?
map线程ConcurrentHashMap
方法
变量
string
公平锁和非公平
公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁
公平锁
是指多个线程按照申请锁的顺序来获取锁类似排队打饭 先来后到
非公平锁
是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象
ReentrantLock reentrantLock = new ReentrantLock(true);
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Java ReentrantLock而言,
通过构造哈数指定该锁是否是公平锁 默认是非公平锁 非公平锁的优点在于吞吐量必公平锁大.
对于synchronized而言 也是一种非公平锁.
可重入锁(又名递归锁)
指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块
ReentrantLock/synchronized就是一个典型的可重入锁
可重入锁最大的作用就是避免死锁
class Phone{
public synchronized void sendSms() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendSms");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendEmail");
}
}
/**
* Description:
* 可重入锁(也叫做递归锁)
* 指的是同一先生外层函数获得锁后,内层敌对函数任然能获取该锁的代码
* 在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
*
* 也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
*
* @author veliger@163.com
* @date 2019-04-12 23:36
**/
public class ReenterLockDemo {
/**
* t1 sendSms
* t1 sendEmail
* t2 sendSms
* t2 sendEmail
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
class Phone implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tget");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tset");
} finally {
lock.unlock();
}
}
}
/**
* Description:
* 可重入锁(也叫做递归锁)
* 指的是同一先生外层函数获得锁后,内层敌对函数任然能获取该锁的代码
* 在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
*
* 也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
*
* @author veliger@163.com
* @date 2019-04-12 23:36
**/
public class ReenterLockDemo {
/**
* Thread-0 get
* Thread-0 set
* Thread-1 get
* Thread-1 set
*
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
Thread t3 = new Thread(phone);
Thread t4 = new Thread(phone);
t3.start();
t4.start();
}
}
两个锁也匹配两个锁的解锁
自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CUP
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 实现自旋锁
* 自旋锁好处,循环比较获取知道成功位置,没有类似wait的阻塞
*
* 通过CAS操作完成自旋锁,A线程先进来调用mylock方法自己持有锁5秒钟,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,知道A释放锁后B随后抢到
*/
public class SpinLockDemo {
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.mylock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
spinLockDemo.myUnlock();
}, "Thread 1").start();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.mylock();
spinLockDemo.myUnlock();
}, "Thread 2").start();
}
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void mylock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnlock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName()+"\t invoked myunlock()");
}
}
独占锁(写)/共享锁(读)/互斥锁
独占锁:指该锁一次只能被一个线程所持有,对ReentrantLock和synchronized而言都是独占锁
共享锁:指锁可被多个线程所持有
对ReentrantReadWriteLock其锁是共享锁,其写锁是独占锁
读锁的共享锁可保证并发读是非常高效的,读写 写读 写写的过程是互斥的
ReentrantReadWriteLock
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
* 但是
* 如果有一个线程象取写共享资源来,就不应该自由其他线程可以对资源进行读或写
* 总结
* 读读能共存
* 读写不能共存
* 写写不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt + "", tempInt + "");
}, "Thread " + i).start();
}
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(tempInt + "");
}, "Thread " + i).start();
}
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt + "", tempInt * 2);
}, "Thread====" + i).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
/**
* 写操作:原子+独占
* 整个过程必须是一个完整的统一体,中间不许被分割,不许被打断
*
* @param key
* @param value
*/
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在写入:" + key);
TimeUnit.MILLISECONDS.sleep(300);
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正在读取:" + key);
TimeUnit.MILLISECONDS.sleep(3000);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t读取完成: " + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
}
public void clear() {
map.clear();
}
}
6.CountDownLatch/CyclicBarrier/Semaphore使用过吗?
计数器:CountDownLatch
让一些线程阻塞直到另外一些完成后才被唤醒
CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞.其他线程调用countDown方法计数器减1(调用countDown方法时线程不会阻塞),当计数器的值变为0,因调用await方法被阻塞的线程会被唤醒,继续执行
package com.jian8.juc.conditionThread;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// general();
countDownLatchTest();
}
public static void general(){
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t上完自习,离开教室");
}, "Thread-->"+i).start();
}
while (Thread.activeCount()>2){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName()+"\t=====班长最后关门走人");
}
public static void countDownLatchTest() 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();
}, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t=====秦统一");
}
}
枚举类型
package com.jian8.juc.conditionThread;
import lombok.Getter;
public enum CountryEnum {
ONE(1, "齐国"), TWO(2, "楚国"), THREE(3, "燕国"),
FOUR(4, "赵国"), FIVE(5, "魏国"), SIX(6, "韩国");
@Getter
private Integer retCode;
@Getter
private String retMessage;
CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
public static CountryEnum forEach_CountryEnum(int index) {
CountryEnum[] myArray = CountryEnum.values();
for (CountryEnum countryEnum : myArray) {
if (index == countryEnum.retCode) {
return countryEnum;
}
}
return null;
}
}
CyclicBarrier
CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法.
CyclicBarrierDemo
集齐7颗龙珠就能召唤神龙
package com.jian8.juc.conditionThread;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
cyclicBarrierTest();
}
public static void cyclicBarrierTest() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("====召唤神龙=====");
});
for (int i = 1; i <= 7; i++) {
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t收集到第" + tempInt + "颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, "" + i).start();
}
}
}
Semaphore
信号量的主要用户两个目的,一个是用于多喝共享资源的相互排斥使用,另一个用于并发资源数的控制.
SemaphoreDemo
package com.jian8.juc.conditionThread;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(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);//停车3s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t停车3s后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, "Car " + i).start();
}
}
}
7.阻塞队列知道吗?
阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:线程1往阻塞队列中添加元素二线程2从队列中移除元素
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞.
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞.
同样
试图往已满的阻塞队列中添加新圆度的线程同样也会被阻塞,知道其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增.
为什么用?有什么好处?
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程优惠被自动唤醒
为什么需要使用BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手给你包办好了
在concurrent包 发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度.\
BlockingQueue的核心方法
抛出异常
当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full
当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException
特殊值
插入方法,成功返回true 失败返回false
移除方法,成功返回元素,队列里面没有就返回null
一直阻塞
当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出
当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用.
超时退出
当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出
架构梳理+种类分析
架构介绍
种类分析
ArrayBlockingQueue: 由数组结构组成的有界阻塞队列.
LinkedBlockingDeque: 由链表结构组成的有界(但大小默认值Integer>MAX_VALUE)阻塞队列.
PriorityBlockingQueue:支持优先级排序的无界阻塞队列.
DelayQueue: 使用优先级队列实现的延迟无界阻塞队列.
SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列.
理论:SynchronousQueue没有容量,与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue,每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然.
LinkedTransferQueue:由链表结构组成的无界阻塞队列.
LinkedBlockingDeque:由了解结构组成的双向阻塞队列.
SynchronousQueueDemo
SynchronousQueue没有容量
与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue
每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然.
/**
* Description
* 阻塞队列SynchronousQueue演示
*
* @author veliger@163.com
* @version 1.0
* @date 2019-04-13 13:49
**/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueueString> blockingQueue = new SynchronousQueue();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(() -> {
try {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
}
}
应用到哪里呢?
生产者消费者模式
线程池
消息中间件
传统版:ProdConsumerTraditionDemo
/**
* 共享资源类
*/
class ShareData {
private int num = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock();
try {
//判断
while (num != 0) {
//等待 不生产
condition.await();
}
//干活
num++;
System.out.println(Thread.currentThread().getName() + "\t" + num);
//通知唤醒
condition.signalAll();
} finally {
lock.unlock();
}
}
public void deIncrement() throws Exception {
lock.lock();
try {
//判断
while (num == 0) {
//等待 不生产
condition.await();
}
//干活
num--;
System.out.println(Thread.currentThread().getName() + "\t" + num);
//通知唤醒
condition.signalAll();
} finally {
lock.unlock();
}
}
}
/**
* Description
* 一个初始值为0的变量 两个线程交替操作 一个加1 一个减1来5轮
*
* @author veliger@163.com
* @version 1.0
* @date 2019-04-13 14:01
**/
public class ProdConsumerTraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 1; i 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 1; i 5; i++) {
try {
shareData.deIncrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "BB").start();
}
}
阻塞队列版:ProdConsumerBlockQueueDemo
class MyResource {
/**
* 默认开启 进行生产消费的交互
*/
private volatile boolean flag = true;
/**
* 默认值是0
*/
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueueString> blockingQueue = null;
public MyResource(BlockingQueueString> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data = null;
boolean returnValue;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (returnValue) {
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" + flag);
}
public void myConsumer() throws Exception {
String result = null;
while (flag) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if(null==result||"".equalsIgnoreCase(result)){
flag=false;
System.out.println(Thread.currentThread().getName()+"\t"+"超过2m没有取到 消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "消费队列" + result + "成功");
}
}
public void stop() throws Exception{
flag=false;
}
}
/**
* Description
* volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
*
* @author veliger@163.com
* @version 1.0
* @date 2019-04-13 14:02
**/
public class ProdConsumerBlockQueueDemo {
public static void main(String[] args) throws Exception {
MyResource myResource = new MyResource(new ArrayBlockingQueue(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
},"consumer").start();
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println();
System.out.println();
System.out.println();
System.out.println("时间到,停止活动");
myResource.stop();
}
}
synchnized和lock的区别
二者的区别
synchnized和lock有什么区别?用新的lock有什么好处?你举例说明
1原始构成
synchronized是关键字属于jvm层面,
monitorenter(底层是通过monitor)对象完成其实wait和/notify等方法也依赖于monitor对象只有在同步
块或方法中才能调wait/notify等方法monitorexit
Lock是具体类(java. util. concurrent. locks. Lock)是api层面的锁
2使用方法
synchronized不需要用户去手动释放锁,当synchronized 代码执行完后系统会自动让线程释放对锁的占用
Reentrantlock,则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。
需要lock() lunlock()方法配合try/finally语句块来完成。
3等待是否可中断
synchronized不可中断,除非抛出异常或者程序正常运行完成
ReentrantLock可中断,1. 设置超时方法tryLock(long timeout, TimeUnit unit)
4加锁是否公平
synchronized非公平锁
ReentrantLock两者都可以,默认公平锁,构造方法可以传入boolean值,true为公平锁, false 为非公平锁
5锁绑定多个条件Condition
synchronized没有
ReentrantLock,用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
ProdConsumer_BlockQueueDemo
/**
* volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
* 不用自我判断什么时候wait什么时候notify这个阻塞对列自己判断 生产一个消费一个
*/
class MySource{
//默认开启,进行生产+消费 volatile高并发下面保持线程之间的可见性 多线程下面不用++i或者说是i++
private volatile boolean FLAG=true;
private AtomicInteger atomicInteger =new AtomicInteger();
//架构的时候一般是传接口不能传递类 只要实现了这个接口的类你都要满足所说注入的思想 构造方法注入set注入 依赖注入
BlockingQueue<String> blockingQueue=null;
public MySource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
//传入接口往高处写,落地的实现往低处更具体的实现
System.out.println(blockingQueue.getClass().getName());
}
//这个是生产者
public void myProd()throws Exception{
String data=null;
boolean retVale;
while (FLAG){//尽量复用
data= atomicInteger.incrementAndGet()+"";
//往对列里面中插入数据
retVale = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retVale){
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()+"大老板叫停,生产动作结束");
}
//这个是消费者
public void myConsumer()throws Exception{
String result=null;
while (FLAG){
//往对列里面中取出数据
result= blockingQueue.poll(2L,TimeUnit.SECONDS);
//没有取出或者说对列为空则直接消费退出
if (null==result || result.equalsIgnoreCase("")){
FLAG=false;
System.out.println(Thread.currentThread().getName()+"\t超过2秒钟没有收取到蛋糕,消费退出");
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName()+"消费对列蛋糕"+result+"成功");
}
}
//停止
public void stop()throws Exception{
this.FLAG=false;
}
}
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MySource mySource = new MySource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 生产线程启动");
try {
mySource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 消费线程启动");
try {
mySource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
},"MyConsumer").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("5秒钟时间到,大老板main线程叫停,结束");
try {
mySource.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Callable
分支合并操作
线程池的理解
为什么用线程池,优势?
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行.
他的主要特点为:线程复用:控制最大并发数:管理线程.
第一:降低资源消耗.通过重复利用自己创建的线程降低线程创建和销毁造成的消耗.
第二: 提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行.
第三: 提高线程的可管理性.线程是稀缺资源,如果无限的创建,不仅会消耗资源,还会较低系统的稳定性,使用线程池可以进行统一分配,调优和监控.
线程池如何使用?
架构实现
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类.
了解
Executors.newCachedThreadPool();
java8新出
Executors.newWorkStealingPool(int);java8新增,
使用目前机器上可以的处理器作为他的并行级别
重点
Executors.newFixedThreadPool(int) 执行一个长期的任务,性能好很多
主要特点如下:
1.创建一个定长线程池,可控制线程的最大并发数,超出的线程会在队列中等待.
2.newFixedThreadPool创建的线程池corePoolSize和MaxmumPoolSize是 相等的,它使用的的LinkedBlockingQueue
Executors.newSingleThreadExecutor() 一个任务一个线程执行的任务场景
主要特点如下:
1.创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务都按照指定顺序执行.
2.newSingleThreadExecutor将corePoolSize和MaxmumPoolSize都设置为1,它使用的的LinkedBlockingQueue
Executors.newCachedThreadPool() 适用:执行很多短期异步的小程序或者负载较轻的服务器
主要特点如下:
1.创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程.
2.newCachedThreadPool将corePoolSize设置为0MaxmumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQUeue,也就是说来了任务就创建线程运行,如果线程空闲超过60秒,就销毁线程
编码实现
/**
* 第四种获得使用Java多线程的方式,线程池
* //一池固定数5线程
* ExecutorService threadPool= Executors.newFixedThreadPool(5);
* pool-1-thread-1办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-3办理业务
* pool-1-thread-1办理业务
* pool-1-thread-4办理业务
* pool-1-thread-5办理业务
*/
//一池固定数1线程ExecutorService threadPool=Executors.newSingleThreadExecutor();
/**
* 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办理业务
*/
/**
* //一池带有缓存的N个处理线程ExecutorService threadPool=Executors.newCachedThreadPool();
* pool-1-thread-2办理业务
* pool-1-thread-1办理业务
* pool-1-thread-3办理业务
* pool-1-thread-4办理业务
* pool-1-thread-5办理业务
* pool-1-thread-6办理业务
* pool-1-thread-7办理业务
* pool-1-thread-8办理业务
* pool-1-thread-9办理业务
* pool-1-thread-2办理业务
*/
/**try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
* 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办理业务
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
/**
* 三种线程的底层源码就是这个
* ThreadPoolExecutor底层源码就是这个
*/
//一池固定数5线程
// ExecutorService threadPool= Executors.newFixedThreadPool(5);
//一池固定数1线程
// ExecutorService threadPool=Executors.newSingleThreadExecutor();
//一池带有缓存的N个处理线程
ExecutorService threadPool=Executors.newCachedThreadPool();
//模拟10个用户来办理业务每个用户就是来自外部请求线程 由这5个人来办理
try {
for (int i = 0; i <10 ; i++)
{
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
线程池几个重要参数介绍?
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.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;
}
1.corePoolSize:线程池中的常驻核心线程数
1.在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近视理解为今日当值线程
2.当线程池中的线程数目达到corePoolSize后,就会把到达的任务放入到缓存队列当中.
2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1
3.keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止
4.unit:keepAliveTime的单位
5.workQueue:任务队列,被提交但尚未被执行的任务.
6.threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可
7.handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示 数(maxnumPoolSize)时如何来拒绝.
原理:
1.在创建了线程池后,等待提交过来的任务请求。
2.当调用execute() 方法添加一个请求任务时,线程池会做如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务:
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
x3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做超过- 定的时间(keepAliveTime) 时,线程池会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
拒绝策略
等待队列也已经排满了,再也塞不下新的任务了
同时,
线程池的max也到达了,无法接续为新任务服务
这时我们需要拒绝策略机制合理的处理这个问题.
JDK内置的拒绝策略:
AbortPolicy(默认):直接抛出RejectedException异常阻止系统正常运行
CallerRunPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低线程压力
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常.如果允许任务丢失,这是最好的拒绝策略
以上内置策略均实现了RejectExecutionHandler接口
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
线程池的使用
你在工作中单一的/固定数的/可变你的三种创建线程池的方法,你用哪个多?超级大坑
答案是一个都不用,我们生产上只能使用自定义的
Executors中JDK给你提供了为什么不用?
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
自定义线程池
/**
* 第四种获得使用Java多线程的方式,线程池
* //一池固定数5线程
* ExecutorService threadPool= Executors.newFixedThreadPool(5);
* pool-1-thread-1办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-2办理业务
* pool-1-thread-3办理业务
* pool-1-thread-1办理业务
* pool-1-thread-4办理业务
* pool-1-thread-5办理业务
*/
//一池固定数1线程ExecutorService threadPool=Executors.newSingleThreadExecutor();
/**
* 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办理业务
*/
/**
* //一池带有缓存的N个处理线程ExecutorService threadPool=Executors.newCachedThreadPool();
* pool-1-thread-2办理业务
* pool-1-thread-1办理业务
* pool-1-thread-3办理业务
* pool-1-thread-4办理业务
* pool-1-thread-5办理业务
* pool-1-thread-6办理业务
* pool-1-thread-7办理业务
* pool-1-thread-8办理业务
* pool-1-thread-9办理业务
* pool-1-thread-2办理业务
*/
/**
* 加时间
* try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
* 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办理业务
*/
/**自定义线程池
* AbortPolicy()
* java.util.concurrent.RejectedExecutionException: Task com.juc.MyThreadPoolDemo$$Lambda$1/495053715@5b6f7412 rejected from java.util.concurrent.ThreadPoolExecutor@27973e9b[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
* at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
* at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
* at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
* at com.juc.MyThreadPoolDemo.main(MyThreadPoolDemo.java:98)
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-2办理业务
* pool-1-thread-3办理业务
* pool-1-thread-4办理业务
* pool-1-thread-5办理业务
*/
/**
* CallerRunsPolicy()
* main办理业务
* main办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-2办理业务
* pool-1-thread-4办理业务
* pool-1-thread-3办理业务
* pool-1-thread-5办理业务
*/
/**
* DiscardPolicy()丢弃线程
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-1办理业务
* pool-1-thread-2办理业务
* pool-1-thread-3办理业务
* pool-1-thread-4办理业务
* pool-1-thread-5办理业务
*/
/**
* DiscardOldestPolicy()丢弃最老的
* pool-1-thread-2办理业务
* pool-1-thread-4办理业务
* pool-1-thread-4办理业务
* pool-1-thread-4办理业务
* pool-1-thread-3办理业务
* pool-1-thread-1办理业务
* pool-1-thread-2办理业务
* pool-1-thread-5办理业务
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool=new ThreadPoolExecutor(
2,
5,
1L,TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
/**
* 三种线程的底层源码就是这个
* ThreadPoolExecutor底层源码就是这个
*/
//一池固定数5线程
// ExecutorService threadPool= Executors.newFixedThreadPool(5);
//一池固定数1线程
// ExecutorService threadPool=Executors.newSingleThreadExecutor();
//一池带有缓存的N个处理线程
// ExecutorService threadPool=Executors.newCachedThreadPool();
//模拟10个用户来办理业务每个用户就是来自外部请求线程 由这5个人来办理
try {
for (int i = 0; i <10 ; i++)
{
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
} catch (Exception e) {
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
合理配置线程池你是如何考虑的?
最大线程数
while true CPU密集型
System.out.println(Runtime.getRuntime().availableProcessors());查看CPU核数
IO密集型
高并发下的数据库取出数据
方式1:
方式2:
死锁
系统资源不足
进程运行推进的顺序不合适
资源分配不当
代码
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()+"自己持有"+lockA+"尝试获得"+lockB);
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 HoldLockThread(lockA,lockB),"AAA").start();
new Thread(new HoldLockThread(lockB,lockA),"BBB").start();
}
}
解决: