【JavaEE】——多线程常用类

简介: Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,

  image.gif 编辑

阿华代码,不是逆风,就是我疯

你们的点赞收藏是我前进最大的动力!!

希望本文内容能够帮助到你!!

目录

引入:

一:Callable和FutureTask类

1:对比Runnable

2:FutureTask类

3:代码示例

示例一:Runnable

编辑示例二:Callable

二:ReentrantLock——可重入锁

1:与synchronized的区别

(1)不会阻塞

(2)公平锁

(3)唤醒机制不同

三:Semaphore——信号量

1:P操作

2:V操作

3:PV代码示例一

4:锁功能

四:CountDownLatch

1:引入

2:代码示例


引入:

通过之前的学习,我们了解到CAS本质上是JVM替我们封装好的,我们没有办法感知到

java.util.concurrent中存放了一些我们多线程编程时常用的类

看下面的一些接口:是不是非常熟悉,我们把这个packet包简称为(JUC)

image.gif 编辑

一:Callable和FutureTask类

读法:“开了波哦”    译为:调用

1:对比Runnable

Runnable提供run方法返回值为void——关注过程,不关注执行结果

Callable提供call方法返回值类型就是执行结果的类型———更关注结果

2:FutureTask类

在Callable中的call方法中完成任务的描述后,我们要想办法把这个任务加载给线程Thread

但是Thread类中并没有给出Callable的构造方法,于是我们通过FutureTask这个中间类(可以理解为加载任务的装置),作为媒介,发射给Thread

即:

Callable中描述方法——卢本伟来啦~~

FutureTask中加载任务——卢本伟已准备就绪~~

Thread中传入futureTask任务执行——卢本伟启动!!

注:

Callable和FutureTask实例化的时候<>中要写返回结果的类型哦。

futureTask.get()方法是带有阻塞功能的,如果线程还没有执行完毕,get就会被阻塞,等到线程执行完了,return的结果就会被get返回回来

3:代码示例

老问题:计算前5000个数字之和

 

看以下两段代码——用Callable类写的代码比Runnable类写的代码更加优雅~~

示例一:Runnable

package thread;
public class ThreadDemon37 {
    private static int sum = 0;//全局变量用来保存最后的结果值
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            int count = 0;//局部变量
            @Override
            public void run() {
                for (int i = 1 ; i <= 5000 ; i++){
                    count += i;
                }
                sum = count;
            }
        });
        t1.start();
        t1.join();
        System.out.println(sum);
    }
}

image.gif

image.gif 编辑示例二:Callable

此处我们不用再引入额外的成员变量了,直接借助返回值即可

package thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemon38 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <= 5000; i++) {
                    sum += i;
                }
                return sum;
            }
        };
        //Thread t1 = new Thread(callable);//Thread中没有提供构造函数来传入callable
        //引入FutureTask类,未来要完成的任务(任务还未执行)
        // 相当于在Callable中确定执行的任务
        //在FutureTask装置中完成任务加载——卢本伟准备就绪~~~
        //最后引入线程——卢本伟启动!!
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t1 = new Thread(futureTask);
        t1.start();
        t1.join();
        System.out.println(futureTask.get());//装置获得一下结果
    }
}

image.gif

image.gif 编辑

补充一点:.futureTask.get()方法本身自带阻塞特性,如果Callable任务还没有执行完,是会一直等待它的返回值结果的

二:ReentrantLock——可重入锁

读音:“瑞安纯特老科” 翻译为:可重入锁

科普:ReentrantLock在很早以前是比没有发展起来的synchronized功能更加强大的,他提供了两个传统的方法lock和unlock,但是在写代码的过程中lock完后往往会忘记unlock解锁,所以一般把unlock操作放到finally里面使用

1:与synchronized的区别

(1)不会阻塞

我们知道synchronized加锁,如果线程“锁竞争”失败,会陷入阻塞等待,使用了ReentrantLodk提供了trylock方法后,如果加不上锁就会返回false,不会阻塞等待。

(2)公平锁

ReentrantLock中加锁依据是:公平锁,所有参与“加锁”的线程会被放进队列里面,按顺序进行加锁

(3)唤醒机制不同

synchronized提供wait和notify,ReentrantLock搭配Condition,功能比notify强一点

三:Semaphore——信号量

读音:“赛摸佛尔” 翻译为:信号量

科普:因为发明信号量的大佬迪杰斯特拉是个荷兰人,荷兰语的申请和释放首字母分别是P和V。实际上英语是acquire和release

1:P操作

申请一个可用资源,可用资源总数就会-1

2:V操作

释放一个可用资源,可用资源总数就会+1

打个比方:去停车场停车,现在有50个停车位,申请一个停车位(p操作),现有可用停车位为49;出来了一辆车(v操作),现有可用停车位为50;

3:PV代码示例一

package thread;
import java.util.concurrent.Semaphore;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-30
 * Time: 10:26
 */
public class ThreadDemon39 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(1);//资源数限制为1个
        semaphore.acquire();
        System.out.println("p操作");
        semaphore.acquire();//第二次申请
        System.out.println("p操作");
        semaphore.acquire();//第三次申请
        System.out.println("p操作");
    }
}

image.gif

image.gif 编辑

4:锁功能

信号量是更为广义的锁

代码示例:继续沿用解决count计数器++线程安全问题的方式

package thread;
import java.util.concurrent.Semaphore;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-30
 * Time: 10:33
 */
public class ThreadDemon40 {
    private static int count = 0;
    //引入Semaphore进行加锁
    private static Semaphore semaphore = new Semaphore(1);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                semaphore.acquire();//加锁
                for (int i = 1 ; i <= 50000 ; i++){
                    count++;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            semaphore.release();//解锁
        });
        Thread t2 = new Thread(() ->{
            try {
                semaphore.acquire();//加锁
                for (int i = 1 ; i <= 50000 ; i++){
                    count++;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            semaphore.release();//解锁
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

image.gif

image.gif 编辑

四:CountDownLatch

1:引入

latch(锁存器)

举个例子,现在下载软件的速度非常快,用的是多线程下载方式,比如要下载一个1G大小的软件,我们把这个任务分成10份,分给10个线程同时进行下载,最后在拼在一起,速度就会快非常多。

这个“拼”的操作,就能被CountDownLatch感知到,比我们用join要更简单方便一些

2:代码示例

package thread;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: Hua YY
 * Date: 2024-09-30
 * Time: 10:57
 */
public class ThreadDemon41 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);//创建10个线程
        Random random = new Random();
        int time = (random.nextInt(4)+1)*1000;//time的范围[0,4]->[1,5]->[1000,5000]
        for(int i = 1 ; i <= 10 ; i++){
            int count = i;
            Thread t = new Thread(() ->{
                try {
                    Thread.sleep(random.nextInt(time));//产生的随机数的范围
                    System.out.println("第" + count + "线程的任务执行完毕");
                    latch.countDown();//告知CountDownLatch有一个任务已经执行完毕了
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        latch.await();//如果CountDownLatch中的任务还没有执行完毕,那么CountDownLatch就会陷入阻塞等待
        System.out.println("所有任务都已经执行完毕了");
    }
}

image.gif

image.gif 编辑


相关文章
|
2天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
2天前
|
Java Go 调度
【JavaEE】——线程池大总结
线程数量问题解决方式,代码实现线程池,ThreadPoolExecutor(核心构造方法),参数的解释(面试:拒绝策略),Executors,工厂模式,工厂类
|
2天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
2天前
|
Java 调度
|
2天前
|
Java 调度
【JavaEE】——线程的安全问题和解决方式
【JavaEE】——线程的安全问题和解决方式。为什么多线程运行会有安全问题,解决线程安全问题的思路,synchronized关键字的运用,加锁机制,“锁竞争”,几个变式
|
2天前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
|
2天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
2天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
2天前
|
安全 前端开发 Java
【JavaEE】——线程的诞生(超详细、易理解)
进程对内存的管理,进程间的通信,进程的缺点,线程的概念和特点,进程和线程在内存中的分配方式,进程和线程的结合,进程和线程之间的关系,线程的缺点,有无线程的对比,线程的总结
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。