典型回答
synchronized是java内建的同步机制,所以有人称其为instrinisc Locking(固有锁),它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。
在java5以前,synchronized是仅有的同步手段,在代码中,synchronized可以用来修饰方法,也可以使用在特定的代码块上,本质上sychronized方法等同于把方法全部语句synchronized块包起来。
ReentrantLock,通常翻译为再入锁,是java5提供的锁实现,它的语义和synchronized基本相同。再入锁通过代码直接调用lock()方法获取,代码书写更加灵活。与此同时ReentrantLock提供了很多实
用的方法,能够提供很多synchronized无法做到的细节控制,比如可以控制fairness,也就是公平性,或者利用定义条件等。但是,编码中也需要注意,必须明确调用unlock()方法释放,不然就会一直持有该锁。
synchronized和ReentrantLock的性能不能一概而论,早期版本synchronized在很多场景下性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于ReentrantLock.
接下为理解锁来扩展下知识
首先,理解什么是线程安全
定义:线程安全是一个多线程环境下正确性的概念,也就是保证多线程环境下共享的,可以修改的状态的正确性,这里的状态反映在程序中其实可以看做是数据。
换个角度来看,如果状态不是共享的,或者不是可修改的,也就不存在线程安全问题,进而知道保证线程安全的两个办法:
- 封装:通过封装,我们可以将对象内部状态隐藏,保护起来。
- 不可变:定义变量或者对象为final,java语言目前还没有真正意义上的原生不可变。
线程安全需要保证几个基本特性:
- 原子性,简单说就是相关操作不会中途被其他线程打扰,一般通过同步机制实现。
- 可见性,是一个线程修改某个共享变量,其状态能够立即被其他线程知晓,通常解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的。
- 有序性,是保证线程内串行语义,避免指令重排。
稍微有点难懂,看下代码,分析下原子性需求体现在哪里。这个例子通过取两次数值然后进行对比,来模拟两次对共享状态的操作。
你可以编译并执行,可以看到,仅仅是两个线程的低度并发,就非常容易碰到former和latter不相等的情况。这是因为,在两次取值的过程中,其他线程可能已经修改了shareState。
public class ThreadSafeSample { public int shareState; public void nonSafeAction(){ while(shareState<100000){ int former = shareState++; int latter = shareState; if(former !=latter - 1){ System.out.println("Observed data race,former is"+former+","+"latter is"+latter); } } } public static void main(String[] args) throws InterruptedException{ final ThreadSafeSample sample = new ThreadSafeSample(); Thread threadA = new Thread(){ public void run(){ sample.nonSafeAction(); } }; Thread threadB = new Thread(){ public void run(){ sample.nonSafeAction(); } }; threadA.start(); threadB.start(); threadA.join(); threadB.join(); } }
运行结果
Observed data race,former is62,latter is64
Observed data race,former is8379,latter is8381
Observed data race,former is45385,latter is45387
但是如果加上
public class ThreadSafeSample { public int shareState; public void nonSafeAction(){ while(shareState<100000){ synchronized(this){ int former = shareState++; int latter = shareState; if(former !=latter - 1){ System.out.println("Observed data race,former is"+former+","+"latter is"+latter); } } } } public static void main(String[] args) throws InterruptedException{ final ThreadSafeSample sample = new ThreadSafeSample(); Thread threadA = new Thread(){ public void run(){ sample.nonSafeAction(); } }; Thread threadB = new Thread(){ public void run(){ sample.nonSafeAction(); } }; threadA.start(); threadB.start(); threadA.join(); threadB.join(); } }
就不会有显示结果,这样保证了安全性
今天先写到这里