JDK多线程核心原理
多线程原理基础
java程序运行原理分析
图片参考:https://www.processon.com/view/link/5e586f5fe4b069f82a16e8a0
线程状态
package com.xbin;
public class ThreadTest {
private static final Object object=new Object();
private static final Object object2=new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("t1 NEW 状态:"+t1.getState());
t1.start();
System.out.println("t1 RUNNABLE 状态:"+t1.getState());
Thread.sleep(3000);
System.out.println("t1 TIMED_WAITING 状态:"+t1.getState());
Thread.sleep(3000);
System.out.println("t1 TERMINATED 状态:"+t1.getState());
Thread t2=new Thread(()->{
synchronized (object){
while (true){
}
}
});
Thread t3=new Thread(()->{
synchronized (object){
}
});
t2.start();
Thread.sleep(200);
t3.start();
Thread.sleep(200);
System.out.println("t3 BLOCKED 状态"+t3.getState());
Thread t4=new Thread(()->{
synchronized (object2){
try {
object2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t5=new Thread(()->{
synchronized (object2){
}
});
t4.start();
Thread.sleep(200);
t5.start();
System.out.println("t4 WAITING 状态 "+t4.getState());
}
}
如何正确让线程中止
- Thread.stop() 方法(已过时)
package com.xbin;
public class ThreadStop extends Thread {
private int i ,j;
@Override
public void run() {
//添加 synchronized 保证原子操作
synchronized (this){
i++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
j++;
}
}
public void print(){
System.out.println("i="+i+",j="+j);
}
}
package com.xbin;
public class ThreadStop1 {
public static void main(String[] args) throws InterruptedException {
ThreadStop threadStop=new ThreadStop();
threadStop.start();
Thread.sleep(200);
//会破坏原子性操作
threadStop.stop();
// threadStop.interrupt();
while (threadStop.isAlive()){
}
threadStop.print();
}
}
从运行结果可以看出这已经破坏了操作的原子性
- Thread.interrupt()中止线程
package com.xbin;
/**
*使用 interrupt 中止线程
*/
public class ThreadStop2 {
public static void main(String[] args) throws InterruptedException {
ThreadStop threadStop=new ThreadStop();
threadStop.start();
Thread.sleep(200);
//会破坏原子性操作
// threadStop.stop();
threadStop.interrupt();
while (threadStop.isAlive()){
}
threadStop.print();
}
}
从结果可以看出这不会破坏操作的原子性
- 标志位中止线程
package com.xbin;
/**
* 标志位中止线程
*/
public class ThreadStop3 {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
while (flag) {
System.out.println("正在运行------");
Thread.sleep(300);
}
System.out.println("结束运行————————————");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
Thread.sleep(3000);
flag=false;
}
}
**小结:**一般都会使用标识中止线程,有利用程序编写一些事后处理工作。
内存屏障和CPU缓存
-
缓存
L1 Cache 是CPU第一层高速缓存,分为数据缓存和指令缓存。一般服务器CPU的L1缓存的容量通常在32–4096kb.L2 Cache 由于L1级高速缓存容量的限制,为了再次提高CPU的运算速度,在CPU外部放置一高速存储器,即二级缓存。
L3 Cache 现在的都是内置的。而它的实际作用即是。L3缓存的应用可以通过进一步的降低内存延迟,同时提升大数据量计算时处理器的性能。具有较大L3缓存的处理器提供更有效的文件系统缓存行为及较短消息和处理器队列长度。一般是多核共享一个L3缓存
-
缓存同步协议
在这种高速缓存回写的场景下,有一个缓存一致性协议多数CPU厂商对它进行了实现。
MESI协议 ,它规定每条缓存有个状态位,同时定义了下面四个状态:
修改态 此cache行已被修改过,内存不同于主存,为此cache专有。
专有态 此cache行内容同于主存,但不出现于其它cache中。
共享态 此cache行内容同于主存,但也出现于其它cache中。
无效态 此cache行内容无效。
多处理器时,单个CPU对缓存中数据进行了改动,需要通知给其他CPU。
CPU处理要控制自己的读写操作,还要监听其他CPU发出的通知,从而保证最终一致。
- 运行时指令重排
指令重排的场景:当CPU写缓存时发现缓存区块正被其它CPU占用,为了提高CPU处理性能,可能将后面的读缓存命令优先执行。
并非随便重排,需要遵守as-if-serial语义
as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime和处理器都必须遵守as-if-serial语义。
编译器和处理器不会对存在数据依赖关系的操作做重排序。
-
缓存和指令重排引起的两个问题。
CPU高速缓存问题:缓存中数据和主内存的数据并不是实时同步的,各CPU间缓存的数据也不是实时同步。同一个时间点,各CPU所看到同一内存地址的数据的值可能是不一致的。
CPU执行指令重排序问题:虽然遵守了as-if-serial语义,单仅在单CPU自己执行的情况下能保证结果正确。
多核多线程中,指令逻辑无法分辨因果关联,可能出现乱序执行,导致程序运行结果错误。 -
内存屏障
处理器提供了两个内存屏障指令用于解决上述两个问题。
写内存屏障;在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其线程可见。强制写入主内存,这种显示调用,CPU就不会因为性能考虑而去对指令重排。
读内存屏障(Load Memory Barrier):在指令前插入Load Barrier,可以让高速缓存中数据失效,强制从新的主内存加载数据。强制读取主内存内容,让CPU缓存与主内存保持一致,避免了缓存导致的一致性问题。
线程通信及控制
- 文件共享
package com.xbin;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 文件共享
*/
public class FileSharding {
public static void main(String[] args) {
Thread write = new Thread(() -> {
try {
while (true) {
Files.write(Paths.get("log.txt"), ("当前时间" + System.currentTimeMillis()).getBytes());
Thread.sleep(1000L);
}
} catch (Exception e) {
e.printStackTrace();
}
});
write.start();
Thread read = new Thread(() -> {
try {
while (true) {
Thread.sleep(1000L);
byte[] bytes = Files.readAllBytes(Paths.get("log.txt"));
System.out.println(new String(bytes));
}
} catch (Exception e) {
}
});
read.start();
}
}
- 变量共享
package com.xbin;
/**
* 变量共享
*/
public class VariableSharding {
private static String context="";
public static void main(String[] args) {
Thread write =new Thread(()->{
try {
while (true){
context="当前时间:"+System.currentTimeMillis();
Thread.sleep(1000L);
}
}catch (Exception e){
e.printStackTrace();
}
});
write.start();
Thread read =new Thread(()->{
try {
while (true){
Thread.sleep(1000L);
System.out.println(context);
}
}catch (Exception e){
e.printStackTrace();
}
});
read.start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LsecR2m-1591524544496)(https://upload-images.jianshu.io/upload_images/22932958-0b7155eac6cc06e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 线程协作
JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。
多线程协作的典型场景是:生产者,消费者模型。(线程阻塞,线程唤醒)
suspend和resume的使用
package com.xbin;
public class SuspendResumeTest {
private Object object=null;
/**
* 方法的调用顺序。(如果先调用resume,在调用suspend就会引起死锁)
*/
public void suspendResumeTest03() {
Thread t1 = new Thread(() -> {
while (object == null) {
System.out.println("1.等待包子生产");
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.currentThread().suspend();
}
System.out.println("3.买到包子了");
});
t1.start();
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object = new Object();
t1.resume();
System.out.println("2.生产包子");
}
/**
* 对象锁操作 --会导致死锁 --suspend()不会释放锁
*/
public void suspendResumeTest02() {
Thread t1 = new Thread(() -> {
while (object == null) {
synchronized (this) {
System.out.println("1.等待包子生产");
Thread.currentThread().suspend();
}
}
System.out.println("3.买到包子了");
});
t1.start();
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object = new Object();
synchronized (this) {
t1.resume();
}
System.out.println("2.生产包子");
}
/**
* suspend 和 resume 正常演示
*/
public void suspendResumeTest() {
Thread t1 = new Thread(() -> {
while (object == null) {
System.out.println("1.等待包子生产");
Thread.currentThread().suspend();
}
System.out.println("3.买到包子了");
});
t1.start();
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object = new Object();
t1.resume();
System.out.println("2.生产包子");
}
public static void main(String[] args) {
SuspendResumeTest suspendResumeTest=new SuspendResumeTest();
// suspendResumeTest.suspendResumeTest03();
// suspendResumeTest.suspendResumeTest02();
suspendResumeTest.suspendResumeTest();
}
}
wait/notify的使用
wait/notify:方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出IllegalMonitorStateException异常。
wait方法会导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
注意:虽然wait会自动解锁,但是对顺序有要求,如果在notify被调用之后,才开始wait方法的调用,线程就会永远处于WAITING状态。
package com.xbin;
public class WaitNotifyTest {
private Object object=null;
/**
* 虽然wait() 和notify() 会释放锁,但是有先后顺序
*/
public void waitNotifyTest02(){
new Thread(() -> {
while (object == null) {
System.out.println("1.等待包子生产");
try {
Thread.sleep(3000L);
synchronized (this) {
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("3.成功买到包子");
}).start();
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object=new Object();
synchronized (this){
this.notify();
}
System.out.println("2.包子生产完成");
}
/**
* 正确地处理方式
*/
public void watiNotifyTest() {
new Thread(() -> {
while (object == null) {
System.out.println("1.等待包子生产");
try {
synchronized (this) {
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("3.成功买到包子");
}).start();
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object=new Object();
synchronized (this){
this.notify();
}
System.out.println("2.包子生产完成");
}
public static void main(String[] args) {
WaitNotifyTest waitNotifyTest=new WaitNotifyTest();
waitNotifyTest.waitNotifyTest02();
// waitNotifyTest.watiNotifyTest();
}
}
pack/unpack机制的使用
pack/unpack机制:线程调用park则等待“许可”,unpack方法为指定线程提供“许可”。
不要求park和unpark方法的调用顺序,多次调用unpark之后,再调用park,线程会直接运行,但不会叠加。
也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。
park方法不会释放锁,也可能会引起死锁操作。
package com.xbin;
import java.util.concurrent.locks.LockSupport;
public class ParkUnParkTest {
private Object object=null;
/**
* 会产生死锁 parK() 方法不会释放锁
*/
public void parkUnpackTest02(){
Thread t1=new Thread(()->{
while (object==null){
System.out.println("1.等待包子生产");
synchronized (this){
LockSupport.park();
}
}
System.out.println("3.成功买到包子");
});
t1.start();
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object=new Object();
synchronized (this){
LockSupport.unpark(t1);
}
System.out.println("2.店家成功生产包子");
}
/**
* park() 和 unpacke() 对调用的方法的先后顺序没有问题。
*/
public void parkUnpackTest(){
Thread t1=new Thread(()->{
while (object==null){
System.out.println("1.等待包子生产");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park();
}
System.out.println("3.成功买到包子");
});
t1.start();
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
object=new Object();
LockSupport.unpark(t1);
System.out.println("2.店家成功生产包子");
}
public static void main(String[] args) {
ParkUnParkTest parkUnParkTest=new ParkUnParkTest();
// parkUnParkTest.parkUnpackTest02();
parkUnParkTest.parkUnpackTest();
}
}
伪唤醒
警告!之前的代码中如果用把while改成if语句来判断是否进入等待状态是错误的
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,
如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
伪唤醒是指线程并非因为notify,notifyall,unpark等api调用而唤醒,是更底层原因导致的。
线程封闭之Threadlocal和栈封闭
-
线程封闭的概念
多线程访问共享可变数据时,涉及到线程间数据同步的问题。并不是所有时候,都要用到共享数据,所以线程封闭概念就提出来了。
数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭。
线程封闭具体的体现有:ThreadLocal,局部变量 -
ThreadLocal
ThreadLocal是java里一种特殊的变量。
它是一个线程级别变量,每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
用法:ThreadLocal local=new ThreadLocal();
会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做饭。(线程中控制同一数据库连接对象就是使用ThreadLocal)
实在难以理解的,可以理解未,JVM维护了一个Map<Thread,T>,每个线程要用这个T的时候,用当前的线程去Map里面取。仅作为一个概念理解 -
栈封闭
局部变量的固有属性之一就是封闭在线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。
线程池及实现原理剖析
######线程池的概念原理
1 线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
2 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3 任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4 任务队列:用于存放没有处理的任务。提供一种缓存机制。
ExecutorService–API
方法名称 | 方法描述 |
---|---|
awaitTermination(long timeout,TimeUnit unit) | ExecutorService是否已经关闭,直到所有任务完成执行,或超时发生,或当前线程被中断 |
invokeAll(Collection<? extends Callable> tasks) | 执行给定的任务集合,执行完毕后,放回结果 |
invokeAll(Collection<? extends Callable> tasks,long timeout,TimeUnit unit) | 执行给定的任务集合,执行完毕或者超时后,放回结果,其它任务终止 |
invokeAny(Collection<? extends Callable>tasks ) | 执行给定的任务,任意一个执行成功则返回结果,其他任务终止 |
invokeAny(Collection<? extends Callable>tasks,long timeout,TimeUnit unit) | 执行给定的任务,任意一个执行成功或者超时后,则返回结果,其他任务终止 |
isShudown() | 如果此线程池已关闭,则返回true |
isTerminated() | 如果关闭后所有任务都已完成,则返回true |
shutdown() | 优雅关闭线程池,之前提交的任务将被执行,但是不会接受新的任务。 |
shutdownNow() | 尝试停止所有正在执行的任务,停止等待任务的处理,并放回等待执行任务的列表 |
submit(Callable task) | 提交一个用于执行的Callable返回任务,并放回一个Future,用于获取Callable执行结果 |
submit(Runnable task) | 提交可运行任务以执行,并放回一个Future对象,执行结果未null |
submit(Runnable task,T result) | 提交可运行任务以执行,并返回Future,执行结果为传入的result |
ScheduledExecutorService --API
方法名称 | 方法描述 |
---|---|
schedule(Callable callable,long delay,TimeUnit unit) | 创建并执行一个一次性任务 |
chedule(Runnable command,long delay,TimeUnit unit) | 创建并执行一个一次性任务 |
scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit | 创建并执行一个周期性任务过了给定的初始延迟时间,会第一次被执行,执行过程中发生了异常,那么任务就终止 。(一次任务执行时长超过了周期时间,下一次任务会等到该次任务执行结束后,立即执行,这就是它和scheduleWithFixedDelay的重要区别) |
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) | 创建并执行一个周期性任务过了给定的初始延迟时间,会第一次被执行,执行过程中发生了异常,那么任务就终止 。(一次任务执行时长超过了周期时间,下一次任务会在该次任务执行结束的时间基础上,计算执行延迟,对于超过周期的长时间处理任务的不同处理方式,这是它和scheduleAtFixedRate的重要区别) |
Executors工具类 --API
方法名称 | 方法描述 |
---|---|
newFixedThreadPool(int nThreads) | 创建一个固定大小,任务队列容量无界的线程池。核心线程数=最大线程数。 |
newCachedThreadPool() | 创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化,适用于执行耗时较小的异步任务。池的核心线程数=0,最大线程数=Integer.MAX_VALUE |
newsingleThreadExecutor() | 只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务按加入的顺序一个一个依次执行,当唯一的线程因任务异常终止时,将创建一个新的线程来继续执行后续的任务。与newFixedThreadPool(1)的区别在于,单一线程池的池大小在newSingleThreadExecutor方法中硬编码,不能再改变 |
newScheduledThreadPool(int corePoolSize) | 能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数=Integer.MAX_VALUE |
任务execute过程
- 是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
- 工作队列是否已满?没满,则将新提交的任务存储在工作队列里。
- 是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。
- 最后,执行拒绝策略来处理这个任务。
如何确定线程池中线程的数量
- 计算型任务:cup数量的1-2倍
- IO型任务:相对计算型任务,需多一些线程,需根据具体的IO阻塞时长进行考量决定。
如果Tomcat中默认的最大线程数为:200.也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。
从本质去了解及线程安全
可见性问题
JAVA内存模型定义
前面章节中的大部分讨论仅涉及代码得行为,即一次执行单个语句或表达式,即通过单个线程来执行。 JAVA虚拟机可以同时支持多个执行线程,若未正确同步线程的行为,线程的行为可能会出现混淆和违反直觉。
本章描述了多线程程序的语义;它包含了,当多个线程修改了共享内存中的值时,应该读取到哪个值得规则。由于这部分规范类似于不同硬件体系结构的内存模型,因此这些语义称为java编程语言内存模型。
这些语言没有规定如何执行多线程程序。相反,它们描述了允许多线程程序的合法行为。
----《Java语言规范》
多线程中的问题
- 所见非所得
- 无法肉眼去检测程序的准确性
- 不同的运行平台有不同的表现
- 错误很难重现
package com.xbin;
/**
* 线程安全-可见性问题
*/
public class ThreadSafeDemo {
private static boolean flag=false;
private static int i=0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (!flag){
i++;
}
System.out.println(i);
}).start();
Thread.sleep(3000L);
flag=true;
System.out.println("shutdown");
}
}
**结论:**不同的环境产生的结果就不同
上述代码运行的内存分析图
上述程序不i输出i的值得原因,虽然和高速缓存没有关系,但高速缓存会在很短的时间内引起可见性的问题,在一些极限的场景需要注意;产生问题的主要原因跟JIT有关。
CPU指令重排
java编程语言的语义允许java编译器和微处理器进行执行优化,这些优化导致了与其交互的代码不再同步,从而导致看似矛盾的行为。
JIT编译器(Just In Time Compiler)
脚本语言与编译语言的区别
- 解释执行:即咱们说的脚本,在执行时,由语言的解释器将其一条条编译成机器可识别的指令。
- 编译执行:将我们编写的程序,直接编译成机器可以识别的指令吗。
下面分析JIT编译器对上述代码的行为
上述问题是JIT对重复执行的代码进行优化产生的问题,解决上述的问题是在flag添加volatile关键字修饰。
volatile关键字
可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。
Java内存模型规定:
对volatile变量v的写入,与所有其他线程后续对v的读同步
要满足这些条件,所以volatiel关键字就有下面两个功能。
- 禁止缓存
volatile变量的访问控制符会加个ACC_VOLATILE
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6U4g0yFO-1591524544549)(https://upload-images.jianshu.io/upload_images/22932958-9475b32c28d417ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 对volatile变量相关的指令不做重排序。
共享变量(Shared Variables)定义
可以在线程之间共享的内存称为共享内存或堆内存。
所有实例字段,静态字段和数组元素都存储在堆内存中,这些字段和数组都是标题中提到的共享变量。
冲突:如果至少有一个访问是写操作,那么对同一个变量的两次访问是冲突的。
这些能被多个线程访问的共享变量是内存模型规范的对象。
线程间操作的定义
- 线程间操作值:一个程序执行的操作可被其他线程感知或被其他线程直接影响。
- Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按照线程内语义执行。
线程间操作有:
-
read操作(一般读,即非volatile读)
-
write操作(一般写,即非volatile写)
-
volatile read
-
volatile write
-
Lock(锁monitor),Unlock
-
线程的第一个和最后一个操作
-
外部操作
所以线程间操作,都存在可见性问题,JMM需要对其进行规范
对于同步的规则定义
- 对volatile变量v的写入,与所有其他线程后续对v的读同步
- 对于监视器m的解锁与所有后续操作对于m的加锁同步
- 对于每个属性写入默认值(0,false,null)与每个线程对其进行的操作同步
- 启动线程的操作与线程中的第一个操作同步
- 线程T2的最后操作与线程T1发现线程T2已经结束同步。(isAlive,join可以判断线程是否终结)
- 如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted
final关键字在JMM中的处理
- final在该对象的构造函数中设置对象的字段,当线程看到该对象时,将始终看到该对象的final字段的正确构造版本。伪代码示例:f=new FinalDemo();读取到的f.x一定最新,x为final字段。
- 如果在构造函数中设置字段后发生读取,则会看到该final字段分配的值,否则它将看到默认值;伪代码示例:public finalDemo(){x=1;y=x;}; y会等于1;
- 读取该共享对象的final成员变量之前,先要读取共享对象。伪代码示例:r=new ReferenceObj(); k=r.f;这两个操作不能重排序。
- 通常被static fianl 修饰的字段,不能被修改。然而System.in,System.out,System.err被static final修饰,却可以修改,遗留问题,必须允许通过set方法改变,我们将这些字段称为写保护,已区别于普通final字段;
原子性
package com.xbin;
/**
* 计数工具类
*/
public class Counter {
int i;
public void add(){
i++;
}
}
package com.xbin;
public class CounterDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
counter.add();
}
}).start();
}
Thread.sleep(5000L);
System.out.println(counter.i);
}
}
上述代码按照正常逻辑执行的结果应该是:10000 ,但结果确实小于10000
产生上述问题的原因是i++ 不是原子操作,通过javap -v -p 操作把.class 文件解析成字节码操作指令。
从第2步到第7步就是i++操作的字节码指令。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPvN4eFg-1591524544596)(https://upload-images.jianshu.io/upload_images/22932958-1bf41bf665f968f3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Tje4El6-1591524544609)(https://upload-images.jianshu.io/upload_images/22932958-99e8cfbeb9354fe9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
分析完这些操作后,就可以很明显的发现问题。在多个线程访问的情况下,就会有多个线程同时操作,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ra7P5qCE-1591524544626)(https://upload-images.jianshu.io/upload_images/22932958-f80b4305c83cffa3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
原子操作
- 原子操作可以是一个步骤,也可以是多个步骤,但是其顺序不可以被打扰,也不可以被切割而只执行其中的一部分(不可中断性)。
- 将整个操作视为一个整体,资源在该次操作中保持一致,这是原子性地核心特征。
原子性问题的场景
- 判断了某种状态后,这个状态失效了
if(owner==null){
owner=currentThread();
}
- 加载了一个值,这个值失效了。列如:i++操作。
原子操作问题的解决方案
- 使用synchronized 添加排他锁(互斥锁)
public class Counter {
int i;
public synchronized void add(){
i++;
}
}
- 使用ReetrantLocak锁。
public class Counter {
int i;
Lock lock=new ReentrantLock();
public synchronized void add(){
lock.lock();
try{
i++;
}finally {
lock.unlock();
}
}
}
- 使用JUC 包下的AtomicInteger进行自增操作
public class Counter {
AtomicInteger i=new AtomicInteger(0);
public synchronized void add(){
i.getAndIncrement();
}
}
CAS(Compare and swap)
- compare and swap 比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。
- CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先对旧值进行比较,若没有发生变化,才交换新值,发生了变化则不交换。
- java中sum,misc,Unsafe类,提供了compareAndSwapInt()和compareAndSwapLong()等几个方法实现CAS
CAS在J.U.C并发编码包下被广泛运用
AtomicInteger的底层使用的就是CAS,首先CAS 是CPU指令级的操作,只有一步原子操作,所以非常快;
package com.xbin.atomic;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* 使用Unsafe 实现i++操作
*/
public class CounterUnsafe {
private static Unsafe unsafe = null;
private static long valueOff;
int i = 0;
static {
//使用反射获取Unsafe对象
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
//计数类属性的偏移量
Field field = CounterUnsafe.class.getDeclaredField("i");
valueOff = unsafe.objectFieldOffset(field);
} catch (Exception e) {
e.printStackTrace();
}
}
public void add() {
try {
while (true) {//自旋
//获取对象属性的值
int intVolatile = unsafe.getIntVolatile(this, valueOff);
boolean b = unsafe.compareAndSwapInt(this, valueOff, intVolatile, intVolatile + 1);
if (b) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.xbin.atomic;
public class CounterDemo {
public static void main(String[] args) throws InterruptedException {
CounterUnsafe counter=new CounterUnsafe();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 10000; j++) {
counter.add();
}
}).start();
}
Thread.sleep(5000L);
System.out.println(counter.i);
}
}
从上述运行结果中可以得到的结论是:使用Unsafe可以实现i++的原子性。
CAS的3个问题
- 循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗。
- 仅针对单个变量的操作,不能拥于多个变量来实现原子操作。
- ABA问题。
ABA问题
- thread1,thread2同时读取到i=0后;
- thread1,thread2都要执行CAS(0,1)操作;
- 假设thread1操作稍之后与thread2,则thread2执行成功;
- thread2紧接着执行CAS(1,0),将i的值改回0;
- thread1也会执行成功;
ABA问题的后果
下图是使用CAS对stack进行操作的过程
- 步骤1 thread1,thread2 中同一时刻读取栈中顶部元素的值A。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Howzkgbv-1591524544655)(https://upload-images.jianshu.io/upload_images/22932958-23b0a6262c1ab38c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 步骤2 thread2对stack进行操作CAS(A,B)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IaIfiWUB-1591524544661)(https://upload-images.jianshu.io/upload_images/22932958-7ee565a1bee2603b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
- 步骤3 thread2进行pop(B)把元素从stack中弹出。
- 步骤4 添加元素到stack中,依次把C,D,A 压入stack中
- 步骤5 thread1进行CAS(A,B),结果就会变成下图所示
我们期望的结果是thread1线程应该是要执行失败的,应该A早就不是当初的A了,对于这种比较不充分情况,可以添加版本号进行比较。
java 锁
- 自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被被成功获取,值到获取到锁才会退出循环。
- 乐观锁:假定没有冲突,在修改数据时如果没有发现数据和之前获取的不一致,则读最新数据,修改后重试修改
- 悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
独享锁(写):给资源加上锁,线程可以修改资源,其他线程不能再加锁;(单写) - 独享锁(写):给资源加上锁,线程可以修改资源,其他线程不能再加锁;(单写)
- 共享锁(读):给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁;(多读)
- 可重入锁,不可重入锁:线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码。
- 公平锁,非公平锁:争抢锁的顺序,如果是按先来后到,则为公平。
synchronized关键字
- 用于实例方法,静态方法时,隐式指定锁对象。
- 用于代码块时,显示指定锁对象
- 锁的作用域:对象锁,类锁,分布式锁
**特性:**可重入,独享,悲观锁
**锁优化:**锁消除(开启锁消除的参数:-XX:+DoescapeAnalysis -XX:+Eliminacatelocks)
锁粗化 JDK做了锁粗化的优化,但我们自己可从代码层面优化
NOTE:synchronized关键字,不仅实现同步,JMM中规定,synchronized要保证可见性(不能够被缓存)。
Mark Word
锁升级过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DoORmt9Q-1591524544688)(https://upload-images.jianshu.io/upload_images/22932958-94ad4b51b6738f80.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
synchronized是JVM提供的锁的一种方式,JVM对其进行一些优化。
J.U.C 并发编程包
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
- AtomicIntegerArray:原子更新整型数组里的元素
- AtomicLongArray:原子更新长整型数组里的元素
- AtoimcReferenceArray:原子更新引用类型数组里的元素
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicReference:原子更新引用类型
- AtomicStampedReference:原子更新带有版本号的引用类型(可用于解决ABA问题)
- AtomicMarkableReference:原子更新带有标记的引用类型
1.8更新
计数器增强版,高并发下性能更好
更新器:DoubleAccumulator,LongAccumulator
计数器:DoubleAdder,LongAdder
原理:分成多个操作单元,不同线程更新不同的单元只有需要汇总的时候才计算所有单元的操作
场景:高并发频繁更新,不太频繁读的取的
Lock接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeggBHRd-1591524544695)(https://upload-images.jianshu.io/upload_images/22932958-83004b047249bc4a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
synchronized VS Lock
synchronized
- 优点 :
使用简单,语义清晰,哪里需要点哪里。
有JVM提供,提供了多种优化方案(锁粗化,锁消除,偏向锁,轻量级锁)
锁的释放由虚拟机来完成,不用人工干预,也降低了死锁的可能性 - 缺点:
无法实现一些锁的高级功能如:公平锁,中断锁,超时锁,读写锁,共享锁等
Lock - 优点:
所有synchronized的缺点
可以实现更多的功能,让synchronized缺点更多 - 缺点:
需手动释放锁unlock,新手使用不当可能死锁
结论:syschronized是卡片机,Lock是单反
ReadWriteLock
概念:维护一对关联锁,一个只用于读操作,一个只用于写操作,读锁可以由多个读线程同时持有,写锁是排他的。同一时间,两把锁不能被不同线程持有。
适用场景:适合读取操作多于写入操作的场景,改进互斥锁的性能,比如:集合的并发线程安全性改造,缓存组件。
锁降级:指的是写锁降级成为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的)
FutureTask
package com.xbin.juc;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
public class MyFutureTask<T> implements Runnable{
private static final int NEW=0;
private static final int RUNABLE=1;
private static final int END=2;
private int state;
private AtomicReference<Thread> ower=new AtomicReference<>(null);
private LinkedBlockingDeque<Thread> water=new LinkedBlockingDeque<>();
T result;
private final Callable<T> callabll;
public MyFutureTask(Callable<T> callabll) {
this.callabll = callabll;
state=NEW;
}
@Override
public void run() {
while (true){
if(state!=NEW||!ower.compareAndSet(null,Thread.currentThread())) return;
try {
state=RUNABLE;
result= callabll.call();
} catch (Exception e) {
e.printStackTrace();
}finally {
state=END;
ower.set(null);
}
while (true) {
Thread thread = water.poll();
if (thread == null) return;
LockSupport.unpark(thread);
}
}
}
public T get(){
if(state!=END){
//等待
water.add(Thread.currentThread());
LockSupport.park();
}
return result;
}
}
package com.xbin.juc;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableImpl callable=new CallableImpl();
MyFutureTask<String> stringFutureTask = new MyFutureTask<>(callable);
new Thread(stringFutureTask).start();
// new Thread(stringFutureTask).start();
System.out.println(stringFutureTask.get());
System.out.println(stringFutureTask.get());
}
static class CallableImpl implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("执行callable");
return "操作成功";
}
}
}
Semaphore
/**
*
*/
public class SemaphoreDemo {
// Semaphore 信号量 限流 共享锁
private static Semaphore semaphore=new Semaphore(6);
public static void main(String[] args) {
for (int i = 0; i <1000 ; i++) {
new Thread(()->{
try {
semaphore.acquire(1);
System.out.println("线程正在执行===============================");
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release(1);
}
}).start();
}
}
}
手写Semaphore
package cn.tk.myproject.lock;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class MySemaphore {
private Sync sync;
public MySemaphore(int permit ) {
this.sync=new Sync(permit);
}
public void acquire(int arg){
sync.acquireShared(arg);
}
public void release(int arg){
sync.releaseShared(arg);
}
class Sync extends AbstractQueuedSynchronizer {
private int permit;
public Sync(int permit) {
this.permit = permit;
}
@Override
protected int tryAcquireShared(int arg) {
for (;;) {
int state = getState();
int next = state + arg;
if (next <= permit) {
//可以获取锁
if (compareAndSetState(state, next)) {
return arg;
}
}
}
}
@Override
protected boolean tryReleaseShared(int arg) {
for (;;){
int state = getState();
if(state==0){
return false;
}
if(compareAndSetState(state,state-arg)){
return true;
}
return false;
}
}
}
}
CountDownLatch
用法1:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch=new CountDownLatch(4);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new Thread(()->{
countDownLatch.countDown();
System.out.println("开始执行代码");
}).start();
}
Thread.sleep(2000L);
countDownLatch.await();
System.err.println("成功执行完毕");
}
}
用法2:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
private static CountDownLatch countDownLatch=new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
System.out.println("准备执行!!");
countDownLatch.await();
System.out.println("开始执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(3000L);
countDownLatch.countDown();
}
}
手写CountDownLatch
package cn.tk.myproject.lock;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/**
* 共享锁,从指定值开始下降
*/
public class MyCountDownLatch {
private final Sync sync;
public MyCountDownLatch(int count) {
this.sync=new Sync(count);
}
public void await(){
sync.acquireShared(1);
}
public void countDown(){
sync.releaseShared(1);
}
/**
* CountDownLatch就可以看做是一个共享锁
* 初始状态,这个共享锁被获取了n次,
* 每次countdown,相当于释放一次锁
* 当锁释放完后,其他线程才能再次获得锁
*/
class Sync extends AbstractQueuedSynchronizer{
public Sync(int count) {
setState(count);
}
@Override
protected int tryAcquireShared(int arg) {
return (getState() == 0) ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int arg) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
}
CyclicBarrier
/**
* 需要线程数达到要求才会被执行
*/
public class CyclicBarrierDemo {
private static CyclicBarrier cyclicBarrier=new CyclicBarrier(4);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
new Thread(()->{
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("正在执行============================");
}).start();
Thread.sleep(300L);
}
}
}
CyclicBarrier源码分析
package cn.tk.myproject.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MyCyclecbarrier {
private ReentrantLock reentrantLock=new ReentrantLock();
private Condition condition=reentrantLock.newCondition();
private int count ;
private final int parties;
private Object gentation=new Object();
public MyCyclecbarrier(int parties) {
this.parties = parties;
this.count=this.parties;
}
public void await(){
final ReentrantLock lock =this.reentrantLock;
lock.lock();
try{
final Object g=gentation;
int index=--count;
if(index==0){
//结束等待
//唤醒等待队列
nextGention();
return;
}
//否则进入等待
for (;;){//防止伪唤醒
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(g!=gentation){
return;
}
}
}finally {
lock.unlock();
}
}
private void nextGention() {
condition.signalAll();
count=parties;//重置数量
gentation=new Object();
}
}