1.参考资料
《Java并发编程艺术》
·网络资料
。https://blog.csdn.net/zqz_zqz/article/details/70246212
。https://www.cnblogs.com/ZoHy/p/11313155.html
。https://www.cnblogs.com/linghu-java/p/8944784.html
。https://blog.csdn.net/qq_32099833/article/details/103721326
。https://www.cnblogs.com/chenyangyao/p/5269622.htm
2.线程安全介绍
当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的
协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。
3.线程安全问题
(1)先看两段代码
代码片段1
private volatile int count=0;
public void unsafe Add(throws InterruptedException(
int threadCount=50000;
Executor Service executor=Executors.new Fixed ThreadPool(10) ;
long start=System.current Time Mill isO
for(inti=0; i<threadCount; i++) (
Runnable runnable=O->count++;
executor.execute(runnable) ;
)
executor.shutdown() ;
executor.await Termination(Long.MAX_VALUE, Time Unit.DAYS) ;
System.out.println(”unsafe Add耗时:”+(System.current Time Millis() -start) +”ms”) ;
System.out.printLn(”unsafe Add执行结果:count=”+count) ;
)
执行结果?
代码片段2
private Atomic Integer atomic Integer=new Atomic Integer(0) ;
public void safe Add() throws InterruptedException(
int threadCount=50000;
Executor Service executor=Executors.new Fixed ThreadPool(10) ;
Long start=System.current Time Mill isO;
for(inti=0; i<threadCount; i++) (
Runnable runnable=(->atomic Integer.increment And Get O;
executor.execute(runnable) ;
~
executor.shutdown O;
executor.await Termination(Long.MAX_VALUE, Time Unit.DAYS) ;
System.out.printLn(”safe Add耗时:”+(System.current Time Millis() -start) +”ms”) ;
System.out.printLn(”safe Add执行结果:count=”+atomic Integer.get O) ;
)
执行结果?
4.为什么会出现线程安全问题????
一、volatile
1.介绍
在多线程并发编程中synchronized和volatile都扮演着重要的角色, volatile是轻量级的synchronized, 它在多处理器开发中保证了共享变量的“可见
性”。可见性的意思是当一个线程修改一个共享变量时, 另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话, 它比
synchronized的使用和执行成本更低, 因为它不会引起线程上下文的切换和调度, volatile还可以禁止指令重排序
2.线程可见性
(1)代码
public class Volatile Main(
private boolean flag=true;
public void run O) (
while(flag) (
System.out.printLn(”end”) ;
)
public static void main(String[] args) throws Exception(
VolatiLe Main volatile Main=new VolatiLe Main O;
new Thread(O->volatile Main.run() .star tO;
Thread.sleep(1000) ;
volatile Main.flag=false;
System.out.printLn(”flag=true”) ;
)
(2)内存模型

3.指令重排序
先看代码
public class Possible Reordering(
static in tx=0, y=0;
static int a=0, b=0;
public static void main(String args) throws InterruptedException(
Thread one=new Thread(new Runnable O) (
public void run() (
a=1;
x=b;
));
Thread other=new Thread(new Runnable O(
public void run O(
b=1;
y=a;
);
one.star tO; other.star tO;
one.join O; other.join O;
System.out.printLn(“(”+x+“, ”+y+“) ”) ;
大多数现代微处理器都会采用将指令乱序执行(out-of-order execution, 简称OoO E或OOE) 的方法, 在条件允许的情况下, 直接运行当前有能力
立即执行的后续指令,避开获取下一条指令所需数据时造成的等待3。通过乱序执行的技术,处理器可以大大提高执行效率。
除了处理器, 常见的Java运行时环境的JIT编译器也会做指令重排序操作, 即生成的机器指令与字节码指令顺序不一致。
4.as-if-serial语义
As-if-serial语义的意思是, 所有的动作(Action) 都可以为了优化而被重排序, 但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。
再看一段代码
int a=1;
in tb=2;
intc=a+b;
查看字节码指令
1.I CONST_1将int类型常量1压入栈
I STORE 1将int类型值存入局部变量
2.I CONST_2将int类型常量2压入栈
I STORE 2将int类型值存入局部变量
3.I LOAD 1从局部变量中装载int类型值
4.I LOAD 2从局部变量中装载int类型值
- I ADD执行int类型的加法
6.I STORE 3将int类型值存入局部变量
问题:以下哪几种重排不可能存在
(1、2、4)
(2、1、3)
(1、3、5)
(3、2、4)
(4、1、3)
(2、4、5)
二CAS
1.介绍
Compare and Swap, 即比较再交换
2.Atomic Integer
(1)代码分析
Atomic Integer atomic Integer=new Atomic Integer(1) ;
System.out.printLn(atomic Integer.increment And Get O) ;
public final int increment And Get() (
return unsafe.get And Add Int(this, value Offset, 1) +1;
public final int get And Add Int(Object var 1, long var 2, int var 4) (
int var 5;
do(
var 5=this.get Int VolatiLe(var 1, var 2) ;
)while(!this.comp are And Swap Int(var 1, var 2, var 5, var 5+var 4) ) ;
return var 5;
)
(2)操作流程
1.读取当前值E
2.计算结果值V
3.比较E和当前新值N
3-1.相等更新为新值V
3-2.不相等重新执行刚才操作
(3) ABA问题
T1想将A从100修改为101,此时T1读取到100
此时T2将A从100改为101,又改为100
T 1进行CAS操作时发现A还是100, 符合条件, 所以修改100为101。但是此时A已经不是原来的A了。
xx交了一个女朋友,一个月后女朋友要去出差3个月,3个月后女朋友回来了,告诉xx说怀孕2个月了
(4) 如何解决ABA
引入版本号Atomic Stamped Reference
三、synchronized
1.介绍
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方
法完成的。
一句话总结出Synchronized的作用:能够保证在同一时刻最多只有一个线程执行该段代码, 以达到保证并发安全的效果
2.使用场景
(1)修饰一个代码块
/**
*修饰代码块
*/
public void block Sync() (
System.out.println(”普通方法开始执行前”+Thread.current Thread) .getName() ) ;
synchronized(this) (
System.out.println(”代码块开始执行”+Thread.current Thread() .getName O) ;
//代码逻辑
try(
Thread.sleep(5000) ;
) catch(InterruptedException e) (
e.printStackTrace O;
)
System.out.printLn(”普通方法执行结束”+Thread.current Thread() .getName() ) ;
)
被修饰的代码块称为同步语句块,其作用的范围是大括号括起来的代码,作用的对象是调用这个代码块的对象。
(2)修饰一个方法
/**
*修饰方法
*/
public synchronized void method Sync O(
//代码逻辑
System.out.println(”普通方法执行:”+Thread.current Thread(.getName() ) ;
try(
Thread.sleep(5000) ;
) catch(InterruptedException e) (
e.printStackTrace(;
)
System.out.printLn(”普通方法执行结束”+Thread.current Thread() .getName() ) ;
)
被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
(3)修饰一个静态的方法
/**
*修饰静态方法
*/
public static synchronized void static Method Sync() (
//代码逻辑
System.out.printLn(”静态方法开始执行:”+Thread.current Thread() .getName O) ) ;
try(
Thread.sleep(5000) ;
) catch(InterruptedException e) (
e.printStackTrace O;
)
System.out.printLn(”静态方法执行结束”+Thread.current Thread O.getName() ) ;
其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
(4)修饰一个类
/**
*修饰类
*/
public void clazz Sync() (
System.out.printLn(”修饰类开始执行前”+Thread.current Thread() .getName() ) ;
synchronized(Sync.class) (
System.out.printLn(”修饰类开始执行”+Thread.current Thread() .getName() ) ;
//代码逻辑
try(
Thread.sleep(5000) ;
) catch(InterruptedException e) (
e.printStackTrace O;
)
System.out.printLn(”修饰类执行结束”+Thread.current Thread O.getName() ) ;
)
其作用的范围是synchronized后面括号括起来的部分, 作用的对象是这个类的所有对象。
网友评论