zoukankan      html  css  js  c++  java
  • synchronized和lock比对

    前言:在上面的博客说了synchronized的一些用法,下面我们再来看看lock,这个出现频率也是非常高的一个。

    1:获取Lock锁的几种方式

    前面说了synchronized有锁对象和锁类对象,当某个线程获取锁其他线程必须等待执行完毕才可继续进行,比如线程A先获取锁,但是出现异常导致的后果就是线程B无法获取锁,会出现死锁的情况(http://www.cnblogs.com/LipeiNet/p/6475851.html),那么我们一起看看Lock是如何解决的。lock有4种方式来获取锁

    1:lock.lock() 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁。此种模式和synchronized一样但是不会出现死锁

    public class Lock1 {
        static int value = 0;
        static Lock lock = new ReentrantLock();
    
        static class Task1 implements Runnable {
            public void run() {
                System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
                lock.lock();
                try {
                    for (int i = 0; i < 1000000; i++) {
                        value++;
                    }
                    System.out.println(value);
                } finally {
                    lock.unlock();
                }
            }
        }
        static class Task2 implements Runnable {
            public void run() {
                System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
                lock.lock();
                try {
                    for (int i = 0; i < 1000000; i++) {
                        value++;
                    }
                    System.out.println(value);
                } finally {
                    lock.unlock();
                }
            }
        }
    
        public static void main(String[] args) {
            ExecutorService service= Executors.newCachedThreadPool();
            service.execute(new Task1());
            service.execute(new Task2());
            service.shutdown();
        }
    }

    输出结果很明显其中一个value是1000000,一个是2000000,效果和synchronized是一样的。但是如果我们去掉lock以后的结果呢,很明显会错,如下图这样

    为啥会出现这样情况呢,是由于cpu速度极快,每次处理完毕之后并没有立即把数值放入Java内存中,而是放在写缓存区,然后由写缓存区同步到Java内存中,这样一样,如果线程1计算结果是2,但是还是到内存中,导致线程2以为value值还是1所以会重复计算,还有从结果我们也可以看出value值并不是100000说明2个线程是同步执行的。

     2:lock.tryLock();

    这个方法和synchronized有所不同,synchronized和lock都会等待直到获取锁。如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;当然我们可以利用while循环一直等待,直到获取锁然后进行。代码如下

    public class Lock2 {
        public static void main(String[] args) {
            final Lock lock = new ReentrantLock();
            Thread t1 = new Thread(new Runnable() {
                public void run() {
                    String tName = Thread.currentThread().getName();
                    while (!lock.tryLock()) {
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("等待获取锁");
                    }
                    try {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(tName + ":" + i);
                        }
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                public void run() {
                    String tName = Thread.currentThread().getName();
                    while (!lock.tryLock()) {
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("等待获取锁");
                    }
                    try {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(tName + ":" + i);
                        }
    
                    } catch (Exception e) {
                        System.out.println(tName + "出错了!!!");
                    } finally {
                        System.out.println(tName + "释放锁!!");
                        lock.unlock();
                    }
                }
            });
            t1.start();
            t2.start();
        }
    }

    3:lock.trylock(long time, TimeUnit unit)如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;unit是time的时间单位比如TimeUnit.SECONDS就是表示秒

    4:lock.lockInterruptibly()如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。也就是说如何线程没有被中断和lock.lock()的作用一样。但是如何线程被中断了,那么此时这个线程不会有任何的响应,想象这么一个场景,线程A和线程B同时执行任务,但是必须等待线程B先执行,但是执行过程中突然线程A突然被中断,那么这个时候就可能出现死锁,哪怕是在finally中加入unlock,这个时候我们就要采用lockInterruptibly()了。代码如下

    public class Lock3 {
        public static void main(String[] args) {
            final Lock lock = new ReentrantLock();
            final Thread thread1 = new Thread(new Runnable() {
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                        System.out.println("等待被中断");
                        lock.lockInterruptibly();
                    } catch (InterruptedException e) {
                        System.out.println("我被中断了");
                    } finally {
                        lock.unlock();
                    }
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                public void run() {
                    lock.lock();
                    try {
                        TimeUnit.SECONDS.sleep(4);
                    } catch (InterruptedException e) {
    
                    }
                    thread1.interrupt();
                    System.out.println("线程1已经被中断");
                }
            });
            thread1.start();
            thread2.start();
        }
    }

    2:读锁和写锁

     在开发中我们最好的愿望就是写的时候加锁,但是读的时候不加锁这样会大大的提升效率,但是采用synchronized却无法满足我们的要求,如果在读的方法面前加锁那么所有的读都需要等待,如果不加锁的话那么如果现在A,B2个线程读取,C线程写入可能导致的后果就是A,B2个线程取得数据不一致,明明同一种业务场景但是获取值却不同。好了lock的读锁和写锁帮助我们实现这种功能。

    public class ReadWriteLockTest {
        private int value = 0;
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
        public void add(int value) {
            Lock writeLock = readWriteLock.writeLock();
            writeLock.lock();
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println("添加开始时间:" + new Date());
                this.value += value;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
            }
        }
    
        public void getValue() {
            Lock readLock = readWriteLock.readLock();
            readLock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("获取开始时间:" + new Date());
                System.out.println(value);
            } catch (InterruptedException e) {
    
            } finally {
                readLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            final ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
            Runnable task1 = new Runnable() {
                public void run() {
                    readWriteLockTest.add(100);
                }
            };
            Runnable task2 = new Runnable() {
                public void run() {
                    readWriteLockTest.getValue();
                }
            };
            ExecutorService service = Executors.newCachedThreadPool();
            for (int i=0;i<2;i++){
                service.execute(task1);
            }
            for (int i=0;i<2;i++){
                service.execute(task2);
            }
            for (int i=0;i<2;i++){
                service.execute(task1);
            }
            service.shutdown();
        }
    }

    运行结果:

    从这个结果我们很明显的可以总结读锁和写锁

    第一:如果执行写的时候,读和写必须等待

    第二:如果执行读的时候,写必须等待,而读却不用等待

    也就是说读和写必须存在先后顺序,不管是先读还是先写。

    3:总结

    相同点:lock能实现synchronized所有可以实现的

    不同点:

    1:lock不容易出现死锁,而synchronized如果某个线程出现异常就会产生死锁

    2:lock更加灵活,可以通过tryLock来验证是否获取锁,在线程中断也同样可以处理

    3:lock有读写锁在并发量大的时候具有很大的优势,因为读的情况一般会比写多很多

  • 相关阅读:
    割点和割边
    差分约束
    错题本(持续更新)
    高中语文小说赏析问题
    CSPS2019游记
    【USACO09FEB】改造路Revamping Trails
    【SDOI2013】森林
    Nozaki_Chiyo的代码盒
    【HAOI2015】树上染色
    kruskal重构树
  • 原文地址:https://www.cnblogs.com/LipeiNet/p/6532842.html
Copyright © 2011-2022 走看看