volatile是Java虚拟机提供的轻量级的同步机制(详见JMM)
Java内存模型本身是一种抽象的概念并不真实存在,他描述的是一组规则或者规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称之为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
JMM可见性
各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存中进行操作后再写回到主内存中的,即工作内存与主内存同步延迟现象就造成了可见性问题。
JMM原子性
不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整,要么同时成功,要么同时失败。
volatile不保证原子性
VolatileDemo代码演示可见性
/**
* @author YLY
* @ClassName Test
* @Date 2020/4/29
* @Version 1.0.2
* 1、验证volatile的可见性
* 1.1 假如int number = 0; 如果number变量之前没有添加volatile关键字修饰
*/
class MyData {
int number = 0;
public void addT060() {
this.number = 60;
}
}
public class Test {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
myData.addT060();
System.out.println(Thread.currentThread().getName() + "\t update number value:"+myData.number);
}, "AAA").start();
while (myData.number==0){
}
System.out.println(Thread.currentThread().getName() + "\t mission is over ,get number value"+myData.number);
}
}
现象:系统3秒后线程AAA将主内存中的number值修改为60,但是main线程对其不可见,所以一直等待循环,直到number值不在等于零。
将myData修改成下面代码
class MyData {
volatile int number = 0;
public void addT060() {
this.number = 60;
}
}
现象:系统3秒后线程AAA将主内存中的number值修改为60,main线程对number具有可见性,不满足while的循环条件,即跳出循环结束程序。
public class Test {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 1; i <=20 ; i++) {
new Thread(()->{
for (int j = 1; j <=1000 ; j++) {
myData.addPlusPlus();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程全部计算完毕,再用main线程取得最终的结果
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t finally number value :"+myData.number);
}
}
class MyData {
volatile int number = 0;
public void addPlusPlus(){
number++;
}
}
现象:每次执行完之后都会少于20000,即number++ 在多线程环境下是非线程安全的,会出现数据丢失的情况。
JMM有序性
计算机在执行程序的时候,编译器和处理器常常会对指令做重排,在多线程环境下能否保证一致性是无法确定的,结果无法预测。可以使用synchronized或者volatile关键字解决,他们都可以使一个线程修改后的变量立即对其他线程可见。而且volatile还有一个作用就是静止指令重排。
class ReSortSeqDemo{
int a = 0;
boolean flag = false;
public void method01(){
a = 1;
flag = true;
}
/**
* 多线程环境中线程交替执行,由于编译器优化重排的存在,
* 两个线程中使用的变量能保证一致性是无法确定的,结果无法预测
*/
public void method02(){
if (flag){
a = a+5;
System.out.println("******retValue: "+a);
}
}
}
多线程下单例模式推荐使用双重检测保证效率和线程安全
/**
* @author yly
* @ClassName IdlerSingleton3 懒汉式3 双重检查(加入synchronized,和volatile线程安全,同时保证效率,推荐使用)
* @Date 2020/2/8 16:04
* @Version 1.0
**/
public class IdlerSingleton3 {
private IdlerSingleton3() {
}
private static volatile IdlerSingleton3 Instance;
public static IdlerSingleton3 getInstance() {
if (Instance == null) {
synchronized (IdlerSingleton3.class) {
if (Instance == null) {
Instance = new IdlerSingleton3();
}
}
}
return Instance;
}
}
compareAndSet(int expectedValue, int newValue)
compareAndSet第一个参数是期望值,第二个参数是修改值,
假如初始化值为5,初始化值和期望值相等,则重新赋值为2019,并返回true
假如初始化值为5,初始化值和期望值不相等,则不重新赋值,返回false
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current data "+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t current data "+atomicInteger.get());
}
}
结果:
true current data 2019
false current data 2019
public class AtomicInteger extends Number implements Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe U = Unsafe.getUnsafe();//初始化Unsafe
private static final long VALUE;//内存地址
private volatile int value;//初始化值
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
}
//实际上底层调用的是Unsafe.class下的compareAndSetInt(Object var1, long var2, int var4, int var5)
public final class Unsafe {
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object var1, long var2, int var4, int var5);
}
Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据,Unsafe类存在于 jdk.internal.misc包中,(Unsafe中jdk8在sun.misc包中),其内部方法操作可以向C一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS有3个操作数,内存值V,旧的预期值A,要修改的更新至B,当且仅当预期值A和内存值V相等时,将内存值V修改成B,否则什么都不做。
ABA问题(狸猫换太子):CAS算法实现一个重要的前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么这个时间差类会导致数据的变化,比如说一个线程one从内存位置V中取出A,这个时候另一个线程two也从内存中取出A,并且线程two进行一些操作将值变化B,然后线程two又将V位置的数据变成A,这个时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。尽管线程one的CAS操作成功,但是并不代表这个过程就是没有问题的。
class User{
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
//忽略getter/setter/toString方法
public static void main(String[] args) {
User zhangsan = new User("张三", 15);
User lisi = new User("李四", 20);
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(zhangsan);
System.out.println(userAtomicReference.compareAndSet(zhangsan, lisi)+"\t"+userAtomicReference.get().toString());
System.out.println(userAtomicReference.compareAndSet(zhangsan, lisi)+"\t"+userAtomicReference.get().toString());
}
}
//结果时
true User{username='李四', age=20}
false User{username='李四', age=20}
/**
* @author YLY
* @ClassName ABADemo
* @Date 2020/4/30
* @Version 1.0.2
*/
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(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
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 = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第1次版本号:"+stamp);
//暂停1秒钟t3线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第3次版本号:"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第1次版本号:"+stamp);
//暂停3秒钟t4线程,保证上面的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+"当前版本号为:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前实际最新值为:"+atomicStampedReference.getReference());
},"t4").start();
}
}
//结果为
==============以下是ABA问题的产生=================
true 2019
==============以下是ABA问题的解决=================
t3 第1次版本号:1
t4 第1次版本号:1
t3 第2次版本号:2
t3 第3次版本号:3
t4 修改成功与否:false 当前版本号为:3
t4 当前实际最新值为:100
线程不安全示例:
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i <300 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
//抛出java.util.ConcurrentModificationExceptionJava高并发修改异常
导致原因
并发争抢修改导致,参考我们的花名册签名情况。一个人正在写入,另一个同学过来抢夺,导致数据不一致异常,并发修改异常。
CopyOnWriteArrayList写时复制,CopyOnWrite容器即写时复制,往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行拷贝,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里面添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
jdk1.8源码:
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();
}
}
jdk11源码:
public boolean add(E e) {
synchronized(this.lock) {
Object[] es = this.getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
this.setArray(es);
return true;
}
}
解决方案:
1、new Vector<>();
2、Collections.synchronizedList(new ArrayList<>());
3、new CopyOnWriteArrayList<>();
解决方案
1、Collections.synchronizedSet(new HashSet<>());
2、Set<String> set = new CopyOnWriteArraySet<>();
其中CopyOnWriteArraySet时换汤不换药,底层是
new CopyOnWriteArrayList<E>();
//HashSet的底层是HashMap,初始值是16,负载因子是0.75
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//HashSet的add()方法调用HashMap的put方法,其中add的值是put的key,value是一个new Object()的常量。
线程不安全类HashMap高并发异常java.util.ConcurrentModificationException
解决方案
1、Collections.synchronizedMap(new HashMap<>());
2、new ConcurrentHashMap<>();
public class TestTransFerValue {
public void changeValue1(int age){
age = 30;
}
public void changeValue2(Person person){
person.setPersonName("xxxx");
}
public void changeValue3(String name){
name = "xxx";
}
public static void main(String[] args) {
TestTransFerValue transFerValue = new TestTransFerValue();
int age = 20;
transFerValue.changeValue1(age);
System.out.println("age--------"+age);
Person person = new Person("aaaaa");
transFerValue.changeValue2(person);
System.out.println("personName-----"+person.getPersonName());
String str = "abc";
transFerValue.changeValue3(str);
System.out.println("str -------"+ str);
}
}
class Person{
private int age;
private String personName;
public Person(String personName) {
this.personName = personName;
}
public int getAge() {
return age;
}
结果:
age--------20
personName-----xxxx
str -------abc
并发包中ReentrantLock的创建可以指定构造函数的Boolean类型来得到公平锁或者非公平锁,默认是非公平锁,非公平锁的优点在于吞吐量比公平锁大,对于synchronization而言,也是一种非公平锁。
可重入锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,即线程可以进入任何一个它已经拥有的锁所同步着的代码块。synchronization/ReentrantLock就是一个典型的可重入锁,可重入锁最大的作用是避免死锁。
public synchronized void method1(){
method2();
}
public synchronized void method2(){
}
public class SynchronizedLockDemo {
public static void main(String[] args) throws Exception{
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 {
public synchronized void sendSMS() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();
}
public synchronized void sendEmail() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
}
}
public class ReentrantLockDemo {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Thread t3 = new Thread(phone,"t3");
Thread t4 = new Thread(phone,"t4");
t3.start();
t4.start();
}
}
class Phone implements Runnable {
@Override
public void run() {
get();
}
Lock lock = new ReentrantLock();
public void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked get()");
set();
} finally {
lock.unlock();
}
}
public void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked set()");
} finally {
lock.unlock();
}
}
}
lock.lock();和lock.unlock();需要配对,无论加几次锁都可以运行成功。
尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
自定义自旋锁:
public class SpinLockDemo {
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.myUnLock();
}, "AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnLock();
}, "BB").start();
}
AtomicReference<Thread> atomicThread = new AtomicReference<>();
public void myLock() {
Thread thread =Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in myLock()");
while (!atomicThread.compareAndSet(null, thread)){
}
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicThread.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t invoked myLock()");
}
}
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁,读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
/**
* @author YLY
* @ClassName ReentrantReadWriteLockDemo
* @Date 2020/5/6
* @Version 1.0.2
* 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
* 但是
* 如果有一个线程想去写共享资源,就不应该再有其他线程可以对该资源进行读或者写
* 小总结:
* 读---读 能共存
* 读---写 不能共存
* 写---写 不能共存
* 写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断
*/
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i < 5; i++) {
final int timeInt = i;
new Thread(() -> {
myCache.put(timeInt + "", timeInt + "");
}, String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i < 5; i++) {
final int timeInt = i;
new Thread(() -> {
myCache.get(timeInt + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
try {
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 {
lock.writeLock().unlock();
}
}
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
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();
},CountryEnum.forEachEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println("秦国 \t一统天下 ");
}
private static void closeDoor() 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();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t 同学走完班长关门");
}
}
枚举:
public enum CountryEnum {
ONE(1,"齐"),
TWO(2,"楚"),
THREE(3,"燕"),
FOUR(4,"韩"),
FIVE(5,"赵"),
SIX(6,"魏");
private Integer retCode;
private String retMessage;
CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
public Integer getRetCode() {
return retCode;
}
public void setRetCode(Integer retCode) {
this.retCode = retCode;
}
public String getRetMessage() {
return retMessage;
}
public void setRetMessage(String retMessage) {
this.retMessage = retMessage;
}
public static CountryEnum forEachEnum(int index){
CountryEnum[] values = CountryEnum.values();
for (CountryEnum value : values) {
if (index==value.getRetCode()){
return value;
}
}
return null;
}
}
closeDoor()运行结果:
2 号同学离开教室
6 号同学离开教室
1 号同学离开教室
3 号同学离开教室
4 号同学离开教室
5 号同学离开教室
main 同学走完班长关门
main运行结果
齐国 被灭
楚国 被灭
燕国 被灭
魏国 被灭
韩国 被灭
赵国 被灭
秦国 一统天下
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("集齐龙珠召唤神龙");
});
for (int i = 1; i <=7 ; i++) {
final int tempInt = i;
new Thread(()->{
System.out.println("收集到第:"+tempInt+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
结果为:
收集到第:1颗龙珠
收集到第:5颗龙珠
收集到第:6颗龙珠
收集到第:7颗龙珠
收集到第:2颗龙珠
收集到第:3颗龙珠
收集到第:4颗龙珠
集齐龙珠召唤神龙
public class SemaphoreDemo {
public static void main(String[] args) {
/**
* 3个车位
*/
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();//抢占车位
System.out.println(Thread.currentThread().getName()+"\t 抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"\t 停车3秒后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//离开车位,通知其他线程继续抢占
}
},String.valueOf(i)).start();
}
}
}
结果展示:
1 抢到车位
2 抢到车位
6 抢到车位
1 停车3秒后离开车位
3 抢到车位
2 停车3秒后离开车位
4 抢到车位
6 停车3秒后离开车位
5 抢到车位
3 停车3秒后离开车位
4 停车3秒后离开车位
5 停车3秒后离开车位
阻塞队列有两种情况:
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒。
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("1");
blockingQueue.add("1");
blockingQueue.add("1");
blockingQueue.add("1");
}
}
//设置的ArrayBlockingQueue的初始值大小为3,如果add()数量超过3时会抛出**Exception in thread "main" //java.lang.IllegalStateException: Queue full**异常
当阻塞队列满时,再往队列里面add插入元素会抛出IllegalStateException: Queue full异常
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
blockingQueue.element();//检查队列是否为空,若不为空,则返回队列首部元素
blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
}
}
当阻塞队列空时,再往队列里remove移除元素会抛出NoSuchElementException异常
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("A"));
System.out.println(blockingQueue.offer("A"));
System.out.println(blockingQueue.offer("A"));
System.out.println(blockingQueue.offer("A"));
}
}
结果
true
true
true
false
使用offer()添加元素,若超出队列长度则返回false。
public class BlockingQueueDemo {
public static void main(String[] args) {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("A"));
System.out.println(blockingQueue.offer("B"));
System.out.println(blockingQueue.offer("C"));
System.out.println(blockingQueue.offer("D"));
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
}
结果:
true
true
true
false
A
A
B
C
nul
插入成功返回true 插入失败返回false,peek()如果队列有值,返回队列首部元素,如果没有返回null,poll()取出队列,如果队列空时返回null.
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("A");
blockingQueue.put("A");
blockingQueue.put("A");
System.out.println("##################");
blockingQueue.put("A");
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
}
}
put,当队列满时,会发生阻塞,程序会一直等待,take当队列空时会发生阻塞,程序会一直等待。
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("A",2, TimeUnit.SECONDS);
blockingQueue.offer("A",2, TimeUnit.SECONDS);
blockingQueue.offer("A",2, TimeUnit.SECONDS);
System.out.println("#############################");
blockingQueue.offer("A",2, TimeUnit.SECONDS);
}
}
当队列满时,会发生阻塞,在设置2秒之后,程序会超时退出
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<String> 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 {
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\t take "+blockingQueue.take());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\t take "+blockingQueue.take());
TimeUnit.SECONDS.sleep(5);
System.out.println(Thread.currentThread().getName() + "\t take "+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
}
}
//第一个队列插入和第二个队列插入中间需要等待5秒钟
使用Condition精准唤醒线程示例:
题目:多线程之间按顺序调用,实现A->B->C三个线程启动要求如下:
AA打印5次,BB打印10次,CC打印15次
紧接着
AA打印5次,BB打印10次,CC打印15次
打印10轮
class ShareResource {
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5() {
lock.lock();
try {
while (number != 1) {
c1.await();
}
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
while (number != 2) {
c2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
while (number != 3) {
c3.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class synchronizedAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
shareResource.print5();
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
shareResource.print10();
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
shareResource.print15();
}
},"CC").start();
}
}
package com.yangluyao.test.blockingqueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author YLY
* @ClassName ProdConsumer_BlockQueueDemo P44 线程通信之生产者消费者阻塞队列版
* @Date 2020/5/21
* @Version 1.0.2
*/
class MyResource {
private volatile boolean FLAG = true;
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myprod() throws Exception {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2L, 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 myConsumer() throws Exception {
String result = null;
boolean retValue;
while (FLAG) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (result == null || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t 超过两秒钟没有取到蛋糕");
System.out.println();
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费队列" + result + "成功");
}
}
public void stop() throws Exception {
this.FLAG = false;
}
}
/**
* 整合volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
* @author 81509
*/
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) throws Exception {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
System.out.println();
System.out.println();
try {
myResource.myprod();
System.out.println();
System.out.println();
} catch (Exception e) {
e.printStackTrace();
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
System.out.println();
System.out.println();
try {
myResource.myConsumer();
myResource.myConsumer();
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "Consumer").start();
TimeUnit.SECONDS.sleep(5);
System.out.println();
System.out.println();
System.out.println();
System.out.println();
System.out.println("5秒钟时间到,大老板叫停,活动结束");
myResource.stop();
}
}
之前多线程实现 Runnable 接口是执行工作独立任务,没有返回值,而Callable接口可以在任务完成之后将期望值返回,实现Callable接口,重写call()方法。
/**
* @author YLY
* @ClassName CallableDemo
* @Date 2020/5/22
* @Version 1.0.2
*/
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "**************** come Callable ");
TimeUnit.SECONDS.sleep(3);
return 1024;
}
}
/**
* Callable线程是异步重启线程,如果要开启多个Callable线程,需要创建多个FutureTask
* @author 81509
*/
public class CallableDemo {
public static void main(String[] args) throws Exception {
//FutureTask(Callable callable)
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
FutureTask<Integer> futureTask2 = new FutureTask<>(new MyThread());
new Thread(futureTask, "AA").start();
new Thread(futureTask2, "BB").start();
System.out.println(Thread.currentThread().getName() + "**************** come main ");
int result = 100;
//futureTask.isDone()表示该线程是否执行完毕
while (!futureTask.isDone()){
}
//futureTask.get() 获得Callable线程的计算结果,
// 如果没有计算完成就要强行获取Callable值,会导致堵塞,直到计算完成
System.out.println("************************ result: " + (result+futureTask.get()));
}
}
为什么使用线程池,优势是什么?
线程池如何使用?
架构说明?
常用方式
Executors.newFixedThreadPool(int) 一池固定数量线程 执行长期任务,性能好很多
主要特点:1、创建一个定长线程池,可控制线程最大并发数。超出的线程会在队列中等待
2、newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用LinkedBlockingQueue
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
Executors.newSingleThreadExecutor() 一池一线程 一个任务一个任务执行的场景
主要特点:1、创建一个单线程化的线程池,她只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
2、newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,它使用LinkedBlockingQueue
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Executors.newCachedThreadPool() 一池多线程 适用:执行很多短期异步的小程序或者负载较轻的服务器。
主要特点:1、创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2、newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就新建线程运行,当线程空闲超过60秒,就销毁线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
线程池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;
}
keepAliveTime值时,多余空闲线程会被销毁直到剩下corePoolSize 个线程为止。
线程池底层工作原理
拒绝策略
工作中使用单一的/固定数的/可变的三种创建线程池的方法,用哪个比较多? 一个也不用
合理配置线程池
死锁是指两个或者两个以上的进程在执行过程中因争夺资源而造成的一种互相等待的现象,若无外力干涉那么它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就会很低,否则就会因争夺有限的资源而陷入死锁。
造成死锁的原因
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(2);
}catch (Exception e){
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 尝试获得"+lockA );
}
}
}
}
/**
* @author YLY
* @ClassName deadLockDemo 死锁demo
* @Date 2020/5/23
* @Version 1.0.2
*/
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();
}
}
定位分析