1、Java锁的分类(参考链接:https://tech.meituan.com/2018/11/15/java-lock.html)
(1)乐观锁 VS 悲观锁:
乐观锁:在获取数据时先加锁,确保数据不会被别的线程修改。synchronized关键字和Lock的实现类都是悲观锁。适合写操作频繁的场景。
悲观锁:只在更新数据时判断有没有别的锁更新了这个数据,若有则通过报错或自动重试等执行操作,否则直接将自己修改的数据写入。适合读操作频繁的场景。
(2)公平锁 VS 非公平锁
公平锁:多个线程按照申请锁的顺序来获取锁。
非公平锁:多个线程加锁时直接尝试获取锁,若此时锁刚好可用,则该线程无需阻塞直接获取到锁,否则到等待队列的队尾等待。ReentrantLock(Lock的实现类)默认使用非公平锁,但可用通过构造器来指定使用公平锁。
(3)可重入锁 VS 非可重入锁
可重入锁:举例说明,当一个线程执行某个synchronized方法时,比如method1(),且在method1()中会调用另一个synchronized方法method2(),此时线程不必重新申请锁而可用直接执行method2()(前提锁对象是同一个对象或者class)。示例代码如下:
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
(4)可中断锁 VS 非可中断锁
可中断锁:在等待获取锁的过程中可中断,例如线程B在等待线程A释放锁,线程B由于等待时间太久可以主动中断等待锁。
(5)读写锁
对资源的的读取和写入拆分为两部分处理,使用读锁保证多线程可以同步读取资源,使用写锁保证数据写入的同步。ReadWriteLock是一个读写锁,通过readLock()获取读锁,通过writeLock()获取写锁。
2、synchronized与Lock的区别
synchronized | Lock | |
---|---|---|
存在层次 | java的关键字,在jvm层面上 | 一个接口 |
锁的获取 | 假设A线程获取锁,B线程等待,若A线程阻塞,B线程会一直等待。 | 可以尝试获取锁,若获取成功则线程不用一直等待(可以通过tryLock判断有没有锁) |
锁的释放 | 1、获取锁的线程执行完同步代码,释放锁;2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁(一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放,以防死锁的发生) |
锁的状态 | 无法判断 | 可以判断 |
锁的类型 | 可重入、不可中断、非公平 | 可重入、可中断、公平/非公平皆可 |
用法比较:
- Lock必须手动获取和释放锁,而synchronized不需要手动释放和开启锁;
- Lock只适用于代码块锁,而synchronizd可用于修饰方法、代码块等。
性能比较:
- Lock可以提高多个线程进行读操作的效率(可以通过ReadWriteLock实现读写分离);
- 在资源竞争不是很激烈的情况下,synchronize的性能由于ReetrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
3、使用示例
(1)synchronized(参考链接:https://blog.csdn.net/luoweifu/article/details/46613015)
修饰静态方法
//同步线程
class SyncTHread implements Runnable{
private static int number;
public SyncTHread(){
number=0;
}
public synchronized static void method(){
for (int i=0;i<5;i++){
try {
System.out.println(Thread.currentThread().getName()+":"+(number++));
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public synchronized void run(){
method();
}
}
//测试
public class testSynThread {
public static void main(String[] args) {
SyncTHread syncTHread1 = new SyncTHread();
SyncTHread syncTHread2 = new SyncTHread();
Thread thread1 = new Thread(syncTHread1, "SyncThread1");
Thread thread2 = new Thread(syncTHread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
//运行结果
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
在测试代码中,syncThread1和syncThread2是SyncThread的两个对象,但是在thread1和thread2并发执行时却保持了线程同步,因为run()中调用了静态方法method(),而synchronized修饰的静态方法锁定的是这个类的所有对象,所以syncThread1和syncThread2相当于使用了同一把锁。
(2)lock
class LockThread implements Runnable{
private static int number;
private ReentrantLock lock=new ReentrantLock();
public LockThread(){
number=0;
}
public void method(){
try {
lock.lock();
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(number++));
Thread.sleep(100);
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void run(){
method();
}
}
//测试
public class testLock{
public static void main(String[] args) {
LockThread lockThread1 = new LockThread();
LockThread lockThread2 = new LockThread();
Thread thread1 = new Thread(lockThread1, "lockThread1");
Thread thread2 = new Thread(lockThread2, "lockThread2");
thread1.start();
thread2.start();
}
}
//运行结果
lockThread1:0
lockThread2:1
lockThread1:2
lockThread2:2
lockThread2:3
lockThread1:4
lockThread1:5
lockThread2:5
lockThread2:6
lockThread1:7