多线程基础-5-Lock显示锁

多线程

5 Lock显式锁

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

5.1 锁的可重入性

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

5.2 ReentrantLock

5.2.1 ReentrantLock的基本使用

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

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * lock锁的基本使用
 * @author Yupeng
 * @create 2020-12-09 21:39
 */
public class Test01 {
    public static void main(String[] args) {
        // 创建三个线程
        new SubThread().start();
        new SubThread().start();
        new SubThread().start();
    }
    // 定义显式锁
    static Lock lock = new ReentrantLock();
    // 定义方法
    public static void sm() {
        //先获得锁
        lock.lock();
        try {
            // 同步代码块
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
    // 定义线程类
    static class SubThread extends Thread{
        @Override
        public void run() {
            sm();
        }
    }
}

使用lock锁,可以同步不同方法中的同步代码块。

5.2.2 ReentrantLock的可重入性
package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock的可重入性
 * @author Yupeng
 * @create 2020-12-09 22:02
 */
public class Test02 {
    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);
    }
    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 < 1000; i++) {
                lock.lock();
                try {
                    // 可重入性指的是可以反复获得该锁
                    lock.lock();
                    try {
                        num++;
                    }finally {
                        lock.unlock();
                    }
                }finally {
                    lock.unlock();
                }
            }
        }
    }
}
5.2.3 lockInterruptibly()方法

lockInterruptibly() 方法的作用:在等待获得锁的过程中,如果当前线程未被中断则可以正常获得锁;如果在等待获得锁的过程中,当前线程被中断,则出现异常。

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * lockInterruptibly() 方法的作用:在等待获得锁的过程中,如果当前线程未被中断则可以正常获得锁;如果在等待获得锁的过程中,当前线程被中断,则出现异常。
 * @author Yupeng
 * @create 2020-12-10 16:18
 */
public class Test03 {
    public static void main(String[] args) {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    service.sm();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);

        t1.start();
        // 保证线程t1先执行
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.start();
        // 睡眠时间不能过长,保证结束睡眠后线程t1还在执行
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.interrupt();
    }

    static class Service{
        // 定义锁对象
        private ReentrantLock lock = new ReentrantLock();
        public void sm() throws InterruptedException {
//            lock.lock(); //获得锁对象,即使调用了线程的interrupt()方法,也没有真正的中断线程
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + "获得锁对象...");
                System.out.println(Thread.currentThread().getName() + "开始同步...");
                // 执行一段耗时操作
                for (int i = 0; i < 1000000000; i++) {
                    new StringBuilder();
                }
                System.out.println( Thread.currentThread().getName() + "结束同步!");
            } finally {
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "释放锁对象!");
            }
        }
    }
}

对于 synchronized 内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待.

对于 ReentrantLock 可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求. 比如通过 ReentrantLock 锁的 lockInterruptibly()方法避免死锁的产生。

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 通过 ReentrantLock锁的lockInterruptibly()方法避免死锁的产生
 * @author Yupeng
 * @create 2020-12-10 16:44
 */
public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        IntLock intLock1 = new IntLock(5); // 奇数
        IntLock intLock2 = new IntLock(6); // 偶数

        Thread t1 = new Thread(intLock1);
        Thread t2 = new Thread(intLock2);
        t1.start();
        t2.start();

        // main线程睡眠3秒,如果线程没有结束,就中断线程
        Thread.sleep(3000);
        // 可以中断任何一个线程来解决死锁
        if (t1.isAlive()) {
            t1.interrupt();
        }
    }
    static class IntLock implements Runnable{
        // 创建两个锁对象
        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() {
                if (lockNum % 2 == 1){ // 奇数,先获得锁1,再获得锁2
                    try {
                        lock1.lockInterruptibly();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println(Thread.currentThread().getName() + "获得锁1,还需要继续获得锁2");
                        lock2.lockInterruptibly();
                        try {
                            System.out.println(Thread.currentThread().getName() + "获得了锁1和锁2,可以继续执行...");
                        } finally {
                            if (lock2.isHeldByCurrentThread()) {
                                lock2.unlock();
                                System.out.println(Thread.currentThread().getName() + "释放锁2");
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if (lock1.isHeldByCurrentThread()) {
                            lock1.unlock();
                            System.out.println(Thread.currentThread().getName() + "释放锁1");
                        }
                    }
                } else { // 偶数,先获得锁2,再获得锁1
                    try {
                        lock2.lockInterruptibly();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println(Thread.currentThread().getName() + "获得锁2,还需要继续获得锁1");
                        lock1.lockInterruptibly();
                        try {
                            System.out.println(Thread.currentThread().getName() + "获得了锁1和锁2,可以继续执行...");
                        } finally {
                            if (lock1.isHeldByCurrentThread()) {
                                lock1.unlock();
                                System.out.println(Thread.currentThread().getName() + "释放锁1");
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if (lock2.isHeldByCurrentThread()) {
                            lock2.unlock();
                            System.out.println(Thread.currentThread().getName() + "释放锁2");
                        }
                    }
                }
        }
    }
}
// 运行结果:
// t1线程获得了锁1,t2线程获得了锁2,发生死锁,t1线程在申请锁2的过程中被中断,则放弃对锁2的申请并且释放持有的锁1,使得t2获得锁1,继续执行。
Thread-0获得锁1,还需要继续获得锁2
Thread-1获得锁2,还需要继续获得锁1
java.lang.InterruptedException
...
Thread-0释放锁1
Thread-1获得了锁1和锁2,可以继续执行...
Thread-1释放锁1
Thread-1释放锁2
5.2.4 tryLock()方法

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

package com.yupeng.lock.reentrant;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * tryLock(long time, TimeUnit unit)的基本使用
 * @author Yupeng
 * @create 2020-12-10 17:20
 */
public class Test05 {
    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        Thread t1 = new Thread(timeLock);
        Thread t2 = new Thread(timeLock);
        t1.start();
        t2.start();
    }
    static class TimeLock implements Runnable{
        // 定义锁对象
        private static ReentrantLock lock = new ReentrantLock();
        @Override
        public void run() {
            try {
                if (lock.tryLock(3, TimeUnit.SECONDS)){ // 在给定等待时长内,如果锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁
                    System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
//                    Thread.sleep(4000); // 执行任务需要4秒,已经超过等待时长,其他线程将放弃申请锁
                    Thread.sleep(2000); // 执行任务需要2秒,没有超过等待时长,其他线程可以获得锁
                } else{
                    System.out.println(Thread.currentThread().getName() + "没有获得锁");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                    System.out.println(Thread.currentThread().getName() + "释放锁");
                }
            }
        }
    }
}

tryLock()方法,调用方法尝试获得锁,如果该锁没有被其他线程占用,则返回 true,锁定成功; 如果锁被其他线程占用,则返回 false,不等待.

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.ReentrantLock;

/**
 * tryLock()方法的使用
 * @author Yupeng
 * @create 2020-12-10 17:35
 */
public class Test06 {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.serviceMethod();
            }
        };
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        // 保证t1先执行
        Thread.sleep(500);
        t2.start();
    }
    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();
                    System.out.println(Thread.currentThread().getName() + "释放锁");
                }
            }
        }
    }
}
// 运行结果:
Thread-0获得锁,继续执行...
Thread-1没有获得锁
Thread-0释放锁
5.2.5 newCondition()方法

内部锁synchronized 与 wait()/notify()这两个方法一起使用,可以实现等待/通知机制. Lock 锁的 newContition()方法返回 Condition 对
象,Condition 类也可以实现等待/通知模式.

使用 notify()通知时, JVM 会随机唤醒某个等待的线程. 使用Condition 类可以进行选择性通知. Condition 比较常用的两个方法:

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

signal()用于唤醒一个等待的线程

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

基本使用:

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Condition的基本使用
 * @author Yupeng
 * @create 2020-12-10 18:20
 */
public class Test07 {
    public static void main(String[] args) throws InterruptedException {
        SubThread t1 = new SubThread();
        t1.start();
        Thread.sleep(2000);
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
    // 定义锁对象
    static private Lock lock = new ReentrantLock();
    // 定义Condition对象
    static Condition condition = lock.newCondition();
    // 定义线程子类
    static class SubThread extends Thread{
        @Override
        public void run() {
            lock.lock(); // 调用await()之前,必须获得锁
            try {
                System.out.println("获得锁对象");
                condition.await();
                System.out.println("开始等待...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("释放锁对象");
            }
        }
    }
}

多个Condition 实现通知部分线程, 使用更灵活:

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多个 Condition 实现通知部分线程, 使用更灵活
 * @author Yupeng
 * @create 2020-12-10 18:30
 */
public class Test08 {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();

        new Thread(new Runnable() {
            @Override
            public void run() {
                service.waitMethodA();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                service.waitMethodB();
            }
        }).start();
        // main线程睡眠3秒
        Thread.sleep(3000);

        service.signalMethodA(); // 唤醒conditionA对象上的等待,conditionB对象上的等待继续等待
    }

    static class Service{
        // 定义锁对象
        private ReentrantLock lock = new ReentrantLock();
        // 定义两个condition对象
        private Condition conditionA = lock.newCondition();
        private Condition conditionB = lock.newCondition();
        // 定义方法,使用conditionA等待
        public void waitMethodA() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " begin await:" +System.currentTimeMillis() );
                conditionA.await();
                System.out.println(Thread.currentThread().getName() + " end await:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        // 定义方法,使用conditionB等待
        public void waitMethodB() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " begin await:" +System.currentTimeMillis() );
                conditionB.await();
                System.out.println(Thread.currentThread().getName() + " end await:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        // 定义方法,唤醒conditionA对象上的等待
        public void signalMethodA() {
            lock.lock();
            try {
                conditionA.signal();
                System.out.println(Thread.currentThread().getName() + " signal A:" + System.currentTimeMillis());
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        // 定义方法,唤醒conditionB对象上的等待
        public void signalMethodB() {
            lock.lock();
            try {
                conditionB.signal();
                System.out.println(Thread.currentThread().getName() + "signal B:" + System.currentTimeMillis());
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }

    }
}

使用condition实现两个线程交替打印:

package com.yupeng.lock.reentrant;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用condition实现两个线程交替打印
 * @author Yupeng
 * @create 2020-12-10 22:05
 */
public class Test09 {
    public static void main(String[] args) {
        Service service = new Service();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    service.printOne();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    service.printTwo();
                }
            }
        }).start();
    }

    static class Service{
        // 定义锁对象
        private ReentrantLock lock = new ReentrantLock();
        // 定义condition对象
        private Condition condition = lock.newCondition();
        // 定义交替打印标志
        private boolean flag = true;
        // 定义方法,打印数字1
        public void printOne() {
            lock.lock();
            try {
                while (!flag) {
                    condition.await();
                }
                System.out.println(Thread.currentThread().getName() + "打印1");
                flag = false;
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
        // 定义方法,打印数字2
        public void printTwo() {
            lock.lock();
            try {
                while (flag) {
                    condition.await();
                }
                System.out.println(Thread.currentThread().getName() + "打印2");
                flag = true;
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    }
}
5.2.6 公平锁与非公平锁

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

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

synchronized 内部锁是非公平的. ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,创建锁对象时实参传递true ,可以把该锁设置为公平锁.

公平锁看起来很公平,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低. 因此默认情况下锁是非公平的. 除非是特别的需求,一般不使用公平锁.

package com.yupeng.lock.method;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 公平锁与非公平锁
 * @author Yupeng
 * @create 2020-12-10 22:44
 */
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) {
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + "获得了锁对象");
                    } finally {
                        if (lock.isHeldByCurrentThread()) {
                            lock.unlock();
                        }
                    }
                }
            }
        };

        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
    }
}
/*
1)如果是非公平锁, 系统倾向于让一个线程再次获得已经持有的锁, 这种分配策略是高效的,非公平的
2)如果是公平锁, 多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性
*/
5.2.7 常用方法
int getHoldCount() //返回当前线程调用lock()方法的次数
int getQueueLength() //返回正在等待获得锁的线程预估数

boolean hasWaiters (Condition condition) // 查询是否有线程正在等待指定的 Condition 条件
int getWaitQueueLength (Condition condition) // 返回与Condition条件相关的等待的线程预估数

boolean hasQueuedThreads() // 查询是否还有线程在等待获得该锁
boolean hasQueuedThread(Thread thread) // 查询参数指定的线程是否在等待获得该锁
boolean isFair() // 判断是否为公平锁
boolean isLocked() // 查询当前锁是否被持有
boolean isHeldByCurrentThread() // 判断该锁是否被当前线程持有

5.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子句中释放锁
}
5.3.1 读读共享

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

package com.yupeng.lock.readwrite;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * ReadWriteLock读写锁可以实现读读共享,允许多个线程同时获得读锁
 * @author Yupeng
 * @create 2020-12-11 9:57
 */
public class Test01 {
    public static void main(String[] args) {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        };

        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
    }
    static class Service{
        // 定义读写锁对象
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        public void read() {
            readWriteLock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据...(读取时间:" + System.currentTimeMillis() +")");
                Thread.sleep(2000); // 模拟读取数据的耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.readLock().unlock();
            }
        }
    }
}


// 运行程序后,多个线程几乎可以同时获得锁读,执行lock()后面的代码
Thread-1获得读锁,开始读取数据...(读取时间:1607652206408)
Thread-0获得读锁,开始读取数据...(读取时间:1607652206409)
Thread-4获得读锁,开始读取数据...(读取时间:1607652206409)
Thread-2获得读锁,开始读取数据...(读取时间:1607652206409)
Thread-3获得读锁,开始读取数据...(读取时间:1607652206409
5.3.2 写写互斥

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

package com.yupeng.lock.readwrite;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * ReadWriteLock的writeLock()写锁是互斥的,只允许有一个线程持有
 * @author Yupeng
 * @create 2020-12-11 10:06
 */
public class Test02 {
    public static void main(String[] args) {
        Service service = new Service();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        };

        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
    }
    static class Service{
        // 定义锁对象
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        // 定义写数据的方法
        public void write() {
            readWriteLock.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "开始写入数据...(写入时间:" + System.currentTimeMillis() + ")");
                Thread.sleep(2000); // 模拟写入数据的耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "写入时间完毕!(完成时间:" + System.currentTimeMillis() + ")");
                readWriteLock.writeLock().unlock();
            }
        }
    }
}
// 从执行结果来看,同一时间只有一个线程获得写锁
Thread-0开始写入数据...(写入时间:1607652962175)
Thread-0写入时间完毕!(完成时间:1607652964176)
Thread-1开始写入数据...(写入时间:1607652964176)
Thread-1写入时间完毕!(完成时间:1607652966176)
Thread-2开始写入数据...(写入时间:1607652966176)
Thread-2写入时间完毕!(完成时间:1607652968177)
Thread-3开始写入数据...(写入时间:1607652968177)
Thread-3写入时间完毕!(完成时间:1607652970177)
Thread-4开始写入数据...(写入时间:1607652970177)
Thread-4写入时间完毕!(完成时间:1607652972178
5.3.3 读写互斥

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

package com.yupeng.lock.readwrite;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写互斥:一个线程获得读锁时,写线程等待; 一个线程获得写锁时,其他线程等待
 * @author Yupeng
 * @create 2020-12-11 10:18
 */
public class Test03 {
    public static void main(String[] args) {
        Service service = new Service();
        Runnable readRunnable = new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        };
        Runnable writeRunnable = new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        };
        // 创建两个写线程和两个读线程
        new Thread(writeRunnable).start();
        new Thread(writeRunnable).start();
        new Thread(readRunnable).start();
        new Thread(readRunnable).start();
    }
    static class Service{
        // 定义锁对象
        private ReadWriteLock lock = new ReentrantReadWriteLock();

        private Lock readLock = lock.readLock();
        private Lock writeLock = lock.writeLock();

        public void read() {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据...(读取时间:" + System.currentTimeMillis() +")");
                Thread.sleep(2000); // 模拟读取数据的耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
            }
        }
        public void write() {
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "开始写入数据...(写入时间:" + System.currentTimeMillis() + ")");
                Thread.sleep(2000); // 模拟写入数据的耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "写入时间完毕!(完成时间:" + System.currentTimeMillis() + ")");
                writeLock.unlock();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值