一、Synchronized
synchronized同步锁是一种可重入锁(能被同一个线程反复获取的锁),代码块在任意时刻只能执行一个线程,保证了一段代码的原子性。
有如下一段代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread add = new AddThread();
Thread dec = new DecThread();
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count);
}
}
class Counter{
public static int count = 0;
}
class AddThread extends Thread{
public void run() {
for(int i = 0;i < 2000;i++) {
Counter.count += 1;
}
}
}
class DecThread extends Thread{
public void run() {
for(int i = 0;i < 800;i++) {
Counter.count -= 1;
}
}
}
我们理想的结果应该是1200,但实际上这段代码的运行结果是不确定的,这是因为多个线程同时读写共享变量,就会出现数据不一致的问题。所以引入了一个锁的概念来确保此操作的原子性。
synchronized的用法:
1、修饰一个代码块:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。
// 计数器类
public class Counter{
// 用于计数的公共变量
public static int count = 0;
// 递增
public void add() {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
Counter.count += 1;
}
}
}
// 递减
public void dec() {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
Counter.count -= 1;
}
}
}
}
测试类
public class Test {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread add = new Thread() {
@Override
public void run() {
counter.add();
}
};
Thread dec = new Thread() {
@Override
public void run() {
counter.dec();
};
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count);//0
}
}
2、修饰一个实例方法:其作用范围是整个方法,作用的对象是调用这个方法的对象。
public class Counter{
// 用于计数的公共变量
public static int count = 0;
// 递增
public synchronized void add() {
for (int i = 0; i < 10000; i++) {
Counter.count += 1;
}
}
// 递减
public synchronized void dec() {
for (int i = 0; i < 10000; i++) {
Counter.count -= 1;
}
}
}
3、修饰一个静态方法:其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
public class Counter{
// 用于计数的公共变量
public static int count = 0;
// 递增
public synchronized static void add() {
for (int i = 0; i < 10000; i++) {
Counter.count += 1;
}
}
// 递减
public synchronized static void dec() {
for (int i = 0; i < 10000; i++) {
Counter.count -= 1;
}
}
}
二、ReentrantLock
synchronized关键字获取时必须一直等待,没有额外的尝试机制。所以,引入了一个ReentrantLock可重入锁。它是由java.util.concurrent.locks包提供的,和synchronized一样,一个线程可以多次获取同一个锁。
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
public static int count = 0;
//创建锁
private final ReentrantLock lock = new ReentrantLock();
public void add() {
for(int i = 0;i < 1230;i++) {
//加锁
lock.lock();
try {
Counter.count += 1;
}finally {
//释放锁
lock.unlock();
}
}
}
public void dec() {
for(int i = 0;i < 1000;i++) {
lock.lock();
try {
Counter.count -= 1;
}finally {
lock.unlock();
}
}
}
}
值得注意的是我们创建ReentrantLock锁,结束后要在finally中正确释放锁。
ReentrantLock和synchronized有一个不同的地方,就是ReentrantLock可以尝试获取锁
public void add() throws InterruptedException {
for(int i = 0;i < 1230;i++) {
//尝试获取锁
//最多等待1秒。如果1秒后仍未获取到锁,tryLock()返回false
if(lock.tryLock(1, TimeUnit.SECONDS)) {
try {
Counter.count += 1;
}finally {
//释放锁
lock.unlock();
}
}
}
}
所以,使用ReentrantLock比直接使用synchronized更安全,线程在tryLock()失败的时候不会导致死锁。
三、ReentrantLock与synchronized的区别
ReentrantLock | Synchronized | |
---|---|---|
用法 | 用于代码块 | 可以修饰普通方法、静态方法、 代码块 |
锁实现机制 | AQS | 监视器Monitor |
获取锁 | 可以通过tryLock()尝试获取锁 | 线程抢占 |
释放锁 | 必须显示通过unlock()释放锁 | 自动释放 |
锁类型 | 支持公平锁和非公平锁 | 非公平锁 |
可重入性 | 可重入 | 可重入 |
响应中断 | 可以响应中断 | 不可以响应中断 |
总结:
1、用法不同:synchronized 可以用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用于代码块。
2、获取锁和释放锁不同:synchronized 是自动加锁和释放锁,而 ReentrantLock 需要手动加锁和释放锁。
3、锁类型不同:synchronized 是非公平锁,而 ReentrantLock 默认为非公平锁,但它也可以手动被指定为公平锁。
4、响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,而 synchronized 不能响应中断。
5、底层实现不同:synchronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的。