zoukankan      html  css  js  c++  java
  • java并发:线程同步机制之Lock

    一、初识Lock

      Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock,其核心方法是lock()、unlock()、tryLock(),实现类有ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock,下图展示了Lock接口中定义的方法:

    二、ReentrantLock

    (1)初识ReentrantLock

      Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点,比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java5通过Lock接口提供了更复杂的控制来解决这些问题,《Java并发编程实战》一书有如下描述:

    (2)示例

    此处我们看下面这两个例子,请注意其中ReentrantLock使用方式的区别:

    (1)此处两个方法之间的锁是独立

    package com.test;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockDemo {
        public static void main(String[] args) {
            final Countx ct = new Countx();
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        ct.get();
                    }
                }.start();
            }
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        ct.put();
                    }
                }.start();
            }
        }
    }
    
    class Countx {
    
        public void get() {
            final ReentrantLock lock = new ReentrantLock();
            try {
                lock.lock();// 加锁
                System.out.println(Thread.currentThread().getName() + "get begin");
                Thread.sleep(1000L);// 模仿干活
                System.out.println(Thread.currentThread().getName() + "get end");
                lock.unlock(); // 解锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void put() {
            final ReentrantLock lock = new ReentrantLock();
            try {
                lock.lock();// 加锁
                System.out.println(Thread.currentThread().getName() + "put begin");
                Thread.sleep(1000L);// 模仿干活
                System.out.println(Thread.currentThread().getName() + "put end");
                lock.unlock(); // 解锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    运行结果如下(每次运行结果都是不一样的,仔细体会一下):

    Thread-1get begin
    Thread-0get begin
    Thread-2put begin
    Thread-3put begin
    Thread-0get end
    Thread-3put end
    Thread-1get end
    Thread-2put end

    (2)此处两个方法之间使用相同的锁

    package com.test;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockDemo {
        public static void main(String[] args) {
            final Countx ct = new Countx();
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        ct.get();
                    }
                }.start();
            }
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        ct.put();
                    }
                }.start();
            }
        }
    }
    
    class Countx {
        final ReentrantLock lock = new ReentrantLock();
    
        public void get() {
            // final ReentrantLock lock = new ReentrantLock();
            try {
                lock.lock();// 加锁
                System.out.println(Thread.currentThread().getName() + "get begin");
                Thread.sleep(1000L);// 模仿干活
                System.out.println(Thread.currentThread().getName() + "get end");
                lock.unlock(); // 解锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public void put() {
            // final ReentrantLock lock = new ReentrantLock();
            try {
                lock.lock();// 加锁
                System.out.println(Thread.currentThread().getName() + "put begin");
                Thread.sleep(1000L);// 模仿干活
                System.out.println(Thread.currentThread().getName() + "put end");
                lock.unlock(); // 解锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    运行结果如下(每次运行结果都是一样的):

    Thread-0get begin
    Thread-0get end
    Thread-1get begin
    Thread-1get end
    Thread-2put begin
    Thread-2put end
    Thread-3put begin
    Thread-3put end

    三、ReadWriteLock

    (1)初识ReadWriteLock

      Java中的ReadWriteLock是什么?一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果,Java中的ReadWriteLock是Java5中新增的一个接口,提供了readLock和writeLock两种锁机制。一个ReadWriteLock维护一对关联的锁,一个用于只读操作,一个用于写,在没有写线程的情况下,一个读锁可能会同时被多个读线程持有,写锁是独占的。

    我们来看一下ReadWriteLock的源码:

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

      从源码上面我们可以看出来ReadWriteLock并不是Lock的子接口,只不过ReadWriteLock借助Lock来实现读写两个锁并存、互斥的操作机制。在ReadWriteLock中每次读取共享数据就需要读取锁,当需要修改共享数据时就需要写入锁,看起来好像是两个锁,但是并非如此。

      ReentrantReadWriteLock是ReadWriteLock在java.util里面唯一的实现类,主要使用场景是当有很多线程都从某个数据结构中读取数据,而很少有线程对其进行修改。在这种情况下,允许读取器线程共享访问时合适的,写入器线程必须是互斥访问的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则。

    ReentrantReadWriteLock的实现里面有以下几个特性:

    (1)公平性

    (2)重入性

    (3)锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就可以从写入锁变成了读取锁,从而实现锁降级的特性。

    (4)锁升级

    (5)锁获取中断

    (6)条件变量

    (7)重入数:读取锁和写入锁的数量最大分别是65535。它最多支持65535个写锁和65535个读锁。

     概括起来其实就是读写锁的机制:

    A、读-读不互斥

    B、读-写互斥

    C、写-写互斥

    (2)示例

    示例一:ReadLock和WriteLock单独使用的情况

    package demo.thread;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
            final Count ct = new Count();
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        ct.get();
                    }
                }.start();
            }
            for (int i = 0; i < 2; i++) {
                new Thread() {
                    @Override
                    public void run() {
                        ct.put();
                    }
                }.start();
            }
        }
    }
    
    class Count {
        private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
    public void get() {
            rwl.readLock().lock();// 上读锁,其他线程只能读不能写,具有高并发性
            try {
                System.out.println(Thread.currentThread().getName() + " read start.");
                Thread.sleep(1000L);// 模拟干活
                System.out.println(Thread.currentThread().getName() + "read end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rwl.readLock().unlock(); // 释放写锁,最好放在finnaly里面
            }
        }
    
        public void put() {
            rwl.writeLock().lock();// 上写锁,具有阻塞性
            try {
                System.out.println(Thread.currentThread().getName() + " write start.");
                Thread.sleep(1000L);// 模拟干活
                System.out.println(Thread.currentThread().getName() + "write end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rwl.writeLock().unlock(); // 释放写锁,最好放在finnaly里面
            }
        }
    
    }

    运行结果如下:

    Thread-1 read start.
    Thread-0 read start.
    Thread-1read end.
    Thread-0read end.
    Thread-3 write start.
    Thread-3write end.
    Thread-2 write start.
    Thread-2write end.

    从结果上面可以看的出来,读的时候是并发的,写的时候是有顺序的带阻塞机制的

    实例二:ReadLock和WriteLock的复杂使用情况,模拟一个有读写数据的场景

    private final Map<String, Object> map = new HashMap<String, Object>();// 假设这里面存了数据缓存
    private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
    
    public Object readWrite(String id) {
          Object value = null;
          rwlock.readLock().lock();// 首先开启读锁,从缓存中去取
          try {
                value = map.get(id);
                if (value == null) { // 如果缓存中没有数据,释放读锁,上写锁
                    rwlock.readLock().unlock();
                    rwlock.writeLock().lock();
                    try {
                        if (value == null) {
                            value = "aaa"; // 此时可以去数据库中查找,这里简单的模拟一下
                        }
                    } finally {
                        rwlock.writeLock().unlock(); // 释放写锁
                    }
                    rwlock.readLock().lock(); // 然后再上读锁
                }
            } finally {
                rwlock.readLock().unlock(); // 最后释放读锁
            }
          return value;
    }

    请一定要注意读写锁的获取与释放顺序。

    (3)比较分析

    ReentrantReadWriteLock与ReentrantLock的比较:

    (1)相同点:都是一种显式锁,手动加锁和解锁,都很适合高并发场景

    (2)不同点:ReentrantReadWriteLock是对ReentrantLock的复杂扩展,能适合更复杂的业务场景,ReentrantReadWriteLock可以实现一个方法中读写分离的锁机制。而ReentrantLock加锁解锁只有一种机制

    四、StampedLock

      Java8引入了一个新的读写锁:StampedLock,这个锁更快,而且它提供强大的乐观锁API,这意味着你能以一个较低的代价获得一个读锁, 在这段时间希望没有写操作发生,当这段时间完成后,你可以查询一下锁,看在刚才这段时间是否有写操作发生,然后你可以决定是否需要再试一次或升级锁或放弃。

    通常我们的同步锁代码如下:

    synchronized(this){
        // do operation
    }

    Java6提供的ReentrantReadWriteLock使用方式如下:

    rwlock.writeLock().lock();
    try {
        // do operation
    } finally {
        rwlock.writeLock().unlock();
    }

      ReentrantReadWriteLock、ReentrantLock和synchronized锁都有相同的内存语义,不管怎么说synchronized代码要更容易书写,而ReentrantLock的代码必须严格按照一定的方式来写,否则就会造成严重的问题。StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小,StampedLock控制锁有三种模式(写,读,乐观读)

    参考资料:

    Java 8新特性StampedLock

    (1)http://www.importnew.com/14941.html

    (2)http://www.jdon.com/idea/java/java-8-stampedlock.html

    ===============深入学习===AbstractQueuedSynchronizer=================

    此处贴出一些资源,后续会研究这部分内容

    (1)http://ifeve.com/jdk1-8-abstractqueuedsynchronizer/

    (2)http://ifeve.com/introduce-abstractqueuedsynchronizer/

  • 相关阅读:
    谷歌浏览器离线安装
    C语言restrict关键字的使用
    Win32编程中radiobutton的分组
    在SourceForge中建立开源项目
    [转载]Linux解压缩命令
    [转载]vim配置文件
    解决VC6在win7下打开文件崩溃问题
    gson解析复杂的json数据
    catch中return语句的执行时间
    踏出了学习clojure的第一步
  • 原文地址:https://www.cnblogs.com/studyLog-share/p/5296376.html
Copyright © 2011-2022 走看看