线程安全问题了解一下

本文是基于http://www.jasongj.com/java/thread_safe/#comments博文的总结,是在前人基础上为了方便个人记忆进行的总结,如有侵权,请联系我、

1、线程安全的三个核心概念

原子性

可见性

顺序性

2、原子性

      2.1什么是原子性

      原子性就是指一个操作,可能包含多个子操作,要么全做,也就是执行过程不能被打断

     2.2如何保证原子性

         2.2.1锁和同步

         锁和同步是保证原子性常用的方法,在方法前使用sychonized关键字能够保证在同一时刻只有一个线程执行该段代码。同样lock也可保证同一时间只有一个线程能拿到锁来执             行加锁和解锁之间的代码。

         sychonized和lock的区别

         1、a.lock使用起来比较灵活,但是使用完毕需要手动释放,sychonized不需要用户手动释放,当执行结束后系统会自动让线程释放对锁的占用

         2、sychonized是Java关键字,属于内置特性,而Lock不是Java语言内置的,他是一个类,通过这个类可以实现同步访问

         3、并发量较小时sychonized是个不错的选择,使用简单方便,但当并发量较大时sychonized性能下降严重,此时lock 是个不错的选择。

         4、使用lock时,阻塞/唤醒使用的是Condition对象的await/signal/signalAll。使用sychonized时,阻塞/唤醒使用的是wait/notify/notifyAll。(Condition是一个接口,其实现类是                   Lock接口的实现类中的内部类,使用时使用lock对象的newCondition()方法即可)

         关于sychonized一点需要了解的

         当sychonized修饰非静态方法时锁住的是类的实例对象,当sychonized修饰静态方法时锁住的是类的Class对象

        2.2.2CAS算法

        Java中提供了Java.util.concurrent.atomic包下的原子操作类来保证操作的原子性,其本质是使用cpu级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与                的锁的开销小。

3、可见性

   3.1什么是可见性

   可见性是指当多个线程并发访问共享变量时,一个线程对变量的修改其他线程能够立即看到。

   3.2如何保证可见性

   Java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此     其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。

   锁和同步也可以保证可见性,参见下方问题第一题。

4、顺序性

   4.1什么是顺序性

   顺序性是指程序执行的顺序按照代码的先后顺序执行

   4.2如何保证顺序性

    1、Java通过volatile关键字一定程度上保证了顺序性(一个被volatile修饰的变量,对其写操作发生在对该变量的读操作之前)

    2、synchronized和锁保证顺序性的原理和保证原子性一样,都是通过保证同一时间只会有一个线程执行目标代码段来实现的。

    3、happens-before原则

关于线程安全的问题,转载自http://www.jasongj.com/java/thread_safe/#comments

问:平时项目中使用锁和synchronized比较多,而很少使用volatile,难道就没有保证可见性?
答:锁和synchronized即可以保证原子性,也可以保证可见性。都是通过保证同一时间只有一个线程执行目标代码段来实现的。

 

问:锁和synchronized为何能保证可见性?

答:根据JDK 7的Java doc中对concurrent包的说明,一个线程的写结果保证对另外线程的读操作可见,只要该写操作可以由happen-before原则推断出在读操作之前发生。

 

The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships.

问:既然锁和synchronized即可保证原子性也可保证可见性,为何还需要volatile?
答:synchronized和锁需要通过操作系统来仲裁谁获得锁,开销比较高,而volatile开销小很多。因此在只需要保证可见性的条件下,使用volatile的性能要比使用锁和synchronized高得多。

问:既然锁和synchronized可以保证原子性,为什么还需要AtomicInteger这种的类来保证原子操作?
答:锁和synchronized需要通过操作系统来仲裁谁获得锁,开销比较高,而AtomicInteger是通过CPU级的CAS操作来保证原子性,开销比较小。所以使用AtomicInteger的目的还是为了提高性能。

问:还有没有别的办法保证线程安全
答:有。尽可能避免引起非线程安全的条件——共享变量。如果能从设计上避免共享变量的使用,即可避免非线程安全的发生,也就无须通过锁或者synchronized以及volatile解决原子性、可见性和顺序性的问题。

问:synchronized即可修饰非静态方式,也可修饰静态方法,还可修饰代码块,有何区别
答:synchronized修饰非静态同步方法时,锁住的是当前实例;synchronized修饰静态同步方法时,锁住的是该类的Class对象;synchronized修饰静态代码块时,锁住的是synchronized关键字后面括号内的对象。

 

问:volatile和static的区别

混淆这两个概念可能是把 全局唯一 和 线程共享 两个概念搞混了,认为一个线程更改全局变量其他线程就都可以看到,其实不然。!! 为了提高优化度和运行速度,一个线程修改一个全局变量是不一定立即写回内存让其他线程看到的。这时候就需要用volatile强制要求每次改变都写回内存了。

注意两者使用的场景,static用于类的多个实例共享唯一一个静态变量;volatile用于多个线程共同读取唯一的内存变量的值

 

             

      

 

posted @ 2018-09-06 17:37  朝朝暮暮dx  阅读(173)  评论(0编辑  收藏  举报