Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
一、Synchronized 和 Lock的概念
Synchronized 是 Java 并发编程中很重要的关键字,另外一个很重要的是 volatile。Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。Synchronized 很像生活中的锁例子,进入由Synchronized 保护的代码区首先需要获取 Synchronized 这把锁,其他线程想要执行必须进行等待。Synchronized 锁住的代码区域执行完成后需要把锁归还,也就是释放锁,这样才能够让其他线程使用。
Lock 是 Java并发编程中很重要的一个接口,它要比 Synchronized 关键字更能直译”锁”的概念,Lock需要手动加锁和手动解锁,一般通过 lock.lock() 方法来进行加锁, 通过 lock.unlock() 方法进行解锁。与 Lock 关联密切的锁有 ReetrantLock 和 ReadWriteLock。
ReetrantLock 实现了Lock接口,它是一个可重入锁,内部定义了公平锁与非公平锁。
ReadWriteLock 一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。ReentrantReadWirteLock实现了ReadWirteLock接口,并未实现Lock接口。
二、Lock 和 synchronize 有以下不同点:
- 存在层面:Syncronized 是Java 中的一个关键字,存在于 JVM 层面,Lock 是 Java 中的一个接口
- 锁的释放条件:1. 获取锁的线程执行完同步代码后,自动释放;2. 线程发生异常时,JVM会让线程释放锁;Lock 必须在 finally 关键字中释放锁,不然容易造成线程死锁
- 锁的获取: 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待。在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法,如果尝试获取到锁,则不用一直等待,可进行中断锁
- 锁的状态:Synchronized 无法判断锁的状态,Lock 则可以判断
- 锁的类型:Synchronized 是可重入,不可中断,非公平锁;Lock 锁则是可重入,可判断,可公平锁
- 锁的性能:Synchronized 适用于少量同步的情况下,性能开销比较大。Lock 锁适用于大量同步阶段:
- Lock 锁可以提高多个线程进行读的效率(使用 readWriteLock)
- 在竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
- ReetrantLock 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。
三、代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class LTicket { private int number = 30;
private final ReentrantLock lock = new ReentrantLock();
public void sale() { lock.lock();; try { if (number>0) { System.out.println(Thread.currentThread().getName()+": 卖出"+number-- + "剩余:"+number); } } finally { lock.unlock(); }
} }
public class LSaleTicket { public static void main(String[] args) { LTicket ticket = new LTicket();
new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "AA").start();
new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "BB").start();
new Thread(()-> { for (int i = 0; i < 40; i++) { ticket.sale(); } }, "CC").start();
}
}
|
测试结果:
![](/../images/java/a16.png)
四、 多线程编程步骤
第一步 创建资源类,在资源类创建属性和操作方法。
第二步 在资源类操作方法中(1.判断 2.干活 3.通知)。
第三步 创建多个线程,调用资源类的操作方法。
第四步 防止虚假唤醒问题。
五、Lock实现 - 线程间通信
例子: 有两个线程,实现对一个初始值是0的变量,一个线程对值+1,另外一个线程对值-1.
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| class Share { private int number = 0; private Lock lock = new ReentrantLock(); Condition condition = lock.newCondition();
public void incr() throws InterruptedException { lock.lock(); try { while (number != 0) { condition.await(); } number++; System.out.println(Thread.currentThread().getName() + " ::" + number); condition.signalAll(); } finally { lock.unlock(); } }
public void decr() throws InterruptedException { lock.lock(); try { while (number != 1) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() + " ::" + number); condition.signalAll(); } finally { lock.unlock(); }
} }
public class ThreadDemo2 { public static void main(String[] args) { Share share = new Share(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start();
new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start();
new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start();
new Thread(() -> { for (int i = 1; i <= 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } }
|
运行结果:
![](/../images/java/a17.png)