synchronized和lock的区别

2,131 阅读3分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

两者对比

  • synchronized是属于jvm层面的的关键字,底层通过monitorenter、monitorexit指令实现的;而lock是属于一个类。
  • synchronized在代码执行异常时或正常执行完毕后,jvm会自动释放锁;而lock不行使用lock必须加上异常处理,而且必须在finally块中写上unlock()释放锁。
  • synchronized不可中断,只能等待程序执行完毕或者异常退出;而lock可通过interrupt来中断,可参考示例
  • synchronized不能精确唤醒指定的线程;而lock可以通过Condition精确唤醒。可参考示例
  • synchronized无法判断锁的状态,从而无法知道是否获取锁;而lock可以判断锁的状态,可参考示例

中断响应

lock可以通过interrupt中断,而isInterrupted可以判断线程是否被中断。

package com.wangscaler.lock;
​
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
/**
 * @author WangScaler
 * @date 2021/8/14 15:41
 */
class Resource {
    private Lock lock = new ReentrantLock();
    private int num = 1;
​
    protected void getLock() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            System.out.println(Thread.currentThread().getName() + "得到了锁");
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName() + num + "次执行");
                ++num;
                if (num == 10) {
                    System.out.println(Thread.currentThread().getName() + "即将中断");
                    Thread.currentThread().interrupt();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "释放了锁");
        }
    }
}
​
public class LockDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            try {
                resource.getLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();
    }
}

首先启动线程,如果num小于10则不中断线程,此时循环执行,直到num自增到10,则执行中断Thread.currentThread().interrupt();,此时循环条件变为false,线程结束。而synchronized一旦执行不能中断,要么执行完毕,要么程序异常。

lock精确唤醒示例

lock可以通过Condition精确唤醒。

比如我们有三个线程A、B、C,我们需要保证他们的执行顺序是A-B-C那么我们可以这样写当A线程执行完通过signal();方法来唤醒B,同理一次类推循环唤醒。

package com.wangscaler.lock;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
/**
 * @author WangScaler
 * @date 2021/8/14 15:41
 */
class Resource {
    private int num = 1;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
​
    protected void startRunA() {
        lock.lock();
        try {
            while (num != 1) {
                condition.await();
            }
            for (int i = 0; i < num; i++) {
                int number = i + 1;
                System.out.println(Thread.currentThread().getName() + number + "次执行");
            }
            ++num;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
    protected void startRunB() {
        lock.lock();
        try {
            while (num != 2) {
                condition1.await();
            }
            for (int i = 0; i < num; i++) {
                int number = i + 1;
                System.out.println(Thread.currentThread().getName() + number + "次执行");
            }
            ++num;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
    protected void startRunC() {
        lock.lock();
        try {
            while (num != 3) {
                condition2.await();
            }
            for (int i = 0; i < num; i++) {
                int number = i + 1;
                System.out.println(Thread.currentThread().getName() + number + "次执行");
            }
            num = 1;
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
​
public class LockDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) {
                resource.startRunA();
            }
        }, "A").start();
​
        new Thread(() -> {
            for (int i = 0; i < 4; i++) {
                resource.startRunB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 4; i++) {
                resource.startRunC();
            }
        }, "C").start();
    }
}

当然这些线程之间就像存在依赖关系一样,只有A能唤醒B,B唤醒C,C唤醒A。就像上述的案例三个线程都是执行4次,可以保证程序的正确执行,但是当B线程改为3次,程序就无法终止,因为C线程一直处于await状态,等待B线程的唤醒,然而B线程已经结束了。不过lock能精确的控制线程的执行顺序,而synchronized则做不到这点,synchronized只能随机唤醒线程。

获取锁的状态

而lock可以通过tryLock判断锁的状态。

package com.wangscaler.lock;
​
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
/**
 * @author WangScaler
 * @date 2021/8/14 15:41
 */
class Resource {
    private Lock lock = new ReentrantLock();
​
    protected void testLock() {
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + "获取锁成功");
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放锁");
                lock.unlock();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + "获取锁失败");
        }
    }
}
​
public class LockDemo {
    public static void main(String[] args) {
        Resource resource = new Resource();
        new Thread(() -> {
            resource.testLock();
        }, "A").start();
​
        new Thread(() -> {
            resource.testLock();
        }, "B").start();
        new Thread(() -> {
            resource.testLock();
        }, "C").start();
    }
}

tryLtrock方法尝试去获取锁,如果获取成功则返回布尔值true,如果获取失败则返回false。所以可以根据tryLtrock()来判断线程是否获的锁。

总结:

在资源竞争不是很激烈的情况下,可以选择synchronized,反之选择lock。synchronized是由jvm管理的,对程序员的要求较低,而lock则相反,如果操作不当,反而会带来严重的后果。

synchronized已经加入了线程自旋和适应性自旋以及锁消除、锁粗化、偏向锁,慢慢的从重量级锁转换成轻量级锁,优势也越来越明显。总之如何选择,大家根据实际情况进行选择吧。

来都来了,点个赞再走呗!

关注WangScaler,祝你升职、加薪、不提桶!