编辑
阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
目录
引入:
通过之前的学习,我们了解到CAS本质上是JVM替我们封装好的,我们没有办法感知到
在java.util.concurrent中存放了一些我们多线程编程时常用的类
看下面的一些接口:是不是非常熟悉,我们把这个packet包简称为(JUC)
编辑
一: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); } }
编辑示例二: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());//装置获得一下结果 } }
编辑
补充一点:.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操作"); } }
编辑
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); } }
编辑
四: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("所有任务都已经执行完毕了"); } }
编辑