zoukankan      html  css  js  c++  java
  • Java并发——synchronized和ReentrantLock的联系与区别

    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. 两者关系与区别汇总

    从上面两个实例的实现可以引出SynchronizedLock的关系:

    1ReentrantLocksynchronized有相同的并发性和内存语义。但是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对象。

    5ReentrantLock可以获取各种锁的信息,比如可以查看锁的状态,锁是否被锁上了。可以查看当前有多少线程在等待锁。

    6Lock使用更灵活、性能更佳。性能的优化体现在,当许多线程都想访问共享资源时,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会一直等待永远不会获取到的锁。


  • 相关阅读:
    leetcode-237-删除链表中的节点
    leetcode-125-验证回文串
    leetcode-217-存在重复元素
    leetcode-189-旋转数组
    leetcode-121-买卖股票的最佳时机
    leetcde-27-移除元素
    redis相关
    leetcode-26-删除排序数组中的重复项
    leetcode-16-最接近的三数之和
    基础-递归
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461534.html
Copyright © 2011-2022 走看看