Synchronized和Lock的区别
原始构造
- synchronized是关键子,属于jvm层面
- Lock是具体类,属于api层面的锁
使用方法
- synchronized不需要用户手动释放锁,执行完代码后会自动释放对锁的占用
- lock需要手动释放锁
等待是否可中断
- synchronized不可中断,除非抛出异常或者正常运行完成
- lock可中断,1. 设置超时时间tryLock(long time,TimeUnit unit),2. lockInterruptibly()代码块中,调用interrupt()方法可中断
加锁是否公平
- synchronized不公平
- ReentrantLock默认不公平,构造方法传入true,公平,传入false,不公平
锁绑定多个条件condition
-
synchronized 没有
-
ReentrantLock使用多个condition用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized一样要么随便唤醒一个线程,要么唤醒全部线程
/** * 多线程之间按顺序调用,实现A-->B-->C三个线程启动,要求如下: * AA打印5次,BB打印10次,CC打印15次 * 循环10轮 */ public class SyncAndReentrantLockDemo { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(() -> { for (int i = 0; i < 10; i++) { shareResource.print5(); } }, "AA").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { shareResource.print10(); } }, "BB").start(); new Thread(() -> { for (int i = 0; i < 15; i++) { shareResource.print15(); } }, "CC").start(); } } class ShareResource { private int number = 1; private Lock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); public void print5() { lock.lock(); try { while (number != 1) { c1.await(); } for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } number = 2; c2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print10() { lock.lock(); try { while (number != 2) { c2.await(); } for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } number = 3; c3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15() { lock.lock(); try { while (number != 3) { c3.await(); } for (int i = 0; i < 15; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } number = 1; c1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
公平锁,非公平锁
-
公平锁:在多线程环境下,按照申请锁的顺序获得锁
-
非公平锁:在多线程环境下,不一定按照申请锁的顺序获得锁,后申请锁的线程可以优先获得锁,有可能造成优先级反转或者饥饿现象
-
在ReentrantLock中,可以通过构造函数中的boolean值来指定是否为公平锁,默认是非公平锁,非公平锁的优点在于吞吐量比公平锁大
Lock lock=new ReentrantLock(true);//公平锁 Lock lock1=new ReentrantLock();//非公平锁,默认为false
-
synchronized是一种非公平锁
可重入锁(又名递归锁)
- 线程可以进入任何一个他已经拥有的锁所同步着的代码
- ReentrantLock/synchronized就是典型的可重入锁
- 可重入锁的最大作用是避免死锁
- 代码验证ReentrantLock/synchronized是可重入锁
public class LockDemo2 { public static void main(String[] args) { Person person = new Person(); new Thread(() -> { person.fun1(); }, "t1").start(); new Thread(person, "t2").start(); } } class Person implements Runnable { //fun1,fun2验证synchronized是可重入锁 public synchronized void fun1() { System.out.println(Thread.currentThread().getName() + " invoked fun1"); fun2(); } public synchronized void fun2() { System.out.println(Thread.currentThread().getName() + " invoked fun2"); } @Override public void run() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } fun3(); } //验证ReentrantLock是可重入锁 Lock lock = new ReentrantLock(); public void fun3() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " invoked fun3"); fun4(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void fun4() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + " invoked fun4"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }
自旋锁
-
定义:尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获得锁
-
好处:减少线程上下文切换的消耗
-
坏处:循环会消耗cpu
-
实现一个自旋锁(CAS实现)
/** * 实现一个自旋锁 * 自旋锁好处:循环比较获取直到成功为止,没有类似wait的阻塞 * <p> * 通过CAS循环比较的方式完成自旋锁,A线程先获取锁,等待5秒,B线程进来时发现锁被使用,一直循环等待直到A释放锁 */ public class SpinLockDemo { AtomicReference<Thread> atomicReference = new AtomicReference<>(); /** * 获取锁 * 1. 判断原子引用值是否为空 * 2. 若为空,则将原子引用值设置为当前线程 * 3. 若不为空,循环等待值为空 */ public void myLock() { Thread thread = Thread.currentThread(); while (!(atomicReference.compareAndSet(null, thread))) { //自旋锁循环获取锁 System.out.println(thread.getName() + " 正在自旋循环尝试获取锁"); } System.out.println(thread.getName() + " lock"); } /** * 释放锁 * 1. 将原子引用值设置为空 */ public void myUnLock() { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread, null); System.out.println(thread.getName() + " unlock"); } public static void main(String[] args) throws InterruptedException { SpinLockDemo spinLockDemo = new SpinLockDemo(); new Thread(() -> { spinLockDemo.myLock(); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLockDemo.myUnLock(); } }, "A").start(); //保证上面的线程先执行 TimeUnit.SECONDS.sleep(1); new Thread(() -> { //5秒之后才能获取到锁 spinLockDemo.myLock(); spinLockDemo.myUnLock(); }, "B").start(); } }
读写锁/独占锁/共享锁
-
独占锁:该锁一次只能被一个线程持有。ReentrantLock,Synchronized都是独占锁
-
共享锁:该锁可被多个线程持有
-
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
-
读写锁代码示例
package com.yls.thread.lock; import java.util.HashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 当一个线程对某个资源进行写操作时,应该是一个整体(原子+独占),不能被其他线程打断 */ public class ReadWriteLockDemo { public static void main(String[] args) throws InterruptedException { MyCache myCache = new MyCache(); for (int i = 0; i < 5; i++) { int finalI = i; new Thread(() -> { myCache.put(finalI, finalI); }).start(); } Thread.sleep(1); for (int i = 0; i < 5; i++) { int finalI = i; new Thread(() -> { myCache.get(finalI); }).start(); } } } //模拟缓存 class MyCache { private HashMap<Integer, Integer> hashMap = new HashMap<>(); private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //缓存中插入值,使用独占锁 public void put(int key, int value) { readWriteLock.writeLock().lock(); System.out.println(key + "准备放入:"); hashMap.put(key, value); System.out.println(key + "放入成功"); readWriteLock.writeLock().unlock(); } //获取数据,共享锁 public int get(int key) { readWriteLock.readLock().lock(); System.out.println("准备获取" + key + ":"); int value = hashMap.get(key); System.out.println(key + "获取成功"); readWriteLock.readLock().unlock(); return value; } public void clear() { hashMap.clear(); } }
CountDownLatch,CyclicBarrier,SemaPhore
- CountDownLatch:减为0时,执行后面的线程
- CyclicBarrier:加到某一个值时,执行指定的线程
- SemaPhore:可伸缩,对统一资源初始化一个锁池,需要使用时从锁池获取锁,使用完后释放锁还给锁池,若锁被用完,则等待其它线程释放锁