0 前言
本文通过使用synchronized以及Lock分别完成“生产消费场景”,再引出两种锁机制的关系和区别,以及一些关于锁的知识点。
本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52401134
1. synchronized, wait, notify结合实现生产消费场景
1.1 生产者类
/* *@author SEU_Calvin *@date 2016/09/01 */ public class Producer implements Runnable { @Override public void run() { int count = LockTest.count; while (count <= 3) { synchronized (LockTest.obj) { LockTest.count++; System.out.println("生产者生产产品...现在有"+LockTest.count+"个"); if(LockTest.count >= 3){ System.out.println("现在产品充足,待消费..."); LockTest.obj.notify();// 主动释放对象锁 try { LockTest.obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
1.2 消费者类
/* *@author SEU_Calvin *@date 2016/09/01 */ public class Consumer implements Runnable { @Override public synchronized void run() { int count = LockTest.count; while (count >= 0) { synchronized (LockTest.obj) { LockTest.count--; System.out.println("消费者消费产品...现在有"+LockTest.count+"个"); if(LockTest.count <= 0){ System.out.println("现在产品缺货,待生产..."); LockTest.obj.notify(); // 主动释放对象锁 try { LockTest.obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
1.3 测试类
public class LockTest { public static final Object obj = new Object(); public static int count = 0; public static void main(String[] args) { new Thread(new Producer()).start(); new Thread(new Consumer()).start(); } }
1.4 运行结果
这个实例比较简单,主要是通过synchronized,wait, notify结合来实现线程的顺序切换。
2. Lock类
除了wait()、notify()以及synchronized协作完成线程同步之外,使用Lock也可以达到同样的目的。
/* *@author SEU_Calvin *@date 2016/09/01 */ public class ReentrantLockTest { private volatile int stopFalg = 10;//控制程序执行次数 private volatile int count = 0; private Lock lock = new ReentrantLock(); private ArrayList<Thread> threads = new ArrayList<Thread>(); public static void main(String[] args) throws InterruptedException { final ReentrantLockTest test = new ReentrantLockTest(); new Thread("Producer") { //开启生产者线程 public void run() { test.threads.add(this); while (test.stopFalg > 0) { test.operateResource(this.getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; }.start(); Thread.sleep(1000); //保证生产者线程先启动,继而两者同时生产、消费 new Thread("Consumer") { //开启消费者线程 public void run() { test.threads.add(this); while (test.stopFalg > 0) { test.operateResource(this.getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; }.start(); } public void operateResource(String id) { lock.lock(); //lock开启,锁两个线程都会访问到的同一区域的代码 try { if ("Producer".equals(id)) { //判断线程类型 if (count < 10) { count++; stopFalg--; System.out.println("Producer=>" + count); } } else if ("Consumer".equals(id)) {//判断线程类型 if (count > 0) { count--; System.out.println("Consumer=>" + count); } } } finally { lock.unlock();//必须unlock } } }
2.1 运行结果
3. 两者关系与区别汇总
从上面两个实例的实现可以引出Synchronized和Lock的关系:
(1)ReentrantLock与synchronized有相同的并发性和内存语义。但是ReentrantLock还包含了区别于synchronized的以下特性。
(2)等待可中断:在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待。有效防止了死锁。
lock();//用来获取锁,如果锁已被其他线程获取,则进行等待 tryLock(); //尝试获取锁,若成功返回true,失败(即锁已被其他线程获取)则返回false tryLock(long timeout, TimeUnit unit); //在拿不到锁时会等待一定的时间 //两个线程同时通过lock.lockInterruptibly()想获取某个锁时 //若线程A获取到了锁,而线程B在等待 //线程B调用threadB.interrupt()方法能够中断线程B的等待过程 lockInterruptibly();
(3)公平锁:按照申请锁的顺序来获得锁。synchronized是非公平锁。ReentrantLock可以通过构造函数实现公平锁。
new RenentrantLock(boolean fair);
(4)绑定多个Condition,添加多个检控条件:通过多次new Condition可以获得多个Condition对象。
(5)ReentrantLock可以获取各种锁的信息,比如可以查看锁的状态,锁是否被锁上了。可以查看当前有多少线程在等待锁。
(6)Lock使用更灵活、性能更佳。性能的优化体现在,当许多线程都想访问共享资源时,JVM可以花更少的时间来调度线程,把更多时间用在执行线程上。
(7)由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock()。同时由于ReentrantLock是类,使用时需要import相关类。
(8)总结:在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
4. 可重入锁
synchronized以及Lock类锁,两者都是可重入锁。
class MyClass { public synchronized void method1() { method2(); } public synchronized void method2() { } }
可重入锁的意思是,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是线程A已经持有了该对象的锁,这样线程A会一直等待永远不会获取到的锁。