多线程——Callable,ReentrantLock,Semaphore,CountDownLatch

文章目录

Callable接口

1.继承了Thread(包含了匿名内部类的方法)

2.实现了Runnable(包含了匿名内部类的方式)

3.基于lambda

4.基于线程池

Runnable与Callable的区别:

Runnable关注的是这个过程,不关注执行结果

Runnable提供的run方法,返回值类型是void

Callable关注过程,也关注结果

Callab提供的call方法,返回值就是线程执行任务得到的结果

举例说明:

创建一个新线程,用新线程实现1+2+3+4+...+1000;

使用Thread写的代码:

java 复制代码
public class Test16 {
    public static int sum = 0;
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                int result = 0;
                for (int i = 1; i <= 1000; i++) {
                    result+=i;
                }
                sum = result;
            }
        });
        t.start();
        t.join();
        System.out.println("sum=" + sum);
    }
}

这样写的代码不美观,且如果有多个线程就要创建多个成员变量来接收result。

使用Callable写的代码:

java 复制代码
public class Test17 {
    public static void main(String[] args) throws ExecutionException,InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 1; i <= 1000 ; i++) {
                    result+=i;
                }
                return result;
            }

        };
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread t = new Thread(task);
        t.start();
        System.out.println(task.get());
    }

代码解释:

创建⼀个匿名内部类, 实现 Callable 接⼝. Callable 带有泛型参数. 泛型参数表⽰返回值的类型.

• 重写 Callable 的 call ⽅法, 完成累加的过程. 直接通过返回值返回计算结果.

• 把 callable 实例使⽤ FutureTask 包装⼀下.

• 创建线程, 线程的构造⽅法传⼊ FutureTask . 此时新线程就会执⾏ FutureTask 内部的 Callable 的call ⽅法, 完成计算. 计算结果就放到了FutureTask 对象中.

• 在主线程中调⽤ futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果。

FutureTask类,作为Thread和callable的粘合剂

理解 Callable

Callable 和 Runnable 相对, 都是描述⼀个 "任务". Callable 描述的是带有返回值的任务, Runnable描述的是不带返回值的任务.

Callable 通常需要搭配 FutureTask 来使⽤. FutureTask ⽤来保存 Callable 的返回结果. 因为Callable 往往是在另⼀个线程中执⾏的, 啥时候执⾏完并不确定.

FutureTask 就可以负责这个等待结果出来的⼯作.

理解 FutureTask

想象去吃⿇辣烫. 当餐点好后, 后厨就开始做了. 同时前台会给你⼀张 "⼩票" . 这个⼩票就是FutureTask. 后⾯我们可以随时凭这张⼩票去查看⾃⼰的这份⿇辣烫做出来了没.

ReentrantLock

可重入互斥锁,和synchronized定位类似,都是来实现互斥效果,保证线程安全

ReentrantLock也是可重入锁,

ReentrantLock的用法:

lock( ) : 加锁,如果获取不到锁就死等

trylock(超时间):加锁,如果获取不到锁,等待一段时间之后就放弃加锁

unlock():解锁

java 复制代码
ReentrantLock lock = new ReentrantLock(); 
-----------------------------------------
lock.lock(); 
try { 
 // working 
} finally { 
 lock.unlock() 
} 

ReentrantLock和synchronized的区别

1.ReentrantLock提供了tryLock操作

2.ReentrantLock提供了公平锁的实现

synchronized是非公平锁

ReentrantLock构造方法中填写参数,就可以设置成公平锁

3.搭配的等待通知机制不同的

对于synchronized搭配wait/notify

对于ReentrantLock搭配Condition类,功能比wait notify略强一点

信号量Semaphore

信号量,用来表示"可用资源的个数",本质上就是一个计时器

如何理解信号量

可以把信号量想象成是停⻋场的展⽰牌: 当前有⻋位 100 个. 表⽰有 100 个可⽤资源.

当有⻋开进去的时候, 就相当于申请⼀个可⽤资源, 可⽤⻋位就 -1 (这个称为信号量的 P 操作)

当有⻋开出来的时候, 就相当于释放⼀个可⽤资源, 可⽤⻋位就 +1 (这个称为信号量的 V 操作)

如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

Semaphore 的 PV 操作中的加减计数器操作都是原⼦的, 可以在多线程环境下直接使⽤.

代码示例

• 创建 Semaphore ⽰例, 初始化为 4, 表⽰有 4 个可⽤资源.

• acquire ⽅法表⽰申请资源(P操作), release ⽅法表⽰释放资源(V操作)

• 创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执⾏效果.

java 复制代码
public class Test18 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(4);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try{
                    System.out.println("申请资源");
                    semaphore.acquire();
                    System.out.println("获取到了资源");
                    Thread.sleep(1000);
                    System.out.println("我释放了资源");
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        };
        for (int i = 0; i < 20; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }
}

CountDownLatch

同时等待 N 个任务执⾏结束.

好像跑步⽐赛,10个选⼿依次就位,哨声响才同时出发;所有选⼿都通过终点,才能公布成绩。

• 构造 CountDownLatch 实例, 初始化 10 表⽰有 10 个任务需要完成.

• 每个任务执⾏完毕, 都调⽤ latch.countDown() . 在 CountDownLatch 内部的计数器同时⾃

减.

• 主线程中使⽤ latch.await(); 阻塞等待所有任务执⾏完毕. 相当于计数器为 0 了.

java 复制代码
import java.util.Random;
import java.util.concurrent.CountDownLatch;


public class Test19 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int id = i;
            Thread t = new Thread(()->{
                Random random = new Random();
                int time = (random.nextInt(5)+1)*1000;
                System.out.println("线程"+id+"开始下载");
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程"+id+"下载完成");
                countDownLatch.countDown();
            });
            t.start();
        }
        countDownLatch.await();
        System.out.println("全部下载完成");
    }
}
相关推荐
奋斗的小花生6 分钟前
c++ 多态性
开发语言·c++
魔道不误砍柴功8 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2348 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
pianmian19 分钟前
python数据结构基础(7)
数据结构·算法
闲晨11 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程38 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang