本文可作为传智播客《张孝祥-Java多线程与并发库高级应用》的学习笔记。

一个简单的例子

两个线程,一个不断打印a,一个不断打印b

public class LockTest {
    public static void main(String[] args){
        final Outputer outputer = new Outputer();
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    outputer.output("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                }                   

            }
        }).start();

        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    outputer.output("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
                }//a的数量与b的数量一致

            }
        }).start();

    }

    static class Outputer{
        public void output(String name){
            int len = name.length();
            try{
                for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }finally{

            }
        }
    }
}

最后的部分结果

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

b

bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

为什么会这样?

很简单,在输出b的时候,还没有输出完,a线程(打印a的那个线程)已经抢到了控制权,开始打印a,等a线程将a输出完后,并且打印了一个回车后,b线程才抢回系统控制权,打印它上一次最后剩下的一个b。

要解决上面的问题很简单:

    static class Outputer{
        public synchronized void output(String name){
            int len = name.length();
            //.....
        }
    }

这样一来,我们就保证了Outputer类里的output方法是原子性的,不会有两个线程同时执行它。



就上面的例子而言我们是否还有更好的方法呢?

有。

java5中提供了一种更加面向对象的技术类解决多线程之间的互斥问题-----锁。

java.util.concurrent.locks Interface Lock

锁技术的核心就是Lock及它的实现类。

基本锁

*******************************************
*******************************************
以下为2016您3月21日补充

既然都说到锁了,我们就看上java.util.concurrent下都有什么东西
首先concurrent下有两个子包

atomic与locks
atomic包里面主要是对基本数据类型如int,float,boolean等的原子封装
lock包是我们今天要说的

OK有3个接口
首先我先说明,这3个接口之间并没有继承的关系
Lock与ReadWriteLock都是锁,可以实现线程的互斥,只是ReadWriteLock可以更进一步的实现读与读不互斥(更多的资料,见下文)


上面的readlock与writelock分别是ReentrantReadWriteLock的两个静态内部类
Condition呢,上面的Lock实现了线程的互斥,但是我们还得实现线程的通信呀,那就是condition
同时只有lock接口有newCondition方法, ReadWriteLock没有这个方法的
关于condition,可参见拙作





以上为2016您3月21日补充
*******************************************
*******************************************

上面的例子如果使用锁,代码如下

    static class Outputer{
        Lock lock = new ReentrantLock();

        public void output(String name){
            int len = name.length();
            lock.lock();      //标识1
            try{
                for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }finally{
                lock.unlock(); //标识2
            }
        }

线程a执行到上面代码的标识1处加锁,当线程a在输出字符a时,线程b也执行到了标识1处。此时线程b是不能获得锁的。它被阻塞到标识1处,直到线程a打印完之后在标识2处释放了锁。(线程a线程b共用一把锁,也就是Lock lock = new ReentrantLock())

另外为什么标识2出的释放锁放到了finally里,大家应该明白了吧。

读与写

上面的问题中output的主体(len是方法内部的局部变量,为每个线程自有,互不干涉)被全部互斥,它保证了任何时候,都只有一个线程执行标识1与标识2直接的代码。

但是我们得意识到:对共有数据的操作,基本可以分为两类,读与写。

对共有资源操作的时候,我们应该遵循三大准则:

1 当一个线程对资源进行写操作的时候,别的线程既不能对资源读也不能对资源写。

2 当一个线程对资源进行读操作的时候,别的线程不能对资源写。

3 当一个线程对资源进行读操作的时候,别的线程能对资源读。

一二准则保证了系统的正确性。第三准则能提高系统的性能。 毕竟多个线程对资源进行读操作是可以的。

看下面这个既有读又有写的例子。

public class ReadWriteLockTest {
    public static void main(String[] args) {
        final Queue3 q3 = new Queue3();
        for(int i=0;i<3;i++)
        {
            new Thread(){
                public void run(){
                    while(true){
                        q3.get();
                    }
                }

            }.start();

            new Thread(){
                public void run(){
                    while(true){
                        q3.put(new Random().nextInt(10000));
                    }
                }            

            }.start();
        }

    }
}

class Queue3{
    private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。

    public void get(){
        try {
            System.out.println(Thread.currentThread().getName() + " be ready to read data!");
            Thread.sleep((long)(Math.random()*1000));
            System.out.println(Thread.currentThread().getName() + "have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    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;
            System.out.println(Thread.currentThread().getName() + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果如下

Thread-0 be ready to read data!

Thread-1 be ready to write data!

Thread-2 be ready to read data!

Thread-3 be ready to write data!

Thread-4 be ready to read data!

Thread-5 be ready to write data!

Thread-0have read data :null

Thread-0 be ready to read data!

Thread-3 have write data: 5280

Thread-3 be ready to write data!

Thread-1 have write data: 5839

Thread-1 be ready to write data!

Thread-4have read data :5839

我们可以看到 读中有写  写中有写 写中有读 完全乱套了。



我们试试个两个方法加上synchronized 结果如下

Thread-0 be ready to read data!

Thread-0have read data :null

Thread-5 be ready to write data!

Thread-5 have write data: 7931

Thread-5 be ready to write data!

Thread-5 have write data: 9564

Thread-5 be ready to write data!

Thread-5 have write data: 1203

Thread-5 be ready to write data!

Thread-5 have write data: 8870

Thread-4 be ready to read data!

Thread-4have read data :8870

Thread-3 be ready to write data!

Thread-3 have write data: 9334

Thread-3 be ready to write data!

Thread-3 have write data: 2680

Thread-3 be ready to write data!

Thread-3 have write data: 9948

Thread-3 be ready to write data!

Thread-3 have write data: 375

Thread-2 be ready to read data!

读与写完全互斥,读的时候不写,写的时候不读。满足一二准则。

读写锁

为了实现准则三,在java5中的出现了读写锁。

java.util.concurrent.locks Interface ReadWriteLock

ReadWriteLock有两个方法

Lock     readLock()   Returns the lock used for reading.

Lock     writeLock()  Returns the lock used for writing.

得到两种锁后,就可以调用锁的lock与unlock方法了。

一般使用它的子类ReentrantReadWriteLock来产生ReadWriteLock

其签名如下:

public class ReentrantReadWriteLock extends Object implements ReadWriteLock, Serializable

看看使用方法

class Queue3{
    private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
    ReadWriteLock rwl = new ReentrantReadWriteLock();
    public void get(){
        rwl.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "              be ready to read data!");
            Thread.sleep(20);
            System.out.println(Thread.currentThread().getName() + "       have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            rwl.readLock().unlock();
        }
    }

    public void put(Object data){

        rwl.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " be ready to write data!");
            Thread.sleep(20);
            this.data = data;
            System.out.println(Thread.currentThread().getName() + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            rwl.writeLock().unlock();
        }
    }
}

结果如下

Thread-5 have write data: 7329

Thread-0              be ready to read data!

Thread-0       have read data :7329

Thread-1 be ready to write data!

Thread-1 have write data: 1361

Thread-2              be ready to read data!

Thread-4              be ready to read data!

Thread-0              be ready to read data!

Thread-2       have read data :1361

Thread-2              be ready to read data!

Thread-4       have read data :1361

我们可以看到 线程1的写是完全互斥的。

而线程2 4 0的读是可以同步进行的。

这是读写锁最简单的例子,下一节,我们看一个稍微复杂的,把读锁与写锁放到一个方法内的例子。

感谢glt

ReentrantReadWriteLock读写锁的使用1的更多相关文章

  1. ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  2. ReentrantReadWriteLock读写锁的使用2

    本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 这一节我们做一个缓存系统. 在读本节前 请先阅读 ReentrantReadWriteLock读写锁的使用1 第一 ...

  3. 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...

  4. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

  5. ReentrantReadWriteLock读写锁简单原理案例证明

    ReentrantReadWriteLock存在原因? 我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了Li ...

  6. ReentrantReadWriteLock读写锁详解

    一.读写锁简介 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁.在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源:但是如果一个线 ...

  7. java多线程:并发包中ReentrantReadWriteLock读写锁的锁降级模板

    写锁降级为读锁,但读锁不可升级或降级为写锁. 锁降级是为了让当前线程感知到数据的变化. //读写锁 private ReentrantReadWriteLock lock=new ReentrantR ...

  8. java中ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  9. java多线程:ReentrantReadWriteLock读写锁使用

    Lock比传统的线程模型synchronized更多的面向对象的方式.锁和生活似,应该是一个对象.两个线程运行的代码片段要实现同步相互排斥的效果.它们必须用同一个Lock对象. 读写锁:分为读锁和写锁 ...

随机推荐

  1. 一张图带你看懂SpriteKit中Update Loop究竟做了神马!

    1首先Scene中只有开始一点时间用来回调其中的update方法 ;] 2然后是Scene中所有动作的模拟 3接下来是上一步完成之后,给你一个机会执行一些代码 4然后是Scene模拟其中的物理世界 5 ...

  2. Java进阶(四十七)Socket通信

    Java进阶(四十七)Socket通信   今天讲解一个 Hello Word 级别的 Java Socket 通信的例子.具体通讯过程如下: 先启动Server端,进入一个死循环以便一直监听某端口是 ...

  3. Java进阶(四十三)线程与进程的区别

    Java进阶(四十三)线程与进程的区别 1.线程的基本概念   概念:线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必 ...

  4. Android自定义处理崩溃异常

    用过安卓手机的用户以及安卓开发者们会时长碰到程序异常退出的情况,普通用户遇到这种情况,肯定非常恼火,甚至会骂一生垃圾软件,然后卸载掉.那么开发者们在开发过程中遇到这种情况给怎么办呢,当然,你不可能世界 ...

  5. Struts 2 之类型转换器

    Struts2自定义类型转换器分为局部类型转换器和全局类型转换器 (1)局部类型转换器 如果页面传来一个参数reg.action?birthday=2010-11-12到后台action,然后属性用d ...

  6. Swift基础之init方法,实例方法,类方法(静态方法)的使用(多标签Demo)

    Xcode更新过后,有些方法都进行了改变,Demo中有变化的都进行了简单的标记,具体以后遇见再说 创建一个UIView类,用init方法创建两种类型,显示多标签,创建静态方法进行调用,创建类方法进行调 ...

  7. Android中三种计时器Timer、CountDownTimer、handler.postDelayed的使用

    在android开发中,我们常常需要用到计时器,倒计时多少秒后再执行相应的功能,下面我就分别来讲讲这三种常用的计时的方法. 一.CountDownTimer 该类是个抽象类,如果要使用这个类中的方法, ...

  8. Linux2.6--虚拟文件系统

          虚拟文件系统(有时也称作虚拟文件交换,更常见的是简称做VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口.系统中的所有文件系统不但依赖VFS共存,而且也依赖VFS系统协同 ...

  9. C++对C的实用性增强

    #include <iostream> using namespace std; //C语言中的变量都必须在作用域开始的位置定义!! //C++中更强调语言的"实用性" ...

  10. 操作系统处理内存时内存页为4k

    windows和unix处理内存时,一个内存页的大小都为4k. 测试代码 int main() { while (1) { int *p = (int *)malloc(1024); getchar( ...