1.java多线程中,可以使用synchronized关键字来实现线程间的同步互斥工作,其实还有个更优秀的机制来完成这个同步互斥的工作——Lock对象,主要有2种锁:重入锁和读写锁,它们比synchronized具有更强大的功能,并且有嗅探锁定、多路分支等功能。
2.ReentrantLock(重入锁)
重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,否则会造成锁永远无法释放,其他线程永远进不来的结果(使用起来跟synchronized很像,并且,在jdk1.8之前,ReentrantLock比synchronized性能好,jdk1.8对synchronized做了优化,性能接近了。但是ReentrantLock比synchronized灵活)
代码示例:
1 package lock020; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class UseReentrantLock { 7 8 private Lock lock = new ReentrantLock(); 9 10 public void method1(){ 11 try { 12 lock.lock(); 13 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1.."); 14 Thread.sleep(1000); 15 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1.."); 16 Thread.sleep(1000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } finally { 20 21 lock.unlock(); 22 } 23 } 24 25 public static void main(String[] args) { 26 27 final UseReentrantLock ur = new UseReentrantLock(); 28 Thread t1 = new Thread(new Runnable() { 29 @Override 30 public void run() { 31 ur.method1(); 32 } 33 }, "t1"); 34 35 t1.start(); 36 37 Thread t2 = new Thread(new Runnable() { 38 @Override 39 public void run() { 40 ur.method1(); 41 } 42 }, "t2"); 43 44 t2.start(); 45 46 try { 47 Thread.sleep(10); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 //System.out.println(ur.lock.getQueueLength()); 52 } 53 54 55 }
执行以后,可以发现,t1和t2是串行执行method1的
3.ReentrantLock锁的等待与通知
synchronized关键字里,有Object的wait()方法和notify()/notifyAll()方法进行多线程之间的工作协调。而同样的,Lock也有自己的等待/通知类,它就是Condition。这个Condition一定是针对某一把具体的锁的,就是说,只有有锁的基础之才会产生Condition
代码实现:
1 package lock020; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 public class UseCondition { 8 9 private Lock lock = new ReentrantLock(); 10 private Condition condition = lock.newCondition(); 11 12 public void method1(){ 13 try { 14 lock.lock(); 15 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态.."); 16 Thread.sleep(3000); 17 System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁.."); 18 condition.await(); // Object wait,释放锁 19 System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行..."); 20 } catch (Exception e) { 21 e.printStackTrace(); 22 } finally { 23 lock.unlock(); 24 } 25 } 26 27 public void method2(){ 28 try { 29 lock.lock(); 30 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入.."); 31 Thread.sleep(3000); 32 System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒.."); 33 condition.signal(); //Object notify 34 } catch (Exception e) { 35 e.printStackTrace(); 36 } finally { 37 lock.unlock(); 38 } 39 } 40 41 public static void main(String[] args) { 42 43 final UseCondition uc = new UseCondition(); 44 Thread t1 = new Thread(new Runnable() { 45 @Override 46 public void run() { 47 uc.method1(); 48 } 49 }, "t1"); 50 Thread t2 = new Thread(new Runnable() { 51 @Override 52 public void run() { 53 uc.method2(); 54 } 55 }, "t2"); 56 57 t1.start(); 58 t2.start(); 59 } 60 61 62 63 }
以上代码,实现了wait/nofity的功能
ps:不同的线程之间,可以用不同的Condition对象来进行通信。例如t1唤醒t2用 Condition1,t3唤醒t4用Condition2,这也是比wait/notify灵活的地方
3.公平锁和非公平锁
公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。(默认是非公平锁)
可以通过构造方法指定参数是ture(公平)或者false(非公平),一般说来,非公平锁比公平锁性能要好,因为公平锁要维护顺序
4.ReentrantLock锁与synchronized的区别
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
5.ReenTrantReadWriteLock(读写锁)
核心其实就是实现读写分离。在高并发的情况下,尤其是读多写少的情况下,性能远高于重入锁
之前的ReentrantLock和synchronized的使用时,同一时间内,只能一个线程访问被锁定的代码。读写锁不同,其本质是2个锁,即读锁和写锁。在读锁下,多个线程可以并发访问,但是在写锁下,只能串行访问
口诀:读读共享,写写互斥,读写互斥
代码示例:
1 package lock021; 2 3 import java.util.concurrent.locks.ReentrantReadWriteLock; 4 import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; 5 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; 6 7 public class UseReentrantReadWriteLock { 8 9 private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); 10 private ReadLock readLock = rwLock.readLock(); 11 private WriteLock writeLock = rwLock.writeLock(); 12 13 public void read(){ 14 try { 15 readLock.lock(); 16 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); 17 Thread.sleep(3000); 18 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } finally { 22 readLock.unlock(); 23 } 24 } 25 26 public void write(){ 27 try { 28 writeLock.lock(); 29 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); 30 Thread.sleep(3000); 31 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); 32 } catch (Exception e) { 33 e.printStackTrace(); 34 } finally { 35 writeLock.unlock(); 36 } 37 } 38 39 public static void main(String[] args) { 40 41 final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock(); 42 43 Thread tr1 = new Thread(new Runnable() { 44 @Override 45 public void run() { 46 urrw.read(); 47 } 48 }, "t1"); 49 Thread tr2 = new Thread(new Runnable() { 50 @Override 51 public void run() { 52 urrw.read(); 53 } 54 }, "t2"); 55 Thread tw3 = new Thread(new Runnable() { 56 @Override 57 public void run() { 58 urrw.write(); 59 } 60 }, "t3"); 61 Thread tw4 = new Thread(new Runnable() { 62 @Override 63 public void run() { 64 urrw.write(); 65 } 66 }, "t4"); 67 68 tr1.start(); 69 tr2.start(); 70 71 tw3.start(); 72 tw4.start(); 73 74 } 75 }
模拟代码可发现:
如果是都是读操作,基本是同时进行的
如果有写操作,是要锁定的
6.锁的优化
(1)避免死锁
(2)减小锁的持有时间
(3)减小锁的粒度
(4)锁的分离
(5)尽量使用无锁的操作,如原子操作(Atomic类系列)、volatile关键字
7.分布式锁的概念
2台机器都部署了项目,显然,运行的jvm是不同的,如果t1线程在机器A,t2线程在机器B,同时要访问同一段锁定的代码块
显然,jvm的锁机制是无法处理这种情况的,这时候要考虑第三方帮助实现,如zookkeeper