Java并发编程JUC(三) Callable接口 JUC辅助类 读写锁

一 Callable接口

创建线程的方式

在这里插入图片描述

Callable接口 Runnable接口比较

  1. 是否有返回值
    • Callable接口有返回值
    • Runnable接口没有返回值
  2. 是否抛出异常
    • Callable接口,如果无法计算返回值会抛出异常
    • Runnable接口,不会抛出异常
  3. 实现方法名称不同
    • Callable ----> call()
    • Runnable ----> run()

Callable使用引入

package new_course.chp4.callable;

import java.util.concurrent.Callable;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 13:25
 */
public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        //Runnable方式接口创建线程
        new Thread(new MyThread01(),"AA").start();

        //Callable接口,报错,不能直接用Callable替换Runnable
//        new Thread(new MyThread02(),"BB").start();
    }
}

//比较两个接口
class MyThread01 implements Runnable{
    
    

    @Override
    public void run() {
    
    

    }
}

class MyThread02 implements Callable {
    
    

    @Override
    public Integer call() throws Exception {
    
    
        return 200;
    }
}

在这里插入图片描述

FutureTask

package new_course.chp4.callable;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 14:13
 * FutureTask
 */
public class Callable_FutureTask {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    

        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " come in callable");
            return 1024;
        });

        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " come in callable");
            return 2048;
        });
        //创建线程
        new Thread(futureTask, "lucy").start();
        new Thread(futureTask2, "kitty").start();

        while (!futureTask.isDone()) {
    
    //判断是否做
            System.out.println("wait...................");
        }

        //调用futureTask的get方法
        System.out.println(futureTask.get());//需要等待
        System.out.println(futureTask2.get());
        try {
    
    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(futureTask.get());//直接返回结果
        System.out.println(Thread.currentThread().getName() + " come over");

        //FutureTask原理 未来任务
        /**
         * 1、老师上课,口渴,想喝水,去买水不合适,讲课线程继续
         *    单开启线程找班长帮我买水
         *    把水买回来,我需要的时候直接get
         *
         * 2、4个同学,1同学(1+2+3+4+5)  2同学(10+11+12...+50)  3同学(60+61+62)  4同学(100+200)
         *    2同学计算量比较大,FutureTask单给2同学开线程计算
         *    先去汇总别的同学,最后等2计算完,最终在做汇总
         *
         * 注意:结果只会汇总一次
         *
         */
    }
}


二 JUC强大辅助类

减少计数 CountDownLatch

该类的构造方法
**CountDownLatch(int count)**构造一个用给定计数初始化的CountDownLatch在这里插入代码片

两个常用的主要方法
await() 使当前线程在锁存器倒计数至零之前一直在等待,除非线程被中断
**countDown()**递减锁存器的计数,如果计数达到零,将释放所有等待的线程

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句
具体步骤可以演化为定义一个类,减1操作,并等待到0,为0执行结果

package new_course.chp4.juc;

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

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 14:49
 * 演示 CountDownLatch
 *
 * 题目:设计一到多线程解决方案,教师有六名同学,只有当六名同学全部走完以后,班长才能锁门
 */

public class CountDownLatchDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        //第一步.创建CountDownLatch,并设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6名同学走出教师
        for (int i = 1; i <= 6 ; i++) {
    
    
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
                //第二步 计数 -1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        //第三步 等待,直到减到0才输出
        countDownLatch.await();
        System.out.println("班长关门");
    }
}

循环栅栏 CyclicBarrier

该类是 允许一组线程 互相 等待,直到到达某个公共屏障点,在设计一组固定大小的线程的程序中,这些线程必须互相等待,因为barrier在释放等待线程后可以重用,所以称为循环barrier

常用的构造方法有:
**CyclicBarrier(int parties,Runnable barrierAction)**创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程操作

常用的方法有:
**await()**在所有的参与者都已经在此barrier上调用await方法之前一直等待

package new_course.chp4.juc;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 15:07
 * 演示CyclicBarrier
 * <p>
 * 题目,当七把钥匙都得到的时候,才能开启这扇大门
 */
public class CyclicBarrierDemo {
    
    
    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args)  {
    
    
        //创建CyclicBarrier,达到值输出的信息
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> System.out.println("集齐7把钥匙,可以打开门"));
        //集齐七把钥匙过程
        for (int i = 1; i <= 7; i++) {
    
    
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName() +" 把钥匙被收集到了");
                try {
    
    
                    //每次执行cyclicBarrier,障碍数+1,直到达到NUMBER,才会执行await()之后的方法
                    cyclicBarrier.await();
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

    }
}

信号灯 Semaphore

一个计数信号量,从概念上将,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后在获取该许可。每个**release()**添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动

具体常用的构造方法有:
**Semaphore(int permits)**创建具有给定的许可数和非公平的公平设置的Semapore

具体常用的方法有:
**acquire()**从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
**release()**释放一个许可,将其返回给信号量

设置许可数量Semaphore semaphore = new Semaphore(3);
一般acquire()都会抛出异常,release在finally中执行

package new_course.chp4.juc;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/2 16:26
 * 6辆汽车,停3个停车位
 */
public class SemaphoreDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建Semaphore,设置3个许可数量
        Semaphore semaphore = new Semaphore(3);
        //模拟6辆汽车
        for (int i = 1; i <= 6 ; i++) {
    
    
            new Thread(() -> {
    
    
                //抢占
                try {
    
    
                    //抢占
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 抢到了车位");
                    //设置随机停车时间
                    try {
    
    
                        TimeUnit.SECONDS.sleep(new Random().nextInt(5));//随机停留5s以内
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " -----离开了车位");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

//结果:
1 抢到了车位
3 抢到了车位
2 抢到了车位
1 -----离开了车位
3 -----离开了车位
5 抢到了车位
4 抢到了车位
4 -----离开了车位
6 抢到了车位
5 -----离开了车位
6 -----离开了车位
2 -----离开了车位

三 读写锁

悲观锁

同一时刻只能有一个线程操作资源
在这里插入图片描述

乐观锁

同一时刻可以有多个线程操作资源,每次操作都会带上版本号version

在这里插入图片描述

表锁

只操作一条记录,但是把整张表锁起来,别的线程操作不了任何资源,不会发生死锁

在这里插入图片描述

行锁

只操作一条记录,锁的是操作的那一条记录,别的线程可以操作别行记录,会发生死锁

读锁(共享锁)

会产生死锁
在这里插入图片描述

写锁(独占锁)

会产生死锁

在这里插入图片描述

读写锁问题引入

package new_course.chp4.readwrite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/3 15:42
 */

class MyCache {
    
    
    //创建Map集合,因为数据不断发生变化,所以要加入volatile
    private volatile Map<String, Object> map = new HashMap<>();


    //向集合放入数据
    public void put(String key, Object value) {
    
    

        System.out.println(Thread.currentThread().getName() + " 正在进行写操作:" + key);
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //放入数据
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + " 数据已经写完: " + key);
    }

    //从集合取出数据
    public Object get(String key) {
    
    
        Object result = null;
        System.out.println(Thread.currentThread().getName() + " 正在进行读取操作:" + key);
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        result = map.get(key);
        System.out.println(Thread.currentThread().getName() + " 已经读取完毕:" + key);
        return result;
    }

}

public class ReadWriteLockDemo {
    
    
    public static void main(String[] args) {
    
    
        MyCache myCache = new MyCache();
        //创建线程,往里面放数据
        for (int i = 1; i <= 5; i++) {
    
    
            final int num = i;
            new Thread(() -> {
    
    
                myCache.put(num + "", num + "");
            }, String.valueOf(i)).start();
        }

        //创建线程,往里面放数据
        for (int i = 1; i <= 5; i++) {
    
    
            final int num = i;
            new Thread(() -> {
    
    
                myCache.get(num + "");
            }, String.valueOf(i)).start();
        }
    }
}

结果:还没写完就开始读,不是正确的结果

在这里插入图片描述

通过读写锁进行改造

package new_course.chp4.readwrite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/3 15:42
 */

class MyCache {
    
    
    //创建Map集合,因为数据不断发生变化,所以要加入volatile
    private volatile Map<String, Object> map = new HashMap<>();

    //创建读写锁对象
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    //向集合放入数据
    public void put(String key, Object value) {
    
    
        //添加写锁
        Lock lock = rwLock.writeLock();
        //加锁
        lock.lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName() + " 正在进行写操作:" + key);
            TimeUnit.MILLISECONDS.sleep(300);
            //放入数据
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 数据已经写完: " + key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //解锁
            lock.unlock();
        }

    }

    //从集合取出数据
    public Object get(String key) {
    
    
        //加上读锁
        Lock lock = rwLock.readLock();
        lock.lock();
        Object result = null;
        try {
    
    
            System.out.println(Thread.currentThread().getName() + " 正在进行读取操作:" + key);
            TimeUnit.MILLISECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 已经读取完毕:" + key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
        return result;
    }

}

public class ReadWriteLockDemo {
    
    
    public static void main(String[] args) {
    
    
        MyCache myCache = new MyCache();
        //创建线程,往里面放数据
        for (int i = 1; i <= 5; i++) {
    
    
            final int num = i;
            new Thread(() -> {
    
    
                myCache.put(num + "", num + "");
            }, String.valueOf(i)).start();
        }

        //创建线程,往里面放数据
        for (int i = 1; i <= 5; i++) {
    
    
            final int num = i;
            new Thread(() -> {
    
    
                myCache.get(num + "");
            }, String.valueOf(i)).start();
        }
    }
}

运行结果:从结果可以发现,读锁是共享锁,所有线程可以一起读,而写锁是独占锁
在这里插入图片描述

读写锁总结

一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享

读写锁的演变

在这里插入图片描述

在这里插入图片描述

读写锁的降级

写锁降级为读锁

在这里插入图片描述

package new_course.chp4.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author Created by Lin Weihong
 * @date on 2022/6/3 16:43
 * 展示读写锁降级
 */
public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        //可重入读写锁对象
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        //锁降级
        //1 获取写锁
        writeLock.lock();
        System.out.println("小新--write");

        //2 获取读锁
        readLock.lock();
        System.out.println("小新--read");
        System.out.println("***************程序正常写和读,证明了写操作时,是可以读的******************");

        //3.释放写锁
        writeLock.unlock();

        //4.释放读锁
        readLock.unlock();

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_48244108/article/details/125342603