一、互斥同步
互斥同步(Mutual Exclusion & Synchronization):是一种常见的也是最主要的并发正确性保证手段。
- Java中基本的互斥同步手段是
synchronized
关键字,这是一种块结构(Block Structured)的同步语法。
synchronized
关键字经过javac编译后,会有两个字节码指令monitorenter
和monitorexit
。这两个字节码指令都需要有一个reference类型的参数来指明要锁定和解锁的对象。如果synchronized明确指定了对象参数,就用对象参数,没有就修饰方法类型(如实例方法或类方法)
在执行moniterenter
指令:首先尝试去获取对象的锁。如果这个对象没有被锁定,或者当前线程已经持有那个对象锁,就把锁的计算器加一,执行到moniterexit时,计数器就减一。一旦计算器为零,锁随之就被释放。如果获取对象失败,当前线程应该被阻塞等待,直到线程被释放为止。
结论:
- 被synchronized修饰的同步款对同一条线程来说时可重入的。意味着同一线程反复进入同步块也不会出现把自己锁死的情况。
- 被synchronized修饰的同步块在持有锁的线程执行完毕并释放之前,会无条件的阻塞后面其他的线程。
二、非阻塞同步
非阻塞同步锁:它是基于冲突检测的乐观并发策略,就是不管风险,先进行操作,那再进行其他的补偿措施,最常用的补偿措施是不断地重试,直到出现没有竞争的共享数据为止。
上面所说的互斥同步带来的主要问题是进行线程阻塞和唤醒所带来的性能开销,它也称为阻塞同步(Blocking Synchronization)。它是一种悲观的并发策略,其总认为只要不去做正常的同步措施(例如加锁),那就肯定会出现问题,但是这会影响性能开销。
三、无同步方案
如果让一个方法本来就不涉及共享数据,那它自然就不需要任何同步措施去保证正确性,因此有一些代码天生是线程安全的。
1、可重入代码(Reenterant Code):它不依赖全局变量、存储在堆上的数据和公用的系统资源,用到的状态量由参数中传入,不调用非可重入的方法。例如:如果一个方法的返回结果是可以预测的,只要输入相同的数据,就都能返回相同的结果,那它就满足可重入性的要求,当然也是线程安全的。
2、线程本地存储(Thread Local Storage):如果一段代码所需要的数据必须与其他代码共享,那就看看这些共享数据是否能保证在同一个线程中执行,如果能保证,我们就可以把共享数据的可见范围限制在同一线程之内。这样,无须同步也能保证线程之间不出现数据争用的问题
例如:消费队列的架构模式(如”生产者-消费者“模式),Web交互模型,”一个请求对应一个服务器线程“(Thread-per-Request)的处理方式