前置知识Java 内存模型中的可见性、原子性和有序性
可见性:
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。
在 Java 中 volatile、synchronized 和 final 实现可见性。
原子性:
原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++;这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等,JDK1.8之后的LonAddr,DoubleAddr提供的分片操作等。
在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。
有序性:
Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU 中。经典的i++问题不是线程安全,
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读。
volatile
保证线程可见性
– MESI
– 缓存一致性协议
禁止指令重排序(CPU)
– DCL单例
– Double Check Lock
-- loadfence 原语指令
-- storefence 原语指令
单例模式-饿汉式
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 缺点:不管用到与否,类加载时就完成实例化,不用的类,加载了干啥
*/
public class VolatileSingle {
private static final VolatileSingle instance = new VolatileSingle();
private VolatileSingle(){
}
private static VolatileSingle getInstance(){
return instance;
}
public static void main(String[] args) {
VolatileSingle single1 = VolatileSingle.getInstance();
VolatileSingle single2 = VolatileSingle.getInstance();
System.out.println(single1 == single2);
}
}
懒汉式
/*
* 懒汉式
* 默认不会实例化,什么时候用什么时候
* 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
*/
public class VolatileSingle1 {
private static VolatileSingle1 instance;
private VolatileSingle1(){
}
private static VolatileSingle1 getInstance(){
if (instance == null){
try {
Thread.sleep(1);
}catch (InterruptedException interruptedException){
interruptedException.printStackTrace();
}
instance = new VolatileSingle1();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0 ; i < 100 ; i++){
new Thread(() ->{
System.out.println(VolatileSingle1.getInstance().hashCode());
}).start();
}
}
}
输出结果:
2068895579
48238505
120111935
875048040
2077358326
95324150
2077358326
1419457248
1641419305
1419457248
1419457248
1944532174
1316704413
2007045553
1641419305
1557134983
1557134983
993945018
176261066
1955884916
2048417961
1526685889
1788262330
786680875
2077358326
2077358326
2077358326
懒汉式 — 方法加锁
/*
* 懒汉式 --方法加锁,
* 默认不会实例化,什么时候用什么时候
* 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
* 虽然可以通过synchronized方法上锁解决,但也带来了效率下降
*/
public class VolatileSingle2 {
private static VolatileSingle2 instance;
private VolatileSingle2(){
}
private static synchronized VolatileSingle2 getInstance(){
if (instance == null){
try {
Thread.sleep(1);
}catch (InterruptedException interruptedException){
interruptedException.printStackTrace();
}
instance = new VolatileSingle2();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0 ; i < 100 ; i++){
new Thread(() ->{
System.out.println(VolatileSingle2.getInstance().hashCode());
}).start();
}
}
}
懒汉式—代码块加锁/**
/*
* 懒汉式 --代码块加锁,
* 默认不会实例化,什么时候用什么时候
* 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
* 虽然可以通过synchronized方法上锁解决,但也带来了效率下降
*/
public class VolatileSingle3 {
private static VolatileSingle3 instance;
private VolatileSingle3(){
}
private static synchronized VolatileSingle3 getInstance(){
if (instance == null){
//通过同步代码块的方式来提高效率
//存在问题:第一个线程过来判断为空,还未执行,线程二来也判断为空,第一个线程上锁,初始化完返回,线程二此时也是空的,也会初始化一遍再返回
synchronized (VolatileSingle3.class){
try {
Thread.sleep(1);
}catch (InterruptedException interruptedException){
interruptedException.printStackTrace();
}
instance = new VolatileSingle3();
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0 ; i < 1000 ; i++){
new Thread(() ->{
System.out.println(VolatileSingle3.getInstance().hashCode());
}).start();
}
}
}
DCL— Double Check Lock(双重校验锁)
/**
* 懒汉式 --双重锁校验,
* 默认不会实例化,什么时候用什么时候
* 虽然达到了按需初始化的目的,但也带来了线程不安全的问题
* 虽然可以通过synchronized方法上锁解决,但也带来了效率下降
*/
public class VolatileSingle4 {
//关于是否需要加上volatile,不加volatile,问题会出现在指令重排序上
//new 一个对象会分成3步:1、申请内存;2、更新值;3赋值给对象;
//若不加volatile,在超高超高的并发情况下,会产生对象在半初始化的时候就指令有可能被打断而重新排序,线程1初始化一半时,线程2到来,取到了半初始化的值
// synchronized是保证了可见性,但不能阻止重排序
private static volatile VolatileSingle4 instance;
private VolatileSingle4(){
}
private static synchronized VolatileSingle4 getInstance(){
if (instance == null){
//双重检查
synchronized (VolatileSingle4.class){
//上锁之后再次进行加锁
if (instance == null){
try {
Thread.sleep(1);
}catch (InterruptedException interruptedException){
interruptedException.printStackTrace();
}
instance = new VolatileSingle4();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0 ; i < 1000 ; i++){
new Thread(() ->{
System.out.println(VolatileSingle4.getInstance().hashCode());
}).start();
}
}
}
volatile能否替代synchronized
/**
* volatile 并不能保证多线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
* count++线程不安全,非原子性操作
*/
public class T {
volatile int count = 0;
//操作时需要加上锁
/**synchronized*/ void m() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
T t = new T();
List<Thread> threadList = new ArrayList<>();
for (int j = 0; j < 10; j++) {
threadList.add(new Thread(t::m,"thread-" + j));
}
threadList.forEach((o) -> o.start());
threadList.forEach((o) ->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
锁优化
/**
* synchronized优化
* 同步代码块中的语句越少越好-----锁细化
* 锁争用的时候,可以考虑锁粗化
* 比较m1和m2
*/
public class FineCoarseLock {
int count = 0;
synchronized void m1() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count ++;
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
//采用细粒度的锁,可以使线程争用时间变短,从而提高效率
synchronized(this) {
count ++;
}
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CAS(无锁化,自旋)
–Compare And Set
–cas(V, Expected, NewValue)-if V == E 若所期望的值相同
V = NEW 则设置成新的值 try again or fail 反之这重试或者直接失败
- CPU 原语支持(执行过程中不能被打断)
- ABA问题 采用加version解决(基础类型–可以不用考虑,引用类型—会通俗来讲就是你大爷还是你大爷,你大妈已经不是你大妈了_)
java.util.concurrent.atomic.目录下以Atomic开头的都是自旋锁,JDK1.8之后提拱了分片区自旋锁,LongAddr、DoubleAddr等
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
*/
public class T_AtomicInteger {
AtomicInteger count = new AtomicInteger(0);
/*synchronized*/ void m() {
for (int i = 0; i < 10000; i++)
count.incrementAndGet();
}
public static void main(String[] args) {
T_AtomicInteger t = new T_AtomicInteger();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
Unsafe
随手点个关注~