线程作为Java知识系统的重要支撑,线程安全问题也变得尤为重要。Java线程安全是整个Java系统安全的核心,实现线程安全虚拟机提供的同步和锁定机制不仅与代码的编写有关,而且起着至关重要的作用。那么如何实现线程安全呢?接下来,为您揭示答案:
一、互斥同步
互斥同步是一种常见的并发正确性保证手段。同步是指在多个线程并发访问共享数据时,保证共享数据同时只有一个(或者有些,使用信号量时)线程使用。互斥是实现同步的手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是实现互斥的主要途径。这样的话,在这在四个字中,互斥是因,同步是果,互斥是方法,同步是目的。
在Java在中间,互斥同步最基本的手段是synchronized关键字和synchronized关键字编译后,将在同步块前后形成两个字节码指令:monitorenter和monitorexit。这两个字节码都需要一个reference类型的参数来指示要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那就是对象的referencee;如果没有明确的指定,则根据synchronized修改实例方法或类别方法,以相应的对象实例或class对象作为锁对象。
重入锁(ReentrantLock):除了synchronized,Reentrantlock还可以用来实现同步。与synchronized相比,Reentrantlock提供了一些先进的功能,主要包括以下三项:
等待可以中断:等待锁的时候可以放弃等待,转而处理其他事情。
公平锁:多线程等待锁时,必须按申请锁的时间顺序获得锁。synchronized中的锁是不公平的,任何线程都可以在释放的瞬间获得锁。reentrantlock默认是不公平的,但公平锁可以通过带bool的值得构建函数来使用。
绑定多个条件:指一个条件Reentrantlock对象可以同时绑定多个对象,而wait()和notify()可以在synchronized中实现一个条件绑定。如果duoy多于一个绑定,则必须添加另一个锁。
不同的是synchronized和reentrantlock的效率问题,reentrantlock的效率相对较高。但是官方不断优化synchronized,synchronized是原生deed ,因此,当tich提倡synchronized可以实现时,synchronized优先实现同步。
在JDK 锁的优化措施很多,所以之后synchronized和reentrantlock的性能基本完全持平。所以性能因素不再是选择reentrantlock的原因,而虚拟机在未来的性能改进中会更倾向于原生synchronized。因此,建议在synchronized能够满足需求的情况下,优先使用synchronized同步。
二、非阻塞同步
互斥同步的主要问题是线程阻塞和唤醒带来的性能问题,因此这种同步也被称为阻塞同步(Blocking Synchronization)。另一种选择:基于冲突检测的乐观并发策略,一般来说是先操作,如果没有其他线程共享数据,则操作成功;如果共享数据有争议,则采取其他补偿措施(最常见的补偿措施是重新测试,直到成功),许多乐观并发策略不需要挂线程,因此这种同步操作被称为非阻塞同步。
需要乐观的并发策略”开发硬件指令集”只能进行,因为我们需要操作和冲突检测这两个步骤是原子的,只能通过硬件来完成,硬件确保一个行为似乎需要多次操作只能通过一个处理器指令来完成,这类指令通常使用:
测试并设置(Test-and-Set)
获取并增加(Fetch-and-Increment)
交换(Swap)
比较并交换(Compare-and-Swap,以下简称CAS)
加载链接/条件存储(Load-Linked/Store-Conditional,下面称LL///LLSC)
其中,前面的三个是大多数指令集中存在于20世纪的处理器指令,后两个是新的现代处理器,这两个指令的目的和功能相似。
CAS指令需要三个操作数,即内存位置(可以简单地理解为Java中的变量内存地址,用V表示)、CAS指令执行旧预期值(用A表示)和新值(用B表示)时,当V符合旧预期值A时,处理器用新值B更新V值,否则不会更新,但无论V值是否更新,都会返回V的旧值。上述处理过程是原子操作。
三、无同步方案
线程安全的同步只是保证共享数据争用的正确性的一种手段。如果一种方法不涉及共享数据,自然不需要任何同步措施来保证正确性。因此,一些代码自然是线程安全的,如以下两类:
可重入代码(Reentrant Code):这种代码也叫纯代码(Pure Code),它可以在代码执行的任何时候中断,然后执行另一个代码(包括递归调用本身),原始程序在控制权返回后不会出现任何错误。与线程安全相比,可重新访问是一个更基本的特征,它可以确保线程安全,即所有可重新访问的代码都是线程安全,但并非所有线程安全代码都可重新访问
可重新访问代码具有一些共同的特点,如不依赖堆叠的数据和公共系统资源,使用的状态量从参数中传输,不调用非可重新访问的方法。可以通过一个简单的原则来判断代码是否具有可重入性:如果一种方法可以预测其返回结果,只要输入相同的数据,就可以返回相同的结果,然后满足可重入性的要求,当然,线程是安全的