zoukankan      html  css  js  c++  java
  • java线程(4)——线程同步的锁技术

    同步

    同步,字面来看,有点一起工作的意思。但在线程同步中,“同”意为协同、互相配合。

    比如:

    A、B两个线程,并不是说两个线程必须同时一起工作,而是说互相配合工作,在某个时间可能线程A要等线程B去工作,之后线程A才能继续工作。如果理解不了,可以参考java线程(2)——模拟生产者与消费者中的例子。

    思考:
    为什么会有线程同步?上面例子中线程A为什么要等B工作之后才能继续工作?

    在生产者和消费者的例子中,作为消费者,如果生产者还没生产,他就没办法消费只能等着,否则就会出现问题。同样,生产者也不能无止尽的生产,毕竟篮子的容量是有限的。

    如果这个例子看不出来问题的严重性,我们可以想想银行取钱的例子,出现问题这后果就相当严重了。所以,多线程如果使用不当,很容易出现线程不安全的问题。尤其涉及同步问题时,一定要小心使用。

    实现方式

    主要有两种:synchronized和lock,前者被称为内置锁,后者叫外置锁。
    关于这两者的不同之处,介绍之后再比较。

    1、 synchronized关键字

    在消费者和生产者的例子中,我们使用到了synchronized关键字,将篮子的“取”和“放”都做了限制,来实现线程同步。

    2、lock

    先来看一下他的接口定义,也是在包java.util.concurrent下。

    package java.util.concurrent.locks;
    
    public interface Lock {
      void lock();
      void lockInterruptibly() throws InterruptedException;
      boolean tryLock();
      void unlock();
      Condition newCondition();
    
    }

    在上篇博客中介绍了一个使用synchronized关键字实现打印当前时间的方法,我们这里来修改一下。

       /**
         * 获得当前时间
         */
        public static void getTime() {
    
            lock.lock();
            try {
                System.out.println("1、进入获取时间方法=====");
                Date date = new Date();
                DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String time = format.format(date);
                System.out.println("2、" + Thread.currentThread().getName() + ":"
                        + time);
                System.out.println("3、获取时间成功=====");
                System.out.println();
            } finally {
                lock.unlock();
            }
        }
    
    //调用
        static Lock lock=null;
        public static void main(String[] args) {
            lock = new ReentrantLock();
    
            for (int i = 0; i < 100; i++) {
    
                new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        getTime();
    
                    }
    
                }).start();
    
            }
        }

    结果同样满足条件:

    这里写图片描述

    读写锁

    Lock中的另一个亮点就是对读写锁的支持。在读写锁中,把对共享资源的访问者划分为读者和写者,读者进行读访问,写者进行写操作。但是,一个读写锁同时能有一个或多个读者,但不能同时既有读者又有写者。

    java中的类为ReadWriteLock,提供了读锁和写锁的方法,返回类型都是Lock

    public interface ReadWriteLock {
    
        Lock readLock();
    
        Lock writeLock();
    }

    举个例子:

    在例子中,data为所要读写的数据,提供了读和写的方法。

        private Object data = null;// 共享数据
        /**
         * 读数据
         */
        public void get() {
            try {
              //准备数据
                System.out.println(Thread.currentThread().getName()
                        + " be ready to read data!");
                //sleep一段时间,主要为了更明显的体现出差别,无实际意义
                Thread.sleep((long) (Math.random() * 1000));
                //打印取出数据
                System.out.println(Thread.currentThread().getName()
                        + "have read data :" + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 写数据
         * 
         * @param data
         */
        public void put(Object data) {
            try {
                System.out.println(Thread.currentThread().getName()
                        + " be ready to write data!");
                Thread.sleep((long) (Math.random() * 1000));
                this.data = data; // 给data赋值
                System.out.println(Thread.currentThread().getName()
                        + " have write data: " + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } 
    
        }

    执行结果:

    这里写图片描述

    从结果上看,当程序刚启动时,多个读写线程同时触发,等待执行。无法实现“多读”,也影响了写的操作。

    那么,现在来看下加上读写锁之后的效果。

    ReadWriteLock rwl = new ReentrantReadWriteLock();
    
        /**
         * 读数据
         */
        public void get(){
            rwl.readLock().lock(); //读锁
            try {
                ....略
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally{
                rwl.readLock().unlock(); //释放读锁
            }
        }

    与上面没有使用读写锁相比,最大的不同在于,他可以实现“多读”,并且“读”与“写”互斥。例如:Thread0,2,4进入之后,可以同时读数据。并没有多条写数据同时写。

    这里写图片描述

    点睛之笔

    1)Lock与ReadWriteLock

    以Lock第一个例子ReentrantLock来说,他实现了标准的互斥操作,有点“独占”的感觉。这种情况下,不允许多读,多写,读写等情况发生。

    读写锁ReadWriteLock中,允许多个读线程同时访问一个资源,但不允许多个写线程同时访问。更符合实际操作和需要。



    2)Lock与synchronized

    synchronized比较武断,只能实现互斥,无法实现读写锁的允许多读操作。在并发量比较小的情况下,他是一个不错的选择。但并发量较大的话,他的性能下降很严重。

    Lock锁技术除了第一点介绍的优点外,他还具有可重入性,即可以进行多次加锁,也更具公平性。不过为了避免死锁,需要在finally中释放锁。

  • 相关阅读:
    让ios支持openssl
    数组
    NSValue
    音频
    NSObject分类
    NSJSONSerialization
    Java并发编程-深入探讨synchronized实现原理
    设计模式-挖掘工厂模式应用场景-全面分析工厂模式原理
    Spring插件安装
    Java并发编程-深入Java同步器AQS原理与应用-线程锁必备知识点
  • 原文地址:https://www.cnblogs.com/saixing/p/6730220.html
Copyright © 2011-2022 走看看