目录
1.JUC常见类
1.1Callable接口
1.2ReentrantLock
1.3信号量 Semaphore
1.4CountDownLatch
2.线程安全的集合类
2.1多线程环境使用 ArrayList
2.2多线程环境使用队列
2.3多线程环境使用哈希表
2.3.1Hashtable
2.3.2ConcurrentHashMap
1.JUC常见类
============
JUC的全称:java.util.concurrent(concurrent指的是多线程相关操作)
1.1Callable接口
①Callable是什么:
Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便程序猿借助多线程的方式计算结果.
②为什么Callable接口更适合写这种关于计算的代码?
我们就是为了解决Runnable不方便返回结果这个问题
③我们使用Callable接口来解决这个问题的代码:
a.创建一个匿名内部类 , 实现 Callable 接口, 泛型参数表示返回值的类型。
b.重写 Callable 的 call 方法 , 实现1+2+3+…+1000的执行过程。
c.把 callable 实例使用 FutureTask 包装一下。
b.创建线程 , 线程的构造方法传入 FutureTask , 此时新线程就会执行 FutureTask 内部的 Callable 的。
e.call 方法 , 完成计算, 计算结果就放到了 FutureTask 的 对象task中。
f.在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕 。 并获取到 FutureTask 中的结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class demo2{
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer>callable=new Callable() {
@Override
public Object call() throws Exception {
int sum=0;
for(int i=0;i<=1000;i++){
sum+=i;
}
return sum;
}
};
//为了让线程执行Callable中的任务,光使用构造方法是不够的,还需要使用一个辅助类
FutureTask<Integer>task=new FutureTask<>(callable);
//创建线程,来完成这里的工作
Thread t=new Thread(task);
t.start();
int result = task.get();
System.out.println(result);
}
}
④进一步理解Callable接口:
Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务,
Runnable 描述的是不带返回值的任务。Callable 通常需要搭配 FutureTask 来使用。 FutureTask 用来保存 Callable 的返回结果.。因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定。FutureTask 就可以负责这个等待结果出来的工作。
举个生活中的例子来进一步说明:
当我们去一个餐馆进行吃饭,点单的时候会给我们一个小票,当我们刚拿到小票时显然商家正准备进行加工,而当加工完成将要反馈给我们的时候,他会进行叫号,以便确保是谁的单。而这种反馈叫号的操作很明显就是用上面我们提到的FutureTask等待接收结果的这个行为。
1.2ReentrantLock
①ReentrantLock是什么?
ReentrantLock也是一种可重入锁,和 synchronized很像,两者都是用来实现互斥效果, 保证线程安全的。
②基本用法:(它是把加锁解锁两个操作进行分开的操作)
lock():加锁,如果获取不到就一直死等到获取到为止的操作
trylock(超时时间):加锁,如果一段时间仍然获取不到锁,就放弃加锁
unlock():解锁
③ReentrantLock和Synchronized的区别:
a.ReentrantLock是在JVM外部实现的一个标准库的类(基于Java来实现的),而synchronized是在JVM内部实现的一个关键字(基于C++来实现的)
b.ReentrantLock是需要我们进行手动加锁解锁的,使用起来确实更加灵活,但是很多时候手动释放也会被我们忽视。synchronized不需要手动释放锁,出了相应的代码块后,锁即自动释放。
c.ReentrantLock在锁竞争失败的时候除了阻塞等待以外,可以尝试trylock()来获取到锁,如果失败了就直接返回,给我们留下了更多的余地。而synchronized如果竞争的时候失败就会阻塞等待。
d.ReentrantLock既可以是公平锁,也可以是非公平锁,我们只需要在它的参数位置进行指定(默认是非公平锁,true即是公平锁),而synchronized只是一个非公平锁。
e.ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程。 synchronized 是通过 Object 的 wait / notify 实现等待-唤醒.。每次唤醒的是一 个随机等待的线程,相对而言功能是有限的。
但是在我们的日常工作开发中,synchronized 就够用啦
1.3信号量 Semaphore
①什么是信号量:
信号量, 用来表示 “可用资源的个数”。实质上是一个更广义的锁。(锁也被称为二元信号量)
②举一个通俗的例子来帮助你理解信号量:
自驾去某个地方,我们经常会遇到停车的问题, 可以把信号量想象成是停车场的展示牌: 当前有车位 20 个,表示有 20 个可用资源。 当有车开进去的时候, 就相当于减少(申请资源)了一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作) 当有车开出来的时候, 就相当于增加(释放资源)一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)。 如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源。
Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用。
import java.util.concurrent.Semaphore;
public class demo2{
public static void main(String[] args) throws InterruptedException {
//表示提供了10个资源
Semaphore s=new Semaphore(3);
//资源申请
s.acquire();
System.out.println("申请资源啦");
s.acquire();
System.out.println("申请资源啦");
s.acquire();
System.out.println("申请资源啦");
s.acquire();
System.out.println("申请资源啦");
//释放资源
// s.release(1);
}
}
如图所示,这个时候只有3个资源位,要是我申请了3个后没有释放继续申请,那么程序就会出现阻塞的情况,结果如下图:(也就说只会打印3次)
1.4CountDownLatch
①什么是CountDownLatch?
这用文字不怎么好理解,所以给大家举一个例子:
大家应该都玩过王者荣耀吧,我们都知道最终的胜利是退掉敌方水晶,所以当我们退掉一座塔是不够的,我们需要把最终的水晶退掉,才能够结束这一场对局。
②相关方法的说明:
countDown
给每个线程里面去调用,就表示到达终点了。(就相当于上面提到游戏中每推掉一座塔)
await
是给等待线程去调用.当所有的任务都到达终点了,await
就从阻塞中返回,就表示任务完成。(就相当于推掉水晶)
③代码演示:(注意,要等所有调用完了,即水晶推完,await才会返回。也就才会打印gameover…那句话)
a.没有调用完
b.调用完:
import java.util.concurrent.CountDownLatch;
public class demo2{
public static void main(String[] args) throws InterruptedException {
//在游戏里,包括水晶,我们需要推掉4个塔
学习路线:
这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!