JAVA-Thread-Base-5

Java 线程基础 5

1Lock 显示锁

在 JDK5 中 增 加 了 Lock 锁 接 口 , 有 ReentrantLock 实 现类,ReentrantLock 锁称为可重入锁, 它功能比 synchronized 多.

1.1 锁的可重入性

锁的可重入是指,当一个线程获得一个对象锁后,再次请求该对象锁时是可以获得该对象的锁的.

package com.sevattal.lock.reentrant;
/**
 * 演示锁的可重入性
 */
public class Test01 {
    public synchronized void sm1(){
        System.out.println("同步方法 1");
        //线程执行 sm1()方法,默认 this 作为锁对象,在 sm1()方法中调用了 sm2()方法,注意当前线程还是持有 this 锁对象的
        //sm2()同步方法默认的锁对象也是 this 对象, 要执行 sm2()必须先获得 this 锁对象, 当前 this 对象被当前线程持有,可以 再次获得 this 对象, 这就是锁的可重入性. 假设锁不可重入的话,可能会造成死锁
        sm2();
    }
    private synchronized void sm2() {
        System.out.println("同步方法 2");
        sm3();
    }
    private synchronized void sm3() {
        System.out.println("同步方法 3");
    }
    public static void main(String[] args) {
        Test01 obj = new Test01();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.sm1();
            }
        }).start();
    }
}

1.2 ReentrantLock

5.2.1 ReentrantLock 的基本使用

调用 lock()方法获得锁, 调用 unlock()释放锁

package com.sevattal.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Lock 锁的基本使用
 */
public class Test02 {
    //定义显示锁
    static Lock lock = new ReentrantLock();
    //定义方法
    public static void sm(){
        //先获得锁
        lock.lock();
        //for 循环就是同步代码块
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " -- " + i);
        }
        //释放锁
        lock.unlock();
    }
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                sm();
            }
        };
        //启动三个线程
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
}

package com.sevattal.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 使用 Lock 锁同步不同方法中的同步代码块
 */
public class Test03 {
    static Lock lock = new ReentrantLock(); //定义锁对象
    public static void sm1(){
        //经常在 try 代码块中获得 Lock 锁, 在 finally 子句中释放锁
        try {
            lock.lock(); //获得锁
            System.out.println(Thread.currentThread().getName() + "-- method 1 -- " +
                    System.currentTimeMillis() );
            Thread.sleep(new Random().nextInt(1000));
            System.out.println(Thread.currentThread().getName() + "-- method 1 -- " +
                    System.currentTimeMillis() );
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //释放锁
        }
    }
    public static void sm2(){
        try {
            lock.lock(); //获得锁
            System.out.println(Thread.currentThread().getName() + "-- method 2 -- " +
                    System.currentTimeMillis() );
            Thread.sleep(new Random().nextInt(1000));
            System.out.println(Thread.currentThread().getName() + "-- method 2 -- " +
                    System.currentTimeMillis() );
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); //释放锁
        }
    }
    public static void main(String[] args) {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                sm1();
            }
        };
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                sm2();
            }
        };
        new Thread(r1).start();
        new Thread(r1).start();
        new Thread(r1).start();
        new Thread(r2).start();
        new Thread(r2).start();
        new Thread(r2).start();
    }
}

1.2.2 ReentrantLock 锁的可重入性

package com.sevattal.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * ReentrantLock 锁的可重入性
 */
public class Test04 {
    static class Subthread extends Thread{
        private static Lock lock = new ReentrantLock(); //定义锁对象
        public static int num = 0; //定义变量
        @Override
        public void run() {
            for (int i = 0; i <10000 ; i++) {
                try {
                    //可重入锁指可以反复获得该锁
                    lock.lock();
                    lock.lock();
                    num++;
                }finally {
                    lock.unlock();
                    lock.unlock();
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Subthread t1 = new Subthread();
        Subthread t2 = new Subthread();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println( Subthread.num );
    }
}

1.2.3 lockInterruptibly()方法

lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁, 如果当前线程被中断则出现异常.

package com.sevattal.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则
 * 出现异常.
 */
public class Test05 {
    static class Servier{
        private Lock lock = new ReentrantLock(); //定义锁对象
        public void serviceMethod(){
            try {
                // lock.lock(); //获得锁定,即使调用了线程的 interrupt()方法,也没有真正的中断线程
                lock.lockInterruptibly(); //如果线程被中断了,不会获得锁,会产生异常
                System.out.println(Thread.currentThread().getName() + "-- begin lock");
                //执行一段耗时的操作
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    new StringBuilder();
                }
                System.out.println( Thread.currentThread().getName() + " -- end lock");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println( Thread.currentThread().getName() + " ***** 释放锁");
                lock.unlock(); //释放锁
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Servier s = new Servier();
        Runnable r = new Runnable() {
            @Override
            public void run() {
                s.serviceMethod();
            }
        };
        Thread t1 = new Thread(r);
        t1.start();
        Thread.sleep(50);
        Thread t2 = new Thread(r);
        t2.start();
        Thread.sleep(50);
        t2.interrupt(); //中断 t2 线程
    }
}

对于 synchronized 内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待.
对于 ReentrantLock 可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求.

package com.sevattal.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 通过 ReentrantLock 锁的 lockInterruptibly()方法避免死锁的产生
 */
public class Test06 {
    static class IntLock implements Runnable{
        //创建两个 ReentrantLock 锁对象
        public static ReentrantLock lock1 = new ReentrantLock();
        public static ReentrantLock lock2 = new ReentrantLock();
        int lockNum; //定义整数变量,决定使用哪个锁
        public IntLock(int lockNum) {
            this.lockNum = lockNum;
        }
        @Override
        public void run() {
            try {
                if ( lockNum % 2 == 1){ //奇数,先锁 1,再锁 2
                    lock1.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "获得锁 1,还需 要获得锁 2");
                    Thread.sleep(new Random().nextInt(500));
                    lock2.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "同时获得了锁 1 与锁 2....");
                }else { //偶数,先锁 2,再锁 1
                    lock2.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "获得锁 2,还需 要获得锁 1");
                    Thread.sleep(new Random().nextInt(500));
                    lock1.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "同时获得了锁 1 与锁 2....");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if ( lock1.isHeldByCurrentThread()) //判断当前线程是否持有该锁
                    lock1.unlock();
                if (lock2.isHeldByCurrentThread())
                    lock2.unlock();
                System.out.println( Thread.currentThread().getName() + "线程退出");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        IntLock intLock1 = new IntLock(11);
        IntLock intLock2 = new IntLock(22);
        Thread t1 = new Thread(intLock1);
        Thread t2 = new Thread(intLock2);
        t1.start();
        t2.start();
        //在 main 线程,等待 3000 秒,如果还有线程没有结束就中断该线程
        Thread.sleep(3000);
        //可以中断任何一个线程来解决死锁, t2 线程会放弃对锁 1 的申请,同时释放锁 2, t1 线程会完成它的任务
        if (t2.isAlive()){ t2.interrupt();}
    }
}

1.2.4 tryLock()方法

tryLock(long time, TimeUnit unit) 的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁.
通过该方法可以实现锁对象的限时等待.

package com.sevattal.lock.reentrant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
 *tryLock(long time, TimeUnit unit) 的基本使用
 */
public class Test07 {
    static class TimeLock implements Runnable{
        private static ReentrantLock lock = new ReentrantLock(); //定义锁对象
        @Override
        public void run() {
            try {
                if ( lock.tryLock(3, TimeUnit.SECONDS) ){ //获得锁返回 true
                    System.out.println(Thread.currentThread().getName() + "获得锁,执行耗 时任务");
                            // Thread.sleep(4000); //假设 Thread-0 线程先持有锁,完成任务需要 4 秒钟,
                            //Thread-1 线程尝试获得锁,Thread-1 线程在 3 秒内还没有获得锁的话,Thread-1线程会放弃
                            Thread.sleep(2000);
                            //假设 Thread-0 线程先持有锁,完成任务需要 2 秒钟,Thread-1 线程尝试获得锁,
                            // Thread-1 线程会一直尝试,在它约定尝试的 3 秒内可以获得锁对象
                }else { //没有获得锁
                    System.out.println(Thread.currentThread().getName() + "没有获得锁");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    }
    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        Thread t1 = new Thread(timeLock);
        Thread t2 = new Thread(timeLock);
        t1.start();
        t2.start();
    }
}

tryLock()仅在调用时锁定未被其他线程持有的锁,如果调用方法时,锁对象对其他线程持有,则放弃. 调用方法尝试获得没,如果该锁没有被其他线程占用则返回 true 表示锁定成功; 如果锁被其他线程占用则返回 false,不等待.

package com.sevattal.lock.reentrant;
import java.util.concurrent.locks.ReentrantLock;
/**
 *tryLock() * 当锁对象没有被其他线程持有的情况下才会获得该锁定
 */
public class Test08 {
    static class Service{
        private ReentrantLock lock = new ReentrantLock();
        public void serviceMethod(){
            try {
                if (lock.tryLock()){
                    System.out.println(Thread.currentThread().getName() + "获得锁定");
                    Thread.sleep(3000); //模拟执行任务的时长
                }else {
                    System.out.println(Thread.currentThread().getName() + "没有获得锁定 ");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        Runnable r = new Runnable() {
            @Override
            public void run() {
                service.serviceMethod();
            }
        };
        Thread t1 = new Thread(r);
        t1.start();
        Thread.sleep(50); //睡眠 50 毫秒,确保 t1 线程锁定
        Thread t2 = new Thread(r);
        t2.start();
    }
}

package com.sevattal.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 使用 tryLock()可以避免死锁
 */
public class Test09 {
    static class IntLock implements Runnable{
        private static ReentrantLock lock1 = new ReentrantLock();
        private static ReentrantLock lock2 = new ReentrantLock();
        private int lockNum; //用于控制锁的顺序
        public IntLock(int lockNum) {
            this.lockNum = lockNum;
        }
        @Override
        public void run() {
            if ( lockNum % 2 == 0 ){ //偶数先锁 1,再锁 2
                while (true){
                    try {
                        if (lock1.tryLock()){
                            System.out.println(Thread.currentThread().getName() + "获得 锁 1, 还想获得锁 2");
                                    Thread.sleep(new Random().nextInt(100));
                            try {
                                if (lock2.tryLock()){
                                    System.out.println(Thread.currentThread().getName() + "同时获得锁 1 与锁 2 ----完成任务了");
                                    return; //结束 run()方法执行,即当前线程结束
                                }
                            } finally {
                                if (lock2.isHeldByCurrentThread()){
                                    lock2.unlock();
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if (lock1.isHeldByCurrentThread()){
                            lock1.unlock();
                        }
                    }
                }
            }else { //奇数就先锁 2,再锁 1
                while (true){
                    try {
                        if (lock2.tryLock()){
                            System.out.println(Thread.currentThread().getName() + "获得 锁 2, 还想获得锁 1");
                                    Thread.sleep(new Random().nextInt(100));
                            try {
                                if (lock1.tryLock()){
                                    System.out.println(Thread.currentThread().getName() + "同时获得锁 1 与锁 2 ----完成任务了");
                                    return;
                                    //结束 run()方法执行,即当前线程结束
                                }
                            } finally {
                                if (lock1.isHeldByCurrentThread()){
                                    lock1.unlock();
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if (lock2.isHeldByCurrentThread()){
                            lock2.unlock();
                        }
                    }
                }
            }
        }
    }
    public static void main(String[] args) {
        IntLock intLock1 = new IntLock(11);
        IntLock intLock2 = new IntLock(22);
        Thread t1 = new Thread(intLock1);
        Thread t2 = new Thread(intLock2);
        t1.start();
        t2.start();
        //运行后,使用 tryLock()尝试获得锁,不会傻傻的等待,通过循环不停的再次尝试,
        // 如果等待的时间足够长,线程总是会获得想要的资源
    }
}

1.2.5 newCondition()方法

关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式. Lock 锁的 new Contition()方法返回 Condition 对象,Condition 类也可以实现等待/通知模式.
使用 notify()通知时, JVM 会随机唤醒某个等待的线程. 使用Condition 类可以进行选择性通知. Condition 比较常用的两个方法:

await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行. 
signal()用于唤醒一个等待的线程

注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关的 Lock 锁. 调用 await()后线程会释放这个锁,在 singal()调用后会从当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒 的线程尝试获得锁, 一旦获得锁成功就继续执行.

package com.sevattal.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Condition 等待与通知
 */
public class Test01 {
    //定义锁
    static Lock lock = new ReentrantLock();
    //获得 Condtion 对象
    static Condition condition = lock.newCondition();
    //定义线程子类
    static class SubThread extends Thread{
        @Override
        public void run() {
            try {
                lock.lock(); //在调用 await()前必须先获得锁
                System.out.println("method lock");
                condition.await(); //等待
                System.out.println("method await");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); //释放锁
                System.out.println("method unlock");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        SubThread t = new SubThread();
        t.start();
        //子线程启动后,会转入等待状态
        Thread.sleep(3000);
        //主线程在睡眠 3 秒后,唤醒子线程的等待
        try {
            lock.lock();
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

package com.sevattal.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 多个 Condition 实现通知部分线程, 使用更灵活
 */
public class Test02 {
    static class Service{
        private ReentrantLock lock = new ReentrantLock(); //定义锁对象
        //定义两个 Condtion 对象
        private Condition conditionA = lock.newCondition();
        private Condition conditionB = lock.newCondition();
        //定义方法,使用 conditionA 等待
        public void waitMethodA(){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " begin wait:" +
                        System.currentTimeMillis());
                conditionA.await(); //等待
                System.out.println(Thread.currentThread().getName() + " end wait:" +
                        System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        //定义方法,使用 conditionB 等待
        public void waitMethodB(){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " begin wait:" +
                        System.currentTimeMillis());
                conditionB.await(); //等待
                System.out.println(Thread.currentThread().getName() + " end wait:" +
                        System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        //定义方法唤醒 conditionA 对象上的等待
        public void signalA(){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " sigal A time = " +
                        System.currentTimeMillis());
                conditionA.signal();
                System.out.println(Thread.currentThread().getName() + " sigal A time = " +
                        System.currentTimeMillis());
            } finally {
                lock.unlock();
            }
        }
        //定义方法唤醒 conditionB 对象上的等待
        public void signalB(){
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " sigal A time = " +
                        System.currentTimeMillis());
                conditionB.signal();
                System.out.println(Thread.currentThread().getName() + " sigal A time = " +
                        System.currentTimeMillis());
            } finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        //开启两个线程,分别调用 waitMethodA(),waitMethodB()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.waitMethodA();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.waitMethodB();
            }
        }).start();
        Thread.sleep(3000); //main 线程睡眠 3 秒
        // service.signalA(); //唤醒 conditionA 对象上的等待,conditionB 上的等待依然继续等待
        service.signalB();
    }
}

package com.sevattal.lock.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 使用 Condition 实现生产者/消费者设计模式, 两个 线程交替打印
 */
public class Test03 {
    static class MyService{
        private Lock lock = new ReentrantLock(); //创建锁对象
        private Condition condition = lock.newCondition(); //创建 Condition 对象
        private boolean flag = true; //定义交替打印标志
        //定义方法只打印----横线
        public void printOne(){
            try {
                lock.lock(); //锁定
                while (flag){ //当 flag 为 true 等待
                    System.out.println(Thread.currentThread().getName() + " waiting...");
                    condition.await();
                }
                //flag 为 false 时打印
                System.out.println(Thread.currentThread().getName() + " ---------------- ");
                flag = true; //修改交替打印标志
                condition.signal(); //通知另外的线程打印
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); //释放锁对象
            }
        }
        //定义方法只打印***横线
        public void printTwo(){
            try {
                lock.lock(); //锁定
                while (!flag){ //当 flag 为 false 等待
                    System.out.println(Thread.currentThread().getName() + " waiting...");
                    condition.await();
                }
                //flag 为 true 时打印
                System.out.println(Thread.currentThread().getName() + " ****** ");
                flag = false; //修改交替打印标志
                condition.signal(); //通知另外的线程打印
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); //释放锁对象
            }
        }
    }
    public static void main(String[] args) {
        MyService myService = new MyService();
        //创建线程打印--
        new Thread(new Runnable() {
           @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    myService.printOne();
                }
            }
        }).start();
        //创建线程打印
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    myService.printTwo();
                }
            }
        }).start();
    }
}

1.2.6 公平锁与非公平锁

大多数情况下,锁的申请都是非公平的. 如果线程1与线程2都在请求锁 A, 当锁 A 可用时, 系统只是会从阻塞队列中随机的选择一个线程, 不能保证其公平性.

公平的锁会按照时间先后顺序,保证先到先得, 公平锁的这一特点不会出现线程饥饿现象.

synchronized 内部锁就是非公平的. ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,当在创建锁对象时实参传递 true 可以把该锁设置为公平锁. 公平锁看起来很公平,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低. 因此默认情况下锁是非公平的. 不是特别的需求,一般不使用公平锁

package com.sevattal.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 公平 锁与非公平锁
 */
public class Test01 {
    // static ReentrantLock lock = new ReentrantLock(); //默认是非公平锁
    static ReentrantLock lock = new ReentrantLock(true); //定义公平锁
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        lock.lock();
                        System.out.println(Thread.currentThread().getName() + " 获 得 了 锁对象");
                    }finally {
                        lock.unlock();
                    }
                }
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
    /* 运行程序
    1)如果是非公平锁, 系统倾向于让一个线程再次获得已经持有的锁, 这种
    分配策略是高效的,非公平的
    2)如果是公平锁, 多个线程不会发生同一个线程连续多次获得锁的可能, 保证了公平性
    */
    }
}

1.2.7 几个常用的方法

int getHoldCount() 返回当前线程调用 lock()方法的次数
int getQueueLength() 返回正等待获得锁的线程预估数
int getWaitQueueLength(Condition condition) 返回与 Condition 条件相关的等待的线程预估数
boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁
boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁
boolean hasWaiters(Condition condition) 查询是否有线程正在等待指定的 Condition 条件
boolean isFair() 判断是否为公平锁
boolean isHeldByCurrentThread() 判断当前线程是否持有该锁
boolean isLocked() 查询当前锁是否被线程持有;

1.3 ReentrantReadWriteLock 读写锁

synchronized 内部锁与 ReentrantLock 锁都是独占锁(排它锁), 同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低.

ReentrantReadWriteLock 读写锁是一种改进的排他锁,也可以称作共享/排他锁. 允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新.

读写锁通过读锁与写锁来完成读写操作. 线程在读取共享数据前必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的.线程在修改共享数据前必须先持有写锁,写锁是排他的, 一个线程持有写锁时其他线程无法获得相应的锁

读锁只是在读线程之间共享,任何一个线程持有读锁时,其他线程都无法获得写锁, 保证线程在读取数据期间没有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改

获得条件 排他性 作用
读锁 写锁未被任意线程持有 对读线程是共享的, 对写线程是排他的 允许多个读线程可以同时读取共享数据,保证在读共享数据时,没有其他线程对共享数据进行修改
写锁 该写锁未被其他线程持有,并且相应的读锁也未被其他线程持有 对读线程或者写线程都是排他的 保证写线程以独占的方式修改共享数据

读写锁允许读读共享, 读写互斥,写写互斥

在java.util.concurrent.locks包中定义了ReadWriteLock接口,该接口中定义了 readLock()返回读锁,定义 writeLock()方法返回写锁. 该接口的实现类是 ReentrantReadWriteLock.

注意 readLock()与 writeLock()方法返回的锁对象是同一个锁的两个不同的角色, 不是分别获得两个不同的锁. ReadWriteLock 接口实例可以充当两个角色.读写锁的其他使用方法

//定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获得读锁
Lock readLock = rwLock.readLock();
//获得写锁
Lock writeLock = rwLock.writeLock();
//读数据
readLock.lock(); //申请读锁
try{
读取共享数据
}finally{
readLock.unlock(); //总是在 finally 子句中释放锁
}
//写数据
writeLock.lock(); //申请写锁
try{
更新修改共享数据
}finally{
writeLock.unlock(); //总是在 finally 子句中释放锁
}

1.3.1 读读共享

ReadWriteLock 读写锁可以实现多个线程同时读取共享数据,即读读共享,可以提高程序的读取数据的效率

package com.sevattal.lock.readwrite;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * ReadWriteLock 读写锁可以实现读读共享,允许多个线程同时获得读锁
 */
public class Test01 {
    static class Service{
        //定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        //定义方法读取数据
        public void read(){
            try {
                readWriteLock.readLock().lock(); //获得读锁
                System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取 数据的时间--" + System.currentTimeMillis());
                TimeUnit.SECONDS.sleep(3); //模拟读取数据用时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                readWriteLock.readLock().unlock(); //释放读锁
            }
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        //创建 5 个线程,调用 read()方法
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.read(); //在线程中调用 read()读取数据
                }
            }).start();
        }
        //运行程序后,这多个 线程几乎可以同时获得锁读,执行 lock()后面的代码
    }
}

1.3.2 写写互斥

通过 ReadWriteLock 读写锁中的写锁,只允许有一个线程执行 lock()后面的代码.

package com.sevattal.lock.readwrite;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 演示 ReadWriteLock 的 writeLock()写锁是互斥的,只允许有一个线程持有
 */
public class Test02 {
    static class Service{
        //先定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        //定义方法修改数据
        public void write(){
            try {
                readWriteLock.writeLock().lock(); //申请获得写锁
                System.out.println(Thread.currentThread().getName() + "获得写锁,开始修改 数据的时间--" + System.currentTimeMillis());
                        Thread.sleep(3000); //模拟修改数据的用时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + "读取数据完毕时的 时间==" + System.currentTimeMillis());
                        readWriteLock.writeLock().unlock(); //释放写锁
            }
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        //创建 5 个线程修改数据
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.write(); //调用修改数据的方法
                }
            }).start();
        }
        //从执行结果来看,同一时间只有一个线程获得写锁
    }
}

1.3.3 读写互斥

写锁是独占锁,是排他锁,读线程与写线程也是互斥的

package com.sevattal.lock.readwrite;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 演示 ReadWriteLock 的读写互斥
 * 一个线程获得读锁时,写线程等待; 一个线程获得写锁时,其他线程等待
 */
public class Test03 {
    static class Service{
        //先定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        Lock readLock = readWriteLock.readLock(); //获得读锁
        Lock writeLock = readWriteLock.writeLock(); //获得写锁
        //定义方法读取数据
        public void read(){
            try {
                readLock.lock(); //申请获得读锁
                System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取 数据的时间--" + System.currentTimeMillis());
                Thread.sleep(3000); //模拟读取数据的用时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + "读取数据完毕时的 时间==" + System.currentTimeMillis());
                readLock.unlock(); //释放读锁
            }
        }
        //定义方法修改数据
        public void write(){
            try {
                writeLock.lock(); //申请获得写锁
                System.out.println(Thread.currentThread().getName() + "获得写锁,开始修改 数据的时间--" + System.currentTimeMillis());
                Thread.sleep(3000); //模拟修改数据的用时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + "修改数据完毕时的 时间==" + System.currentTimeMillis());
                writeLock.unlock(); //释放写锁
            }
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        //定义一个线程读数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        }).start();
        //定义一个线程写数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        }).start();
    }
}
Contents
  1. 1. Java 线程基础 5
    1. 1.1. 1Lock 显示锁
      1. 1.1.1. 1.1 锁的可重入性
      2. 1.1.2. 1.2 ReentrantLock
        1. 1.1.2.1. 5.2.1 ReentrantLock 的基本使用
        2. 1.1.2.2. 1.2.2 ReentrantLock 锁的可重入性
        3. 1.1.2.3. 1.2.3 lockInterruptibly()方法
        4. 1.1.2.4. 1.2.4 tryLock()方法
        5. 1.1.2.5. 1.2.5 newCondition()方法
        6. 1.1.2.6. 1.2.6 公平锁与非公平锁
        7. 1.1.2.7. 1.2.7 几个常用的方法
      3. 1.1.3. 1.3 ReentrantReadWriteLock 读写锁
        1. 1.1.3.1. 1.3.1 读读共享
        2. 1.1.3.2. 1.3.2 写写互斥
        3. 1.1.3.3. 1.3.3 读写互斥
|