文章目录

  • 1、读写锁
  • 1.1、读写锁理论知识
  • 1.2、代码验证
  • 2、CountDownLatch
  • 3、CyclicBarrierDemo
  • 4、SemaphoreDemo


1、读写锁

1.1、读写锁理论知识

  • 独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
  • 共享锁:指该锁可被多个线程所持有
  • 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写
  • 对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁
  • 读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的

1.2、代码验证

实现一个读写缓存的操作,假设开始没有加锁的时候,会出现什么情况

public class ReadWriteWithoutLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        // 线程操作资源类,5个线程写
        for (int i = 1; i <= 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, "写操作线程"+i).start();
        }

        // 线程操作资源类, 5个线程读
        for (int i = 1; i <= 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, "读操作线程"+i).start();
        }
    }
}

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
        try {
            // 模拟网络拥堵,延迟0.3秒
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t 写入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
        try {
            // 模拟网络拥堵,延迟0.3秒
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object value = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
    }
}

运行结果:

写操作线程1	 正在写入:1
写操作线程5	 正在写入:5
写操作线程4	 正在写入:4
写操作线程3	 正在写入:3
写操作线程2	 正在写入:2
读操作线程1	 正在读取:1
读操作线程2	 正在读取:2
读操作线程3	 正在读取:3
读操作线程4	 正在读取:4
读操作线程5	 正在读取:5
写操作线程4	 写入完成
读操作线程1	 读取完成:null
读操作线程4	 读取完成:null
写操作线程5	 写入完成
写操作线程2	 写入完成
读操作线程2	 读取完成:null
读操作线程3	 读取完成:null
写操作线程3	 写入完成
写操作线程1	 写入完成
读操作线程5	 读取完成:5

Process finished with exit code 0

用ReentrantReadWriteLock解决

public class ReadWriteWithLockDemo {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        // 线程操作资源类,5个线程写
        for (int i = 1; i <= 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt +  "");
            }, "写操作线程"+i).start();
        }

        // 线程操作资源类, 5个线程读
        for (int i = 1; i <= 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, "读操作线程"+i).start();
        }
    }
}

class MyCache2 {

    private volatile Map<String, Object> map = new HashMap<>();
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        // 创建一个写锁
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 写锁 释放
            rwLock.writeLock().unlock();
        }
    }

    public void get(String key) {
        // 读锁
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 读锁释放
            rwLock.readLock().unlock();
        }
    }
}

运行结果:

写操作线程1	 正在写入:1
写操作线程1	 写入完成
写操作线程2	 正在写入:2
写操作线程2	 写入完成
写操作线程3	 正在写入:3
写操作线程3	 写入完成
写操作线程5	 正在写入:5
写操作线程5	 写入完成
读操作线程2	 正在读取:2
读操作线程2	 读取完成:2
写操作线程4	 正在写入:4
写操作线程4	 写入完成
读操作线程3	 正在读取:3
读操作线程1	 正在读取:1
读操作线程4	 正在读取:4
读操作线程5	 正在读取:5
读操作线程3	 读取完成:3
读操作线程1	 读取完成:1
读操作线程5	 读取完成:5
读操作线程4	 读取完成:4

Process finished with exit code 0

2、CountDownLatch

  • 让一线程阻塞直到另一些线程完成一系列操作才被唤醒
  • CountDownLatch主要有两个方法(await(),countDown())
  • 当一个或多个线程调用await()时,调用线程会被阻塞,其它线程调用countDown()会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行

示例

假设一个自习室里有7个人,其中有一个是班长,班长的主要职责就是在其它6个同学走了后,关灯,锁教室门,然后走人,因此班长是需要最后一个走的,那么有什么方法能够控制班长这个线程是最后一个执行,而其它线程是随机执行的

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

        // 计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
                countDownLatch.countDown();
            }, "线程"+i).start();
        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + "\t 班长最后关门");
    }
}

运行结果:

线程1	 上完自习,离开教室
线程5	 上完自习,离开教室
线程4	 上完自习,离开教室
线程2	 上完自习,离开教室
线程3	 上完自习,离开教室
线程6	 上完自习,离开教室
main	 班长最后关门

Process finished with exit code 0
  • main线程(班长)一定会最后走
  • 一共7个线程,前六个线程计算器为6,必须执行完(减1,减6次)

3、CyclicBarrierDemo

  • CyclicBarrier的字面意思就是可循环(Cyclic)使用的屏障(Barrier)
  • 它要求做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await方法
  • CyclicBarrier与CountDownLatch的区别:CyclicBarrier可重复多次,而CountDownLatch只能是一次
  • 累计到某个值(计数器),才能执行自定义的方法

示例

程序演示集齐7个龙珠,召唤神龙

public class SummonTheDragonDemo {
    public static void main(String[] args) {
        /**
         * 定义一个循环屏障,参数1:需要累加的值,参数2 需要执行的方法
         */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            final int tempInt = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 收集到 第" + tempInt + "颗龙珠");
                try {
                    // 先到的被阻塞,等全部线程完成后,才能执行方法
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

运行结果:

3	 收集到 第3颗龙珠
2	 收集到 第2颗龙珠
1	 收集到 第1颗龙珠
5	 收集到 第5颗龙珠
4	 收集到 第4颗龙珠
6	 收集到 第6颗龙珠
7	 收集到 第7颗龙珠
召唤神龙

Process finished with exit code 0

当线程数从7增加到14时候,此时有两次到7得机会,即两次集齐龙珠

1	 收集到 第1颗龙珠
4	 收集到 第4颗龙珠
13	 收集到 第13颗龙珠
14	 收集到 第14颗龙珠
11	 收集到 第11颗龙珠
12	 收集到 第12颗龙珠
6	 收集到 第6颗龙珠
3	 收集到 第3颗龙珠
10	 收集到 第10颗龙珠
5	 收集到 第5颗龙珠
9	 收集到 第9颗龙珠
8	 收集到 第8颗龙珠
7	 收集到 第7颗龙珠
召唤神龙
2	 收集到 第2颗龙珠
召唤神龙

Process finished with exit code 0

4、SemaphoreDemo

  • 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制
  • 正常的锁在任何时刻都只允许一个任务访问一项资源,而 Semaphore允许n个任务同时访问这个资源

示例

模拟一个抢车位的场景,假设一共有6个车,3个停车位

public class SemaphoreDemo {
    public static void main(String[] args) {
        /**
         * 初始化一个信号量为3,默认是false 非公平锁, 模拟3个停车位
         */
        Semaphore semaphore = new Semaphore(3, false);

        // 模拟6部车
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    // 代表一辆车,已经占用了该车位
                    semaphore.acquire(); // 抢占

                    System.out.println(Thread.currentThread().getName() + "\t 抢到车位");

                    // 每个车停3秒
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "\t 离开车位");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放停车位
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

运行结果:

1	 抢到车位
2	 抢到车位
0	 抢到车位
0	 离开车位
2	 离开车位
1	 离开车位
4	 抢到车位
3	 抢到车位
5	 抢到车位
5	 离开车位
4	 离开车位
3	 离开车位

Process finished with exit code 0