1.互斥同步
互斥同步是一种常见也是最主要的并发正确性保障手段,同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被线程使用。互斥实现方式有互斥量,临界区,信号量等手段。
JAVA里面最常见的互斥同步就是synchronized,synchronized关键字经javac编译后,会在同步块的前后形成monitorenter和monitorexit两个字节码指令。这两个字节码指令都需要一个明确的reference对象来解锁:
对于static方法锁住的是class对象;对于非static方法,锁住的是调用方法的对象,对于synchronized代码块,锁住的是synchronized(obj)中指定的对象
在获取对应的reference对象后,当前线程可以操作同步代码块,若当前线程在持有锁的情况下再次获取锁,会成功,且锁的持有计数会+1,每当释放一次锁,锁的持有计数-1,当锁的持有计数为0时,锁释放,可被其他线程获取。当一个线程持有锁对象时,对于互斥同步,其他线程在获取锁对象失败后,会被阻塞。
从上面的分析可以看出,互斥同步是一个重量级的操作,涉及到用户态与内核态的切换(操作互斥量和阻塞线程需要使用内核态),需要花费大量的时间,对于代码量较小的操作,应对这种重量级操作进行优化。
2.非阻塞同步
互斥同步面临的问题主要是线程阻塞和唤醒所带来的性能开销,因此这种也被称为阻塞同步。而与之对应的就是非阻塞同步。它的主要改进是对于获取锁失败的线程不再将其挂起,而是让其自旋等待一段时间,若还是无法获取锁,则挂起。
设置锁的持有对象也不再需要操作互斥量,而是采用原子操作进行设置,在设置成功后,当前线程获取到锁的对象。而其他线程在原子操作获取锁失败后,会自旋(可以理解为继续走循环尝试继续获取锁)等待,直到获取锁成功,或超过一定次数后被挂起。
但这种方法存在一个漏洞,即ABA问题。具体可以参照下面这几张图。即线程1在进行原子操作时因某些原因被阻塞,此时线程2利用原子操作将目的对象的值改为A,之后线程2又使用原子操作将值改回B,此后线程1突然恢复,再次进行原子操作将值改回A,此时原本应该是B的值变为了A,出现了安全性问题。
3.无同步方案
要保证线程安全,不一定非要用阻塞同步或者非阻塞同步,同步与线程安全两者没有必要的联系。如果一个方法不涉及数据共享,那么不需要任何手段就能保证其线程安全。这种无同步方案主要有两种方式实现。
一是可重入代码(Reentrant Code)。它可以在运行的任何时刻中断,转而去执行另一端代码,而在控制权返回后可以继续执行,原来的程序不会出错,也不会对结果有影响。
二是线程本地存储(Thread Local Storage)。如果一段代码中所需要的数据必须与其他代码共享,那么就看这些共享的数据能否保证在同一个线程中执行,如果能,则无需同步。对于每一个Thread对象,其中都有一个ThreadLocalMap对象,这个对象中,以本地线程变量的hashCode为键,本地线程变量的值为key-value键值对,因而我们可以通过这个ThreadLocalMap对象来找到对应的本地变量的值。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)