初识Java线程安全【个人整理】

线程安全这个词不管是在面试还是开发中,都应该是听得最多的一个词语吧!什么是线程安全?

常规操作(百度一波):线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

然后呢?面试官再往深一点问就GG了。那么跟着我简单了解下Java的线程安全吧!

---------------------------------------------------------------------------------------

线程安全

我们已经了解了线程安全的定义,那我我们接下来就看一下Java语言中线程安全是如何实现的,那些操作设计线程安全的!在《深入理解java虚拟机》一书中将Java语言中的各种操作共享数据(如果一段代码根本不会和其他线程共享数据,从线程安全的角度看多线程执行和串行执行没什么区别)分为5类:不可变、绝对线程安全、相对线程安全(有条件的)、线程兼容、线程对立。

1、不可变

在Java语言中不可变的对象一定是线程安全的,只要一个不可变的对象被正确的构建出来,那其他外部的可见状态也不会改变,永远也不会看到它在多个线程之中处于不一致的状态。不可变带来的安全性是最简单和最纯粹的。

Java语言中如果共享数据是一个基本数据类型,我们只需要在定义的时候使用final关键字就可以保证它是不可变的;如果共享数据是一个对象,那么就需要保证对象的行为不会对其状态产生任何影响才行。保证对象的行为不影响自己状态最简单的方法就是把对象中带有状态的变量都声明为final,这样在构造函数结束后它就是不可变的,例如java.lang.Integer够着函数所示:

它通过内部状态变量value定义final来保障状态不变。

2、绝对线程安全

一个类要达到“不管运行时环境如何,调用者都不需要额外的同步措施“”。这需要很大很大的代价。大多Java API中标注的线程安全类,都不是绝对线程安全。比如Vector,它的get、add、size等方法都是被synchronized修饰的,单独调用时是安全的,但以一些特殊顺序调用一系列的方法时,就不安全了。

3、相对线程安全

相对安全就是我们通常意义上讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定的连续调用就需要在调用端是用额外的同步手段来保障调用的正确性。

如Vector、HashTable、Collections的synchronizedCollections()方法包装的集合等。

4、线程兼容

线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。

许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。

5、线程对立

线程对立类无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。线程对立很少见,并且通常都是有害的,应当尽量避免。

实现线程安全

1、互斥同步(Mutual Exclusion & Synchronization)

同步是指多个线程并发访问共享数据时,保证共享数据在同一个时刻只能被一个(或者是一些,使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区、互斥量、和信号量都是主要的互斥实现方式。因此在互斥同步里面,互斥是因,同步是果;互斥是方法,同步是目的。

Java实现互斥同步的基本办法就是synchronized,它在编译时会在代码块前后生成monitorenter和monitorexit两个字节码指令,这俩个指令都需要一个reference类型的参数来指明要锁定和解锁的对象。在虚拟机对monitorenter和monitorexit的行为描述中synchronized同步块是可重入的;synchronized会阻塞后面线程对锁的获取。由于前面讲过Java线程模型是1对1的,一个java线程对应一个操作系统原生线程,线程的阻塞和唤醒需要操作系统帮忙,需要从用户态转换到内核态,这个开销很大,所以synchronized是重量级的操作。

除了synchronized之外,我们还可以使用java.util.concurrent中的重入锁ReentrantLock实现同步。二者都是可重入的,ReentrantLock是从API层面上的互斥锁,synchronized是从原生语法层面的互斥锁,相比synchronized,ReentrantLock增加了一些高级功能,主要有3项:

1.1等待可中断

        等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断性对处理执行时间非常长的同步块很有帮助。

1.2可实现公平锁

        公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来一次获取锁;synchronized中的锁是非公平锁,ReentrantLock默认情况下也是非公平的,但是可以通过带布尔值的构造函数要求使用公平锁。

1.3锁绑定多个条件

        锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的wait()和notif()或notifyAll()方法可以实现一个隐含的条件,如果要和多余一个条件关联的时候,就不得不额外的添加一个锁,而ReentrantLock则无须这样,只需要多次调用newCondition()方法即可。

从性能上来讲虽然synchronized更重一些,但是作为jdk的实现,许多优化措施已经被发布,而且未来的优化空间还有很大(jdk1.6中synchronized和ReentrantLock性能基本持平了),所以还是推荐使用synchronized。

2、非阻塞同步

互斥同步因为会阻塞线程,称为阻塞同步,开销很大。互斥同步是属于一种悲观的并发策略,认为不同步就会出问题。随着硬件发展,出现了非阻塞同步的方法,这是一种乐观的策略。它采用先行模式,就是先进行数据操作,如果没有竞争,那操作就成功了。如果发生冲突,再补救,通常是循环重试。这就避免了线程的挂起。这种方案需要计算和冲突检测两个步骤具有原子性,实际是通过硬件提供的指令

        测试并设置(Test-and-Set)

        获取并增加(Fetch-and-Increment)

        交换(Swap)

        比较并交换(Compare-and-Swap)

        加载连接/条件存储(Load-Linkde/Store-Conditional)

JDK1.5后, sun.misc.Unsafe类提供了对CAS的包装,这个类不是给用户用的,只有通过启动类加载器加载的类才能使用。只能通过反射或其他封装类库使用,例如Atomic。这种方案存在ABA问题,尽管提供了AtomicAtampedReference通过版本解决,但如果非解决不能,还不如互斥同步的效率好。

3、无同步方案

要保证线程的安全,并不一定就要进行同步,两者没有因果关系。同步只是保证共享数据争用时的正确性手段,如果一个方法本来就不涉及共享数据,那它自反就无需任何同步措施保证正确性,因此会有一些代码天生就是线程安全的如 可重入代码、线程本地存储。

 

转载于:https://my.oschina.net/u/3903095/blog/1860868

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值