多线程与高并发编程2
volatile
public class VolatileDemo {
/*volatile*/ boolean running = true;
void m(){
System.out.println("m start");
while (running) {
/*try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println("m end");
}
public static void main(String[] args) {
VolatileDemo volatileDemo = new VolatileDemo();
new Thread(volatileDemo::m,"t1").start();
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileDemo.running = false;
}
}
上面的程序模拟的是一个类似于服务器的流程,只要runing这个标志位的数据是ture,这个线程就会一直执行,创建线程之后会执行m.start之后会输出m start,线程启动后使其睡眠1s,希望可以把running改编为false,之后输出m end
public class VolatileDemo {
volatile boolean running = true;
void m(){
System.out.println("m start");
while (running) {
/*try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println("m end");
}
public static void main(String[] args) {
VolatileDemo volatileDemo = new VolatileDemo();
new Thread(volatileDemo::m,"t1").start();
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileDemo.running = false;
}
}
现在给标志running加上volatile
volatile是可变的易变的
饿汉模式(创建一个单例模式的对象不再销毁)
/**
* @Date: 2022/08/16/00:02
* @Description:饿汉式
* 类加载到内存后,就是实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用
* 唯一缺点:无论用到与否,类加载时就完成实例化
* Class.forName("")
* (话说你不用他,为什么要装载他)
**/
/**
* 使用场景 :有一些类只需要创建一次不需要多次多个创建
*/
public class StarvingHanMode {
// class加载到内存到内存到时候 static(由JVM来帮忙初始化的) 创建时就new 而且无参构造是私有方法别人不能new
private static final StarvingHanMode INSTANCE = new StarvingHanMode();
private StarvingHanMode(){};
public static StarvingHanMode getInstance(){return INSTANCE;}
public static void main(String[] args) {
StarvingHanMode m1 = StarvingHanMode.getInstance();
StarvingHanMode m2 = StarvingHanMode.getInstance();
System.out.println(m1 == m2);
}
}
懒汉模式(用的时候看看存不存在,不存在再创建)
/**
* @Author:
* @Date: 2022/08/16/21:33
* @Description:lazy loading
* 也称懒汉模式
* 虽然达到了按需初始化到目的,但却带来线程不安全到问题
* 可以通过synchronized解决,但也带来效率的下降
**/
public class LazyHanMode {
private static LazyHanMode INSTANCE;
private LazyHanMode(){
}
public static LazyHanMode getInstance(){
//此时没有实例化但是两个线程同时去判断 就会同时实例化多个LazyHanMoudle
//虽然达到了按需初始化到目的,但却带来线程不安全到问题
//可以通过synchronized解决,但也带来效率的下降
if (INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyHanMode();
}
return INSTANCE;
}
public void m(){
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++){
new Thread(()->
System.out.println(LazyHanMode.getInstance().hashCode())).start();
}
}
}
可见根据不同的hash值说明创建了多个对象
增加synchronized可以解决这个问题
/**
* @Author:
* @Date: 2022/08/16/21:33
* @Description:lazy loading
* 也称懒汉模式
* 虽然达到了按需初始化到目的,但却带来线程不安全到问题
* 可以通过synchronized解决,但也带来效率的下降
**/
public class LazyHanMode1 {
private static LazyHanMode1 INSTANCE;
private LazyHanMode1(){
}
public static synchronized LazyHanMode1 getInstance(){
//此时没有实例化但是两个线程同时去判断 就会同时实例化多个LazyHanMoudle
//虽然达到了按需初始化到目的,但却带来线程不安全到问题
//可以通过synchronized解决,但也带来效率的下降
if (INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyHanMode1();
}
return INSTANCE;
}
public void m(){
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++){
new Thread(()->
System.out.println(LazyHanMode1.getInstance().hashCode())).start();
}
}
}
锁定了getInstance整个代码块有人会说这样锁定的代码太多了,可能影响业务的效率
就有提出了更细粒度的锁的方案
/**
* @Author:
* @Date: 2022/08/16/21:33
* @Description:lazy loading
* 也称懒汉模式
* 虽然达到了按需初始化到目的,但却带来线程不安全到问题
* 可以通过synchronized解决,但也带来效率的下降
**/
public class LazyHanModeWrong {
private static LazyHanModeWrong INSTANCE;
private LazyHanModeWrong(){
}
public static LazyHanModeWrong getInstance(){
//此时没有实例化但是两个线程同时去判断 就会同时实例化多个LazyHanMoudle
//虽然达到了按需初始化到目的,但却带来线程不安全到问题
//可以通过synchronized解决,但也带来效率的下降
if (INSTANCE == null){
synchronized (LazyHanMode.class){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyHanModeWrong();
}
}
return INSTANCE;
}
public void m(){
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++){
new Thread(()->
System.out.println(LazyHanModeWrong.getInstance().hashCode())).start();
}
}
}
只把实例化的代码锁住但是这样是错误的 也会线程不安全
原因:线程A判断完没有 INSTANCE 准备创建线程的时候,线程B同时也判断的话就会创建出多个INSTANCE
/**
* @Author:
* 双重校验
* @Date: 2022/08/16/21:33
* @Description:lazy loading
* 也称懒汉模式
* 虽然达到了按需初始化到目的,但却带来线程不安全到问题
* 可以通过synchronized解决,但也带来效率的下降
**/
public class LazyHanModeWrongDoubleCheck {
private static LazyHanModeWrongDoubleCheck INSTANCE;
private LazyHanModeWrongDoubleCheck(){
}
public static LazyHanModeWrongDoubleCheck getInstance(){
//此时没有实例化但是两个线程同时去判断 就会同时实例化多个LazyHanMoudle
//虽然达到了按需初始化到目的,但却带来线程不安全到问题
//可以通过synchronized解决,但也带来效率的下降
if (INSTANCE == null){
synchronized (LazyHanMode.class){
if(INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new LazyHanModeWrongDoubleCheck();
}
return INSTANCE;
}
}
return INSTANCE;
}
public void m(){
System.out.println("M");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++){
new Thread(()->
System.out.println(LazyHanModeWrongDoubleCheck.getInstance().hashCode())).start();
}
}
}
双重校验就会解决准备实例化的时候已经被实例化过,在双重校验的前提下,100台机器10000个线程同时调用也不太可能出现问题
过程是线程A和线程B首先判断是否已经实例化,A尝试获得锁成功,B未能获得锁,自旋等待(锁升级)不会创建实例,线程A获得了锁,会实例化,实例化结束后让出锁,线程B自旋等待后获得锁,但是在开始实例化之前会再次检测是否已经实例化,这样就避免了重复实例化一个对象造成内存的浪费和系统性能的损耗,可见双重检查可以保证线程安全
但是INSTANCE是否需要加volatile
实际的压测过程中很难发现加volatile与不加volatile的区别,有些人因此就不加volatile,但是实际上是需要加的,原因和禁止指令重排序有关
new一个对象的时候分三步,1申请内存,2初始化成员变量 3 内存的内容赋值给INSTANCE
如果没有volatile呢么就有可能申请完内存后没有初始化成员变量,直接把这块内存赋值给INSTANCE,会在极高并发的情况下产生问题,比如申请完内存后没有初始化成员变量,直接把这块内存赋值给INSTANCE,就会读到0
如果使用了volatile,保证了指令不可重拍讯,则不会出现上述的问题
volatile是否可以替代sync
不加volatile
import java.util.ArrayList;
import java.util.List;
/**
* @Author:
* @Date: 2022/08/22/23:06
* @Description:
**/
public class DifVolatileWithSync {
int count = 0;
void m(){
for (int i = 0;i < 10000;i++)
count++;
}
public static void main(String[] args) {
DifVolatileWithSync difVolatileWithSync = new DifVolatileWithSync();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10;i++){
threads.add(new Thread(difVolatileWithSync::m,"thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(difVolatileWithSync.count);
}
}
可见结果不是10w
加volatile
import java.util.ArrayList;
import java.util.List;
/**
* @Author:
* @Date: 2022/08/22/23:06
* @Description:
**/
public class DifVolatileWithSync {
volatile int count = 0;
void m(){
for (int i = 0;i < 10000;i++)
count++;
}
public static void main(String[] args) {
DifVolatileWithSync difVolatileWithSync = new DifVolatileWithSync();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10;i++){
threads.add(new Thread(difVolatileWithSync::m,"thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(difVolatileWithSync.count);
}
}
可见还是没有达到预期
为什么最后的值不是10w,第一个线程吧count修改成1,这时候线程2和3都都看到count是1,于是都加了1,count都是2,写回内存,这样就已经少了一次,这是因为count++是满足原子性的,但是内部的指令有很多不一个
import java.util.ArrayList;
import java.util.List;
/**
* @Author:
* @Date: 2022/08/22/23:06
* @Description:
**/
public class DifVolatileWithSync {
/*volatile*/ int count = 0;
synchronized void m(){
for (int i = 0;i < 10000;i++)
count++;
}
public static void main(String[] args) {
DifVolatileWithSync difVolatileWithSync = new DifVolatileWithSync();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10;i++){
threads.add(new Thread(difVolatileWithSync::m,"thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(difVolatileWithSync.count);
}
}
锁优化
细化锁
import java.util.concurrent.TimeUnit;
/**
* @Author:
* @Date: 2022/08/23/20:57
* @Description:同步代码块中语句越少越好
**/
public class FineCoarseLock {
int count = 0;
synchronized void m1(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//不是锁住整个代码块,而是只锁住count++
synchronized (this){
count ++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
粗力度锁
有很多锁,每行都有锁,不如用一个大的锁锁住,减少锁的获取
object被改变了
import java.util.concurrent.TimeUnit;
/**
* @Author:
* @Date: 2022/08/23/22:38
* @Description:
**/
public class LockObjChange {
final Object o = new Object();
void m() {
synchronized (o){
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
LockObjChange lockObjChange = new LockObjChange();
new Thread(lockObjChange::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread = new Thread(lockObjChange::m, "t2");
lockObjChange.o = new Object();
thread.start();
}
}
import java.util.concurrent.TimeUnit;
/**
* @Author:
* @Date: 2022/08/23/22:38
* @Description:
**/
public class LockObjChange {
final Object o = new Object();
void m() {
synchronized (o){
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
LockObjChange lockObjChange = new LockObjChange();
new Thread(lockObjChange::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread = new Thread(lockObjChange::m, "t2");
// lockObjChange.o = new Object();
thread.start();
}
}
加final,让o不能被改变
CAS
CASAtomicInteger
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author:
* @Date: 2022/08/24/20:40
* @Description:
**/
public class CASAtomicInteger {
AtomicInteger count = new AtomicInteger(0);
void m(){
for (int i = 0;i<10000;i++){
count.incrementAndGet();
}
}
public static void main(String[] args) {
CASAtomicInteger casAtomicInteger = new CASAtomicInteger();
List<Thread> threads = new ArrayList<>();
for (int i = 0;i<10;i++){
threads.add(new Thread(casAtomicInteger::m,"thread-" + i));
}
threads.stream().forEach((o)->o.start());
threads.stream().forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(casAtomicInteger.count);
}
}
线程安全的自增类有jdk提供
因为是cpu指令的操作,由cpu原语支持,所以在判断 V==E的时候不能插别的操作
ABA问题
简单说基础类型不影响,应用类型属性不变,内容已经改变
可见不影响基础类型,两次Thread读到的数据都是8,所以不影响他的操作
如果是对象,对象的地址不变,但是内部引用的对象已经发生了改变
解决办法增加版本号