线程的显式锁(Lock)·读写锁(ReadWriteLock)·条件阻塞(Condition)


在一些多个线程同时访问一些共享数据,一般都会涉及到线程安全问题。这时候就需要使用一些手段去防止多个线程同时去修改共享数据,防止同时去执行某段代码逻辑。这时候就要考虑到加锁了。

0 锁的条件

具有排他性,同一时刻只能有一个对象获取到锁。

1. 显式锁(Lock)

1.1 用法场景
  1. 多个线程对同一个共享资源进行操作。
  2. 防止重复请求。
1.2 Lock与synchronized

synchronized:java内置锁,一般会用于锁一些代码块,不需要手动的去释放锁,一般与Object类中的wait()和notify()配合使用。
Lock:显示锁,一般用于锁一个对象,需要手动的去释放锁,要不然会一直被锁。
目前微服务和分布式盛行的时代,Lock用的都很少了,一般都会使用第三方工具来实现锁。最常用的是Redis的锁。
jdk也对Lock进行了实现ReentrantLock。ReentrantLock和synchronized都是可重入锁,一个线程在锁定的代码块里继续调用该锁锁定的方法或代码块,是能进入锁定的代码中运行的。类似于下面的代码。

public void useLock() {
        lock.lock();  //阻塞方法,一直在等待获取锁
        try {
            System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
            Thread.sleep(3000);
            useLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
1.3 显式锁(Lock)的具体实现

Lock有三种获取方式:
1.lock():阻塞方法,一直在等待获取锁,知道获取成功。
2.tryLock():尝试着去获取锁,获取不到返回false,获取到返回true。
3.lock.tryLock(1, TimeUnit.SECONDS):尝试获取锁,如果获取设置的等待时间内还是获取不到锁,返回false,获取到返回true。
最后不要忘记在finally中主动释放锁。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 线程锁的使用
 */
public class UseLock {
    Lock lock = new ReentrantLock(); // 可重入锁
    public void useLock() {
        lock.lock();  //阻塞方法,一直在等待获取锁
        try {
            System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
            Thread.sleep(3000);
            useLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void useTryLock() {
        if (lock.tryLock()) { //尝试获取锁,获取不到返回false
            try {
                System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("Thread:" + Thread.currentThread().getName() +"线程锁获取失败。");
        }

    }
    public void useTryLockTimeout() throws InterruptedException {
        if (lock.tryLock(1, TimeUnit.SECONDS)) {  //尝试获取锁,等待一段时间后还是获取不到再返回false
            try {
                System.out.println("Thread:" + Thread.currentThread().getName() + "运行。。。");
                Thread.sleep(7000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("Thread:" + Thread.currentThread().getName() +"线程锁获取失败。");
        }

    }
    public static void main(String[] args) {
        UseLock useLock = new UseLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("useLock1");
                useLock.useLock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("useLock2");
                useLock.useLock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("useLock3");
                useLock.useLock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("useTryLock1");
                useLock.useTryLock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("useTryLock2");
                useLock.useTryLock();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().setName("useTryLockTimeout1");
                    useLock.useTryLockTimeout();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.currentThread().setName("useTryLockTimeout2");
                    useLock.useTryLockTimeout();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

2. 读写锁(ReadWriteLock)

2.1用法场景

1.一般用于读多写少的场景。

2.2原理

当线程获取到写锁时,别的线程读锁和写锁都获取不到。当线程获取到读锁时,别的线程也能正常获取到读锁和写锁。具体底层实现有待研究。

2.3实现代码

ReadWriteLock接口由ReentrantReadWriteLock实现。ReentrantReadWriteLock中有两个内部类ReadLock和WriteLock,这两个类都实现Lock接口。

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

/**
 * 读写锁的使用
 */
public class UseReadWriteLock {
    ReadWriteLock lock = new ReentrantReadWriteLock();
    Lock readLock = lock.readLock();
    Lock writeLock = lock.writeLock();
    int num = 0;
    public void setNum(int num) {
        writeLock.lock();
        try {
            this.num = num;
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }
    public int getNum() {
        readLock.lock();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
        }
        return this.num;
    }
    public static void main(String[] args) throws InterruptedException {
        UseReadWriteLock useReadWriteLock = new UseReadWriteLock();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    int num = new Random().nextInt();
                    useReadWriteLock.setNum(num);
                    long endTime = System.currentTimeMillis();
                    System.out.println(Thread.currentThread().getName() + "写" + num + "耗时" + (endTime - startTime));
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    int num = useReadWriteLock.getNum();
                    long endTime = System.currentTimeMillis();
                    System.out.println(Thread.currentThread().getName() + "读取num耗时" + (endTime - startTime) + "ms,读取的值为" + num);
                }
            }).start();
        }
    }
}

3. 条件阻塞(Condition)

3.1 Condition的优点

和Object中的三个方法相比,Condition的优点是:
1.Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。
    2.Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
    例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,“读线程"需要等待。 如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
    在使用Condition时,必须保证当前对象已经获取到锁,不然会报错。

3.2具体实现
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 条件阻塞Condition
 */
public class UseCondition {
    static Lock lock = new ReentrantLock(); // 可重入锁
    static Condition packCondition = lock.newCondition(); //包装
    static Condition workCondition = lock.newCondition(); //生产
    static Condition repairCondition = lock.newCondition(); //维修
    static Boolean finish = false;
    //包装类
    static class Pack implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                int i = 1;
                do {
                    System.out.println("包装部门等待包装");
                    packCondition.await();
                    Thread.sleep(2000);
                    System.out.println("包装部门包装好第" + i + "包");
                    workCondition.signal();
                    i++;
                } while (i <= 5);
                finish = true;
                repairCondition.signal();
                System.out.println("包装部门工作完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    //生产部门
    static class Work implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                int i=0;
                do {
                    System.out.println("生产部门开始生产");
                    Thread.sleep(2000);
                    Random random = new Random();
                    int num = random.nextInt(10);
                    System.out.println("随机数是"+num);
                    if (num % 4 != 0) {
                        //生产部门完成工作,通知包装部门
                        System.out.println("生产部门生产完成,通知包装部门包装,等待包装部回馈");
                        packCondition.signal();
                        workCondition.await();
                        System.out.println("生产部门收到包装部门完成回馈,检查是否完成");
                        i++;
                    } else {
                        //生产部门通知维修部门修机器
                        System.out.println("机器坏了,生产部门通知维修部门修机器,并等待修好");
                        repairCondition.signal();
                        workCondition.await();
                        System.out.println("机器修好,开始工作。");
                    }
                } while (!finish);
                System.out.println("生产部门完成了今天的工作,共生产了"+i+"包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    //维修部门
    static class Repair implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                do {
                    System.out.println("维修部门待命");
                    repairCondition.await();
                    if(finish){
                        System.out.println("工作完成,维修部门结束工作");
                        break;
                    }
                    System.out.println("维修部门接到通知开始修机器");
                    Thread.sleep(2000);
                    System.out.println("维修部门修好机器通知生产部");
                    workCondition.signal();
                } while (true);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Pack()).start();
        new Thread(new Repair()).start();
        new Thread(new Work()).start();
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值