JMM
JVM运行程序的实体是线程,每个线程创建时JVM都会分配一个线程自己私有的工作内存
JMM(java memory model)是指Java内存模型(一种规范,并不真实存在),不是Java内存布局,不是所谓的栈、堆、方法区。
JMM关于同步的规定:
1.线程解锁前,必须把共享变量的值刷新回主存(共享内存区域)
2.线程加锁前,必须读取主内存的最新值到自己的工作内存(各个线程私有的数据区域——所以线程间的通信必须通过主存)
3.加锁解锁是同一把锁
每个Java线程都有自己的工作内存。操作数据,首先从主内存中读,得到一份拷贝,操作完毕后再写回到主内存。
JMM可能带来可见性、原子性和有序性问题。
所谓可见性,就是某个线程对主内存内容的更改,应该立刻通知到其它线程。原子性是指一个操作是不可分割的,不能执行到一半,就不执行了。所谓有序性,就是指令是有序的,不会被重排。
volatile关键字
volatile关键字是Java提供的一种轻量级同步机制。
三大特性:
1.它能够保证可见性,
2.但是不能保证原子性,
3.禁止指令重排,有序性。
可见性
可能存在一个问题:一个线程AAA修改了共享变量X的值但还没写回主存,另一个线程BBB又对主存同一个共享变量进行了操作,但此时A线程工作内存中共享变量x对线程B来说不可见,这种工作内存于主存同步延迟的现象造成了可见性问题
因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
class MyData{
volatile int num = 0;
public void addTo60(){
this.num = 60;
}
}
public class VolatileDemo {
/**
1.验证可见性
1.1 加入int num=0;num之前没有添加volatile关键字
进入while死循环
1.2加入volatile到num,3秒后main离开死循环
**/
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " come in");
//延时3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + " updata value:" + myData.num);
}, "AAA").start();
while (myData.num == 0){
//num=0 main一直在这里等待循环
//没有volatile时候,进入死循环,因为main线程
//不知道3秒后AAA更新了num
}
//加了volatile(可见性),main线程才可以得到num改变的通知
System.out.println(Thread.currentThread().getName() + " mission over;");
}
}
volatile不保证原子性
结果不是20000,显然不保证原子性
class MyData{
volatile int num = 0;
public void addTo60(){
this.num = 60;
}
public void addPP(){
num++;
}
}
public class VolatileDemo {
/**
1.验证可见性
1.1 加入int num=0;num之前没有添加volatile关键字
进入while死循环
1.2加入volatile到num,3秒后main离开死循环
2.不保证原子性:不可分割,完整性,某个线程做某具体业务时候中间不可被分割,
要么同时成功要么同时失败
2.1 结果不是20000
**/
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addPP();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount() > 2){//除了main和gc线程
Thread.yield();//异步执行,main让出资源
}
System.out.println(Thread.currentThread().getName() + " num :" + myData.num);
}
Java中只有对基本类型变量的赋值和读取是原子操作,如i = 1的赋值操作,但是像j = i或者i++这样的操作都不是原子操作,因为他们都进行了多次原子操作,比如先读取i的值,再将i的值赋值给j,两个原子操作加起来就不是原子操作了。
++操作底层是3条指令
getfield //读,拿到原始num
iconst_1 //工作内存的变量+1
iadd //加操作
putfield //写操作(可能写覆盖)
- 解释:为什么这里的可见性不能保证原子性,因为赋值和自增等操作在底层是多步的,即使见到其他线程对主存的修改时,这个线程的读取原子操作已经结束,进入下一个原子操作了,最终造成重复写
一个变量i被volatile修饰,两个线程想对这个变量修改,都对其进行自增操作也就是i++,i++的过程可以分为三步,首先获取i的值,其次对i的值进行加1,最后将得到的新值写会到缓存中。
线程A首先得到了i的初始值100,但是还没来得及修改,就阻塞了,这时线程B开始了,它也得到了i的值,由于i的值未被修改,即使是被volatile修饰,主存的变量还没变化,那么线程B得到的值也是100,之后对其进行加1操作,得到101后,将新值写入到缓存中,再刷入主存中。根据可见性的原则,这个主存的值可以被其他线程可见。
问题来了,线程A已经读取到了i的值为100,也就是说读取的这个原子操作已经结束了,所以这个可见性来的有点晚,线程A阻塞结束后,继续将100这个值加1,得到101,再将值写到缓存,最后刷入主存,所以即便是volatile具有可见性,也不能保证对它修饰的变量具有原子性。
解决方法:
1)对该方法加锁
2)使用concurrent包中的AtomicInteger类
原子操作做完了,其他线程才能读
public void addato() {
atomicInteger.getAndIncrement();
}
AtomicInteger atomicInteger = new AtomicInteger();
volatile禁止指令重排
volatile可以保证有序性,也就是防止指令重排序。所谓指令重排序,就是出于优化考虑,CPU执行指令的顺序跟程序员自己编写的顺序不一致。就好比一份试卷,题号是老师规定的,是程序员规定的,但是考生(CPU)可以先做选择,也可以先做填空。
源代码--编译器优化的重排--质量并行的重排--内存系统的重排--最终执行指令
单线程:代码顺序执行结果和程序最终结果一样
多线程:线程交替执行,由于编译器优化重排(考虑数据依赖性)的存在,两个线程使用的变量能否保证一致无法确定
- 数据依赖性
int x = 11; //语句1
int y = 12; //语句2
x = x + 5; //语句3
y = x * x; //语句4
以上例子,可能出现的执行顺序有1234、2134、1342,这三个都没有问题,最终结果都是x = 16,y=256。因为数据依赖性所以3和4不可能第一位执行,因为还没有声明
- 指令重排
class ResortSeqDemo {
int a=0;
boolean flag=false;
/*
多线程下flag=true可能先执行,还没走到a=1就被挂起。
其它线程进入method02的判断,修改a的值=5,而不是6。
*/
public void method01(){
a=1;
flag=true;
}
public void method02(){
if (flag){
a+=5;
System.out.println("*****retValue: "+a);
//多线程可以是6 5
}
}
}
这里提一下挂起:进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态,系统在超过一定的时间没有任何动作.
volatile底层是用CPU的内存屏障(Memory Barrier)指令来实现的,有两个作用,一个是保证特定操作的顺序性,二是保证变量的可见性——其实就是volatile具有的两个特性。在指令之间插入一条Memory Barrier指令,告诉编译器和CPU,在Memory Barrier指令之间的指令不能被重排序。
- 总结:
1) 工作内存和主内存同步延迟现象导致的可见性问题——synchronized和volatile解决,一个线程修改变量后立刻对其他线程可见
2)对指令重排导致的可见性问题和有序性问题,可以用volatile解决——禁止重排序优化
单例模式
回顾单例模式:
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName() + "\t 我是构造方法");
}
public static SingletonDemo getInstance(){
if(instance == null){
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//main 我是构造方法
//true true true
}
}
懒汉模式:调用getInstance时候才创建对象
饿汉模式:开始就创建了对象
多线程下的安全问题:
for (int i = 0; i < 11; i++) {
new Thread(() ->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
// 0 我是构造方法
// 2 我是构造方法
// 1 我是构造方法
- 单例模式下的volatile
synchronizated大材小用,而且锁死整个函数;
DCL模式:double check lock双端检索机制
加锁前后都检索
if(instance == null){
synchronized (SingletonDemo.class){
if (instance == null){
instance = new SingletonDemo();
}
}
}
可能会出现异常,因为new分为三步:
1)分配内存空间
2)初始化对象
3)设置instance指向刚分配的内存地址,此时不等于null了
2和3不存在数据依赖,所以指令重排可以导
致1,3,2这时候对象还没初始化但instance!=null了
一条线程访问不为空,但此时instance未必已经完成初始化,
return会错误,线程安全问题
解决方法:
双端检索+volatile
private static volatile SingletonDemo instance = null;
CAS(compare and swap)
定义:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
//expect和主存值要一样,才写回修改
System.out.println(atomicInteger.compareAndSet(5, 5000) + "\t curr" +
"ent data: " + atomicInteger.get());
// true current data: 5000
System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t curr" +
"ent data: " + atomicInteger.get());
// false current data: 5000
}
底层原理,unsafe的理解:
- 进入AtomicInteger类
三个重要的成员变量:
1)private static final Unsafe unsafe = Unsafe.getUnsafe();
unsafe类保证原子性:通过native访问底层,直接操作特定的
内存数据,类似指针操作内存
2)static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
valueOffset表示该变量在内存中的偏移地址,Unsafe根据它来
获取数据
3)private volatile int value;
volatile保证有序性和可见性
CAS:一条cpu并发原语,判断内存中某个位置是否为预期值,
如果是就更改为更新的值,这个过程是原子的,unsafe类中的CAS方法
this表示当前对象;valueoffset是内存偏移量(内存地址)
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
进入unsafe类:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//var2地址的值--var5
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//再读一次当前地址值,和var5比较,那么加
//1,成功就跳出循环了,失败就继续比较,直到成功
return var5;
}
CAS就是一条cpu原语,不会被打断。由unsafe类底层实现,compareAndSwapInt是要给native方法,用到了底层汇编
-
简单小总结:unsafe类:
var1:当前对象
var2:value的地址
var4:需要变动的数量
var5:用var1和var2找到主存中对应的值
再读一次内存,和开始保存的var5比较:
相同:修改写回主存
不同:继续循环,直到两者相同,完成更新 -
所谓为什么AutomicInteger用CAS而不用同步锁?
因为同步的保证了一致性但是并发性下降,而CAS没有加锁,只是反复CAS循环比较,既保证了一致性,又保证了并发性
应用底层.png
unsafe类+CAS思想(自旋)
CAS的缺点:
1)循环时间长(do-while),开销大(CPU开销大,如果多个线程都在自旋)
2)只能保证一个共享变量的原子操作
3)ABA问题:
ABA问题:
CAS--UnSafe--底层--ABA--原子引用更新--如何规避ABA问题
-
介绍
比如主存是A、两个速度不一样的线程,快的改了两次B-A,而慢的线程虽然写回时还是A,但其实主存已经变动过了
示例.png
文字表述.png - AtomicReference原子引用:对自定义类进行原子包装
User z3 = new User("z3", 22);
User z4 = new User("z4", 25);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, z4) +"" +
"\t" + atomicReference.get().toString());
- AtomicStampedReference时间戳原子引用解决ABA问题
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+
"\t第一次版本号" + atomicStampedReference.getStamp());
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第一次版本号" + atomicStampedReference.getStamp());
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第2次版本号" + atomicStampedReference.getStamp() + "\t修改成功吗?"+ result);
System.out.println(Thread.currentThread().getName()+ "\t最新值为:" + atomicStampedReference.getReference());
}, "t4").start();
// t3 第一次版本号1
// t4 第一次版本号1
// t3 第2次版本号2
// t3 第3次版本号3
// t4 第2次版本号3 修改成功吗?false
// t4 最新值为:100
}
- //要求期望值和内存值相同,并且时间戳和期望时间戳相同才能修改。
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp,stamp+1);
四 集合类的不安全问题
- List(CopyOnWriteArrayList)
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import java.sql.Time;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collector;
/**
* @author : liulinzhi
* @date: 2020/09/03/14:27
* @description:
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i < 5; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
1.异常:java.util.ConcurrentModificationException
2.原因:
并发争抢修改导致;比如一个人正在写,另一个线程抢夺,导致数据不一致
3.解决方案:
1)Vector类add有同步锁,并发性下降
ArrayList并发性上升,但牺牲了多线程安全性
2)Collections.synchronizedList工具类的同步包装
3)CopyOnWriteArrayList<E>
源码:
底层是要给加了个volatile的Object数组:
private transient volatile Object[] array;
不直接在array中添加,而是在一个赋值array添加,最后
将原引用指向新的array。所以可以对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;
//底层array编程newElements了
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
读取操作没有加任何锁
public E get(int index) {
return get(getArray(), index);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return array;
}
4.避免同类问题,优化建议:
读写分离思想,
}
}
如果是多个线程读,则不会阻塞;
-set
public static void main(String[] args) {
// List<String> list = new CopyOnWriteArrayList<>();
// Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
// Set<String> set1 = new CopyOnWriteArraySet<String>();
Set<String> set1 = new HashSet<>();
for (int i = 1; i < 50; i++) {
new Thread(() -> {
set1.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set1);
},String.valueOf(i)).start();
}
Hashset同样不安全
解决:
1.Collections.synchronizedSet(new HashSet<>());
2.CopyOnWriteArraySet,底层用的CopyOnWriteArrayList
HashSet底层就是HashMap,它的add方法,是add的key,value是一个
公用的Object类型成员引用 PRESENT
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- Map
Map<String, String> set1 = new ConcurrentHashMap<>();
for (int i = 1; i < 50; i++) {
new Thread(() -> {
set1.put(UUID.randomUUID().toString().substring(0,8),
UUID.randomUUID().toString().substring(0,8));
System.out.println(set1);
},String.valueOf(i)).start();
}
HashMap:不安全
解决:
1.Collections.synchronizedMap
2.ConcurrentHashMap(JUC)
五 值传递和引用传递
public void changeValue(int age){
age = 30;
}
public void changeValue1(Person age){
age.setPersonName("xxx");
}
public void changeValue2(String age){
age = "xxx";
}
public static void main(String[] args) {
TestTransferValue testTransferValue = new TestTransferValue();
int age =20;
testTransferValue.changeValue(age);
System.out.println(age);
rew
栈管内存(方法的堆叠,两个age分别时两个方法栈中的各自变量),堆管存储,
// 值传递,20
Person person = new Person("abc");
testTransferValue.changeValue1(person);
System.out.println(person);
// 引用传递,堆空间修改 xxx
String str = "abc";
testTransferValue.changeValue2(str);
System.out.println(str);
// String对象,常量池中创建xxx,指向局部变量age
}
六 锁
- 公平锁和非公平锁
ReentrantLock(boolean ):true为公平锁,默认非公平锁
公平锁:队列FIFO,多个锁按照申请锁的顺序来获取锁,先来后到;
非公平锁:多个线程获取锁的顺序并不是先来后到的,高并发的情况下
可能造成优先级反转或饥饿现象;
饥饿:“不患寡,而患不均”,如果线程优先级“不均”,在CPU繁忙
的情况下,优先级低的线程得到执行的机会很小,就可能发生线程
“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“
饥饿”问题。
两者的区别:一个按FIFO从等待队列中选择线程获取锁
后者,线程上来就抢占,如果线程抢占失败,就再采用公平锁的那种
方式(派到队尾)
优点:
ReentrantLock默认非公平锁,吞吐量比公平锁大;
Synchronized也是非公平锁
- 可重入锁(递归锁)
可重入锁(也叫递归锁:synchronized ReentrantLock,
可以避免死锁;
同一线程函数获得锁之后,内层递归函数仍然能获取该锁的代码,再
同一个线程在外层获取锁时候,再进入内层方法会自动获取锁
也就是线程可以进入任何一个它已经拥有锁的同步代码块;
比如:两个方法用同一把锁,method1获取锁时候,方法体中访问
method2时候同样获取该锁
syn void method1(){
method2()
}
syn void method2(){
}
/**
* @author : liulinzhi
* @date: 2020/09/08/10:59
* @description:
12 invoked sendSMS Synchronized Lock
12 invoked sendMail 进入内层方法自动获取锁
13 invoked sendSMS
13 invoked sendMail
14 invoked get ReentraitLock
14 invoked set
15 invoked get
15 invoked set
*/
class Phone implements Runnable{
public synchronized void sendSMS() throws Exception{
System.out.println(Thread.currentThread().getId() +
"\t invoked sendSMS");
sendMail();
}
public synchronized void sendMail() throws Exception{
System.out.println(Thread.currentThread().getId() +
"\t invoked sendMail");
}
Lock lock = new ReentrantLock();
public void get() throws Exception{
lock.lock();
try {
System.out.println(Thread.currentThread().getId() +
"\t invoked get");
set();
}finally {
lock.unlock();
}
}
public void set() throws Exception{
lock.lock();
try {
System.out.println(Thread.currentThread().getId() +
"\t invoked set");
}finally {
lock.unlock();
}
}
@Override
public void run() {
try {
get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) throws InterruptedException {
// Lock this
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();
TimeUnit.SECONDS.sleep(1);
Thread t3 = new Thread(phone);
Thread t4 = new Thread(phone);
t3.start();
t4.start();
}
}
锁匹配(lock,unlock),几把锁都可以正常运行,
语法编译通过,运行正常;
public void get() throws Exception{
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getId() +
"\t invoked get");
set();
}finally {
lock.unlock();
lock.unlock();
}
}
- 自旋锁
unsafe+CAS:尝试获取锁得线程不会立即阻塞,而是尝试循环得方式持续获取锁
好处:减少线程上下文切换得消耗
缺点:消耗CPU
atomic
进入unsafe类:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//var2地址的值--var5
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//再读一次当前地址值,和var5比较,那么加
//1,成功就跳出循环了,失败就继续比较,直到成功
return var5;
}
验证
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t my current 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()");
}
public static void main(String[] args) {
SpinLockDemo sld = new SpinLockDemo();
new Thread(()->{
sld.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
sld.myUnlock();
},"AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
sld.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
sld.myUnlock();
},"BB").start();
// AA my current come in
// BB my current come in
// AA invoked myUnlock()
// BB invoked myUnlock()
}
- (共享锁)读(独占锁)写锁理论(互斥锁)
独占锁:该锁一次只能被一个线程所持有,ReentrantLock和Synchronized都是独占锁;
共享锁:可被多个线程持有,ReentrantReadWriteLock读锁是共享锁,写锁是独占(写的时候共享读);
/**
* @author : liulinzhi
* @date: 2020/09/09/10:12
* @description: 多个线程同时读一个资源没问题,为了满足并发量,读取共享
* 资源应该可以同时进行,但是如果有一个线程想去写共享资源
* ,就不应该有其他线程可以对该资源读写
* 总结:
* 读-读 可以共存
* 写-写 互斥
* 读-写 互斥
* <p>
* 第一个例子没有加锁:
* 写操作:原子+独占,这个过程必须是一个完整得统一体,中间
* 不许被分割,被打断;
*/
class MyCache {
//保证缓存可见性
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock rwlock = new ReentrantReadWriteLock();
public void put(String key, Object value) throws InterruptedException {
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) throws InterruptedException {
rwlock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取 ");
//模拟网络临时拥堵
TimeUnit.MILLISECONDS.sleep(300);
Object val = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成 " + val);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwlock.readLock().unlock();
}
}
}
/**
* 加了ReentrantReadWriteLock之后保证了读共享和写互斥
0 正在写入 0
0 写入完成
4 正在写入 4
4 写入完成
1 正在写入 1
1 写入完成
3 正在写入 3
3 写入完成
2 正在写入 2
2 写入完成
0 正在读取
1 正在读取
2 正在读取
3 正在读取
4 正在读取
3 读取完成 3
1 读取完成 1
0 读取完成 0
2 读取完成 2
4 读取完成 4
**/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache mycache = new MyCache();
for (int i = 0; i < 5; i++) {
final int tempInt = i;
new Thread(() -> {
try {
mycache.put(tempInt + "", tempInt + "");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int tempInt = i;
new Thread(() -> {
try {
mycache.get(tempInt + "");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
这个例子更能体现处读写锁ReentrantReadWriteLock的性质:
t1+t2---可以在对方解锁前进入read--读-读共享
t2+t3---必须在对方unlock后才可以进入--读--写互斥
t3+t4---必须在对方unlock后才可以进入write--写--写互斥
public class RWRLTest {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
public void read(){
try {
readLock.lock();
System.out.println("线程"+Thread.currentThread().getName()+"进入。。。");
Thread.sleep(3000);
System.out.println("线程"+Thread.currentThread().getName()+"退出。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
readLock.unlock();
}
}
public void write(){
try {
writeLock.lock();
System.out.println("线程"+Thread.currentThread().getName()+"进入。。。");
Thread.sleep(3000);
System.out.println("线程"+Thread.currentThread().getName()+"退出。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
writeLock.unlock();
}
}
public static void main(String[] args) {
final RWRLTest wr = new RWRLTest();
Thread t1 = new Thread(new Runnable() {
public void run() {
wr.read();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
wr.read();
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
public void run() {
wr.write();
}
}, "t3");
Thread t4 = new Thread(new Runnable() {
public void run() {
wr.write();
}
}, "t4");
// t1.start();
t2.start();
t3.start();
//t4.start();
}
- CountDownLatch、CyclicBarrier、Semaphore的使用:
CountDownLatch:计数为0后,方可继续执行;
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(6);
for (int i = 1; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 离开教室");
cdl.countDown();
},String.valueOf(i)).start();
}
cdl.await();//reach zero 才能继续执行
//如何让main线程最后
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 retMsg;
CountryEnum(Integer retCode, String retMsg) {
this.retCode = retCode;
this.retMsg = retMsg;
}
public Integer getRetCode() {
return retCode;
}
public void setRetCode(Integer retCode) {
this.retCode = retCode;
}
public String getRetMsg() {
return retMsg;
}
public void setRetMsg(String retMsg) {
this.retMsg = retMsg;
}
public static CountryEnum forEach_CountryEnum(int index){
CountryEnum[] values = CountryEnum.values();
for(CountryEnum element : values){
if(index == element.getRetCode()){
return element;
}
}
return null;
}
}
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(6);
for (int i = 1; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 国灭");
cdl.countDown();
},CountryEnum.forEach_CountryEnum(i).getRetMsg()).start();
}
cdl.await();//reach zero 才能继续执行
//如何让main线程最后
System.out.println(Thread.currentThread().getName() + "\t 统一");
}
CyclicBarrier:“人到齐才可以开会”,所有线程到达屏障才可以继续。
比如7个线程都到才解除阻塞
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("****");
});
for (int i = 1; i < 8; 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();
}
},String.valueOf(i)).start();
}
Semaphore:“争车位”,用途:
1)多个共享资源互斥使用
2)并发线程数量的控制
public static void main(String[] args) {
//3个停车位
// 1 抢到车位
// 4 抢到车位
// 2 抢到车位
// 2 3s后离开车位
// 1 3s后离开车位
// 4 3s后离开车位
// 3 抢到车位
// 5 抢到车位
// 3 3s后离开车位
// 5 3s后离开车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i < 6; i++) {
final int tempInt = i;
new Thread(() ->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() +
"\t 抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +
"\t 3s后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
//释放
}
},String.valueOf(i)).start();
}
}
阻塞队列
线程池的底层就是用三个红色的;
方法类型 | 抛出异常 | 返回布尔 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(E e) | offer(E e) | put(E e) | offer(E e,Time,TimeUnit) |
取出 | remove() | poll() | take() | poll(Time,TimeUnit) |
队首 | element() | peek() | 无 | 无 |
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> bq = new ArrayBlockingQueue<>(3);
//1.抛出异常,add remove element
//如果队满插入和队空删除会直接报错,一言不合就抛出异常
System.out.println(bq.add("a"));
System.out.println(bq.add("b"));
System.out.println(bq.add("c"));
System.out.println(bq.element());
//IllegalStateException: Queue full
// System.out.println(bq.add("d"));
System.out.println(bq.remove());
System.out.println(bq.remove("c"));
System.out.println(bq.element());
// true
// true
// true
// a
// a
// true
// b
//2.返回布尔值组:offer poll peek
//不抛异常,返回false(插入失败) null(取失败)
BlockingQueue<String> bq1 = new ArrayBlockingQueue<>(3);
System.out.println("--------------");
System.out.println(bq1.offer("a"));
System.out.println(bq1.offer("b"));
System.out.println(bq1.offer("c"));
System.out.println(bq1.offer("d"));
System.out.println(bq1.peek());
// true
// true
// true
// false
// a
System.out.println(bq1.poll());
System.out.println(bq1.poll());
System.out.println(bq1.poll());
System.out.println(bq1.poll());
// a
// b
// c
// null
//3.阻塞和超时控制,
BlockingQueue<String> bq2 = new ArrayBlockingQueue<>(3);
System.out.println("--------------");
bq2.put("a");
bq2.put("a");
bq2.put("a");
System.out.println("---");
//线程阻塞,不结束
// bq2.put("a");
bq2.take();
bq2.take();
bq2.take();
System.out.println("---");
// bq2.take();
//超时控制
BlockingQueue<String> bq3 = new ArrayBlockingQueue<>(3);
System.out.println("--------------");
System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
//阻塞2秒
System.out.println(bq3.offer("a", 2L, TimeUnit.SECONDS));
}