高并发基础——Lock(锁)

引入:

之前Java基础学习了使用synchronized来做锁来实现线程安全问题,今天学习使用JUC中的Lock,他比synchronized更加灵活。

1.Lock的实现类ReentrantLock - 重入锁

锁在释放之后允许被其他线程抢占或者被一个线程重复抢占

main方法中:

static int i = 0;

    public static void main(String[] args) throws InterruptedException {

        //声明ReentrantLock对象
        Lock lock = new ReentrantLock();
        //启动线程
        new Thread(new Add(lock),"one").start();
        new Thread(new Add(lock),"two").start();
        // main所在的类是一个线程类 - 主线程
        // 启动2个Add线程,在Add线程启动和准备过程中, 主线程会抢占资源继续执行的
        // 所以要让主线程阻塞
        Thread.sleep(3000);
        System.out.println(i);
    }

线程类:

class Add implements Runnable {

    private Lock lock;

    public Add(Lock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        // 加锁
        lock.lock();
        for (int i = 0; i < 100000; i++) {
            LockDemo.i++;
        }
        // 解锁
        lock.unlock();
    }

从代码中可以看出one线程和two线程都会去抢cpu中的资源,但是我们在线程中使用Lock,并且在线程启动的时候把Lock传递了进去,虽然是两个线程,但是Lock是同一个Lock,所以两个线程无论谁进去,都会锁住代码,直到解锁,才开始新一轮的抢占CPU。

2.ReadWriteLock - 读写锁

分为读锁写锁 。在使用的时候需要先创建ReentrantReadWriteLock,通过这个对象获取读锁或者写锁,之后再加锁解锁或者解锁 。(readwritelock是一个接口,ReentrantReadWriteLock实现了这个接口。)

main方法:

static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        // 加写锁 - 获取写锁
        Lock lock = new ReentrantReadWriteLock().writeLock();
        new Thread(new Add(lock),"one").start();
        new Thread(new Add(lock),"two").start();
        // main所在的类是一个线程类 - 主线程
        // 启动2个Add线程,在Add线程启动和准备过程中,
        // 主线程会抢占资源继续执行的
        // 让主线程阻塞
        Thread.sleep(3000);
        System.out.println(i);
    }

线程类:

class Add implements Runnable {

    private Lock lock;

    public Add(Lock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        // 加锁
        lock.lock();
        for (int i = 0; i < 100000; i++) {
            LockDemo.i++;
        }
        // 解锁
        lock.unlock();
    }

可以看出和前面使用reentraLock的区别就是在获取锁对象的时候不一样。

锁的公平和非公平原则:

线程在抢占CPU的时候,线程的执行次数不一定都是平均的,所以这就是非公平原则,Lock默认为非公平,而synchronized也是非公平的。反而之,所谓的公平就是每个线程的执行次数差不多,那么如何实现公平原则?
使用一个队列,让线程不再直接抢占资源,而是去抢占入队顺序。

CountDownLatch(闭锁)和CyclicBarrier(栅栏):

1.CountDownLatch(闭锁): 对线程进行计数,在计数归零之前线程会陷入阻塞;直到计数归零为止,才会放开阻塞。一组线程结束之后开启另一组线程
CountDownLatch(闭锁)
案例:5个学生,2个老师,7个人都要到达考场,才开始考试,使用countdownLatch需要传入一个参数,表示计数个数。在计数器没有到0的时候,.await方法前面的线程都需要阻塞。
main:

 public static void main(String[] args) throws InterruptedException {

        CountDownLatch cdl = new CountDownLatch(7);
        new Thread(new Teacher(cdl)).start();
        new Thread(new Teacher(cdl)).start();
        new Thread(new Student(cdl)).start();
        new Thread(new Student(cdl)).start();
        new Thread(new Student(cdl)).start();
        new Thread(new Student(cdl)).start();
        new Thread(new Student(cdl)).start();

        // 正常逻辑:上面七个线程执行完成之后,才能开始执行下面的代码
        // 在上面七个线程结束之前,当前线程需要阻塞
        // 在计数归零之前,需要阻塞
        cdl.await();
        System.out.println("考试开始~~~");
    }

考生线程逻辑:

class Student implements Runnable {

    private CountDownLatch cdl;

    public Student(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        try {
            // 模拟:考生到考场花费的时间
            Thread.sleep((long) (Math.random() * 10000));
            System.out.println("考生到达考场~~~");
            //计数器减一
            cdl.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

老师线程逻辑:

class Teacher implements Runnable {

    private CountDownLatch cdl;

    public Teacher(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        try {
            // 模拟:考官到考场花费的时间
            Thread.sleep((long) (Math.random() * 10000));
            System.out.println("考官到达考场~~~");
            // 一个线程已经结束,需要减少一个计数
            cdl.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.CyclicBarrier(栅栏): 对线程进行计数,在计数归零之前线程会陷入阻塞;直到计数归零为止,才会放开阻塞。这一组线程到达同一个点之后再分别继续执行
在这里插入图片描述
案例:比赛跑步的时候,需要所有人到了起跑线上开始跑步,每个线程代表一个跑者,每个线程到起跑线就阻塞,直到所有线程都到了起跑线,计数器为0,再放开阻塞。
main:

public class CyclicBarrierDemo {
    public static void main(String[] args) {
//计数器为6
        CyclicBarrier cb = new CyclicBarrier(6);
        new Thread(new Runner(cb), "1号").start();
        new Thread(new Runner(cb), "2号").start();
        new Thread(new Runner(cb), "3号").start();
        new Thread(new Runner(cb), "4号").start();
        new Thread(new Runner(cb), "5号").start();
        new Thread(new Runner(cb), "6号").start();

    }
}

线程类:

class Runner implements Runnable {

    private CyclicBarrier cb;

    public Runner(CyclicBarrier cb) {
        this.cb = cb;
    }

    @Override
    public void run() {
        try {
            // 模拟:运动员走到起跑线的时间
            Thread.sleep((long) (Math.random() * 10000));
            String name = Thread.currentThread().getName();
            System.out.println(name + "运动员到了起跑线~~~");
            // 正常逻辑:先到的运动员应该阻塞,
            // 直到所有的运动员都到了起跑线才能往外跑
            // 阻塞,计数
            // 这个方法在阻塞的同时会减少一个计数
            // 当计数归零的时候,会放开阻塞
            cb.await();
            System.out.println(name + "运动员跑了出去~~~");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Exchanger:

交换机。用于交换两个线程之间的信息。注意: 只能交换两个线程之间的信息。

案例:生产者和消费者,生产者生产“商品”放入交换机,并且把消费者放入交换机中的“钱”拿出来。
main:

public class ExchangerDemo {

    public static void main(String[] args) {

        Exchanger<String> ex = new Exchanger<>();
        new Thread(new Producer(ex)).start();
        new Thread(new Consumer(ex)).start();

    }

}

生产者:

class Producer implements Runnable {

    private Exchanger<String> ex;

    public Producer(Exchanger<String> ex) {
        this.ex = ex;
    }

    @Override
    public void run() {
        try {
            String info = "商品";
            // 生产者应该商品交换给消费者,应该收到消费者换过来的钱
            String msg = ex.exchange(info);
            System.out.println("生产者收到消费者换来的:" + msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

消费者:

class Consumer implements Runnable {

    private Exchanger<String> ex;

    public Consumer(Exchanger<String> ex) {
        this.ex = ex;
    }

    @Override
    public void run() {
        try {
            String info = "钱";
            // 消费者应该将钱交还给生产者,应该受到生产者换过来的商品
            String msg = ex.exchange(info);
            System.out.println("消费者收到生产者换来的:" + msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

Semaphore(信号量):

信号量它更像一个权限,线程只有拿到信号量,才可以有执行下去的权限,如果没有信号量就阻塞,直到前面的线程释放出信号量。信号量的作用是用于限流 。
案例:7个客人(线程)用餐,5张桌子作为信号量,客人进来用餐,取走一个信号量,吃完释放信号量,让后面阻塞的线程可以继续。
main:

public class SemaphoreDemo {

    public static void main(String[] args) {
//声明信号量,5个信号
        Semaphore s = new Semaphore(5);
        for (int i = 0; i < 7; i++) {
        //启动7个线程,表示7个客人
            new Thread(new Eater(s)).start();
        }

    }

}

线程类:

class Eater implements Runnable {

    private Semaphore s;

    public Eater(Semaphore s) {
        this.s = s;
    }

    @Override
    public void run() {

        try {
            // 桌子的数量是有限的
            // 如果桌子被全部占用,后来的客人就需要等待
            // 桌子相当于信号,只要有信号,那么就可以使用
            s.acquire();
            System.out.println("来了一波客人,占用了一张桌子~~~");
            // 模拟:客人吃饭花费的时间
            Thread.sleep((long) (Math.random() * 10000));
            System.out.println("客人买单离开,空出一张桌子~~~");
            // 释放1个信号,被阻塞的线程就可以获取信号执行代码
            s.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42596778/article/details/106862924