一、JUC
1.JUC相关知识点汇总
2.什么是JUC
JUC:java.util.concurrent包名的简写,是关于并发编程的API。
与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。
二、进程与线程
1、进程与线程
进程:程序的一次执行过程,是系统运行的基本单位,因此进程是动态的(很多次执行)。系统运行一个程序是一个进程创建、运行到消亡的过程。
在JAVA中,当我们启动Main函数其实就是启动了一个JVM的进程,而Main函数所在的线程就是这个进程中的一个线程,也称为主线程。
在Windows中,我们可以通过任务管理器看我们电脑运行着这那些进程。
Java默认有2个线程:main和 GC
线程:是一个比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、本地方法栈、虚拟机栈。所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
对于Java而言,创建线程: Thread. Runnable. Callable,线程池
2.Java真的可以开启线程吗?
不行,看源码可知调用了native方法,底层为C++
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
3.并发和并行
并发(多线程操作同一个资源)
●CPU单核,模拟出来多条线程,快速交替
并行(多个人一起行走)
●CPU多核,多个线程可以同时执行;
查看cpu核数
public class Test1 {
public static void main(String[] args){
//获取cpu的核数
//CPU密集型和IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
4.线程状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待,死等
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
5.wait/sleep区别
(1)来自于不同的类
wait==》Object
sleep==》Thread
(2)锁的释放
wait 会释放锁
sleep ,休眠,不释放锁
(3)使用范围
wait:必须在同步代码块中
sleep: 可以在任何位置
三、Lock 锁(重点)
1.不加锁(同步监视器)
会出现线程争抢资源的情况,不安全
public class SaleTicketDemo_synchronized{
public static void main(String[] args){
//并发:多线程操作同一个资源类
//创建资源类
Ticket ticket = new Ticket();
/*
//,使用匿名内部类创建线程
new Thread(new Runnable() {
public void run() {
}
}).start()
*/
;
//lambda创建线程 ->lambda表达式(参数)->{代码}
new Thread(()->{
for (int i = 1;i<50;i++) ticket.sale();},"A").start();
new Thread(()->{
for (int i = 1;i<50;i++) ticket.sale();},"B").start();
new Thread(()->{
for (int i = 1;i<50;i++) ticket.sale();},"C").start();
}
}
//线程是一个单独的资源类
class Ticket{
//属性,方法
private int number = 50;
public void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
}
}
}
2.synchronized-自动锁
但会出现一个线程多次抢占资源使用
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
}
}
3.Lock-手动锁
(1)加锁和解锁
(2)实现类
(3)公平锁
//公平锁:排队
Lock lock = new ReentrantLock(true);
部分代码
class Ticket2{
//属性,方法
private int number = 50;
Lock lock = new ReentrantLock(true);
public void sale(){
lock.lock();//加锁
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
截图
(4)非公平锁
//非公平锁:默认,可以插队
Lock lock = new ReentrantLock();
部分代码
class Ticket2{
//属性,方法
private int number = 50;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();//加锁
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
截图
(5)Synchronized 和Lock区别
1、Synchronized 内置的Java关键字 ,Lock 是一个java类
2、Synchronized 无法判断获取锁的状态, Lock可以判断是否获取到了锁
3、Synchronized 会自动释放锁, lock必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程1 (获得锁,阻塞)、线程2(等待,死等) ; Lock锁就不一定会等待下去(lock.tryLock();尝试获取锁);
5、Synchronized 可重入锁,不可以中断的,非公平; Lock ,可重入锁,可以判断锁,非公平(可以自己设置)
6、Synchronized 适合锁少量的代码同步问题, Lock适合锁大量的同步代码!
四、生产者消费者问题
1.synchronized、wait、notify传统生产者消费者问题
注意:必须使用while循环进行临界值判断,若为if作为判断条件,只会判断一次,所以 如果是多个生产者和多个消费者,可能会出现虚假唤醒,即生产者notifyAll唤醒后,可能是另外一个正在wait阻塞的生产者抢到了锁,使得会继续生产,而不是去消费,唤醒是唤醒了多个,但只会有一个抢到了锁。但如果使while则会多次进行判断,使得一个生产者拿到锁,则另一个生产者会去等待。
public class Demo1 {
public static void main(String[] args){
//创建资源类
Data data = new Data();
//生产者线程1
new Thread(()->{
for (int i=0; i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者1").start();
//消费者线程1
new Thread(()->{
for (int i=0; i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者1").start();
//生产者线程2
new Thread(()->{
for (int i=0; i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者2").start();
//消费者线程2
new Thread(()->{
for (int i=0; i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者2").start();
}
}
//判断等待,业务,通知
class Data{
private int number = 0;
//生产资源+1
public synchronized void increment() throws InterruptedException {
while (number != 0){ //number==0 则干活,执行num++
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知消费者线程,我生产完了(+1)
this.notifyAll();
}
//消费资源-1
public synchronized void decrement() throws InterruptedException {
while (number == 0){//number==1 则干活,执行num--
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知生产者线程,我消费完了(-1)
this.notifyAll();
}
}
截图
2.Lock、await、signal新版生产者消费者问题
Lock替换synchronized方法和语句的使用,Condition取代了对象监视器方法的使用
2.1一个生产者一个消费者
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo2 {
public static void main(String[] args){
//创建资源类
Data2 data = new Data2();
//生产者线程
new Thread(()->{
for (int i=0; i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者").start();
//消费者线程
new Thread(()->{
for (int i=0; i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者").start();
}
}
//判断等待,业务,通知
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//生产资源+1
public void increment() throws InterruptedException {
lock.lock();//加索
try {
while (number != 0){ //number==0 则干活,执行num++
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知消费者线程,我生产完了(+1)
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
//消费资源-1
public void decrement() throws InterruptedException {
lock.lock();//加锁
try {
while (number == 0){//number==1 则干活,执行num--
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知生产者线程,我消费完了(-1)
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}
截图
2.2 condition实现精准通知唤醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
public static void main(String[] args){
Data3 data = new Data3();
new Thread(()->{
for (int i=0 ; i<10 ; i++){
data.printA();
}
},"A").start();
new Thread(()->{
for (int i=0 ; i<10 ; i++){
data.printB();
}
},"B").start();
new Thread(()->{
for (int i=0 ; i<10 ; i++){
data.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock = new ReentrantLock();
//需要三个condition监视器去精确通知唤醒
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();//加锁
try {
while(number != 1 ){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+":第1个执行");
//唤醒指定的人
number = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();//加锁
try {
while(number != 2 ){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+":第2个执行");
//唤醒指定的人
number = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();//加锁
try {
while(number != 3 ){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+":第3个执行");
//唤醒指定的人
number = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
部分截图
五、八锁问题
1.一个对象,两个线程先打印,哪个方法先执行?
发短信-》打电话
2.sendMs延迟5秒,两个线程谁先执行?
发短信-》打电话
synchronized锁的对象是方法的调用者
一个对象,两个方法用的是同一把锁,谁先拿到谁执行
public class Demo1 {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMs();
try {
TimeUnit.SECONDS.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
//synchronized锁的对象是方法的调用者
//两个方法用的是同一把锁,谁先拿到谁执行
public synchronized void sendMs(){
try {
TimeUnit.SECONDS.sleep(5);//休息5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
3.一个对象,增加一个普通方法后,先执行哪个方法 ?
听音乐-》发短信
listenMc没有锁,不是同步方法,不受锁的影响,会先执行
public class Demo2 {
public static void main(String[] args){
Phone2 phone = new Phone2();
//
new Thread(()->{
phone.sendMs();
try {
TimeUnit.SECONDS.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{phone.listenMc();},"B").start();
}
}
class Phone2{
//synchronized锁的对象是方法的调用者
public synchronized void sendMs(){
try {
TimeUnit.SECONDS.sleep(5);//休息5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void listenMc(){
System.out.println("听音乐");
}
}
4.两个对象,两个同步方法,先执行哪一个?
两个对象,两把不同的锁,不存在资源的竞争,并且sendMs,存在延迟,所以先打电话后发短信
public class Demo3 {
public static void main(String[] args){
//两个对象,两把不同的锁,不存在资源的竞争,并且sendMs,存在延迟,所以先打电话后发短信
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendMs();
try {
TimeUnit.SECONDS.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone3{
//synchronized锁的对象是方法的调用者
public synchronized void sendMs(){
try {
TimeUnit.SECONDS.sleep(5);//休息5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
5.增加两个静态方法,只有一个对象,谁先执行?
6.增加两个静态方法,两个对象,谁先执行?
static修饰为静态方法,类一加载的时候就只有一个Phone3.Class模板,所以不管有几个对象,只有一个类锁,所以先发短信后打电话
public class Demo4 {
public static void main(String[] args){
//两个对象,两把锁,但是只有一个Class类模板,static 锁的是Class
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendMs();
try {
TimeUnit.SECONDS.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone4{
//synchronized锁的对象是方法的调用者
//static修饰为静态方法,类一加载的时候就有了,锁的是Class,所以只有一个类锁
public static synchronized void sendMs(){
try {
TimeUnit.SECONDS.sleep(5);//休息5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
7.一个对象,一个静态同步方法,一个普通的同步方法,谁先执行?
8.两个对象,一个静态同步方法,一个普通的同步方法,谁先执行?
一旦声明为static的同步方法,这个方法只依赖于类锁,一个static 锁的是Class,一个是对象锁,互不影响,且sendMs有延迟,所以先打电话后发短信,
public class Demo6 {
public static void main(String[] args){
//两个对象,两把锁,一个static 锁的是Class,一个是对象锁,互不影响,且sendMs有延迟,所以先打电话后发短信,
Phone6 phone1 = new Phone6();
Phone6 phone2 = new Phone6();
new Thread(()->{
phone1.sendMs();
try {
TimeUnit.SECONDS.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone6{
//synchronized锁的对象是方法的调用者
//static修饰为静态方法,类一加载的时候就有了,锁的是Class,所以只有一个类锁
public static synchronized void sendMs(){
try {
TimeUnit.SECONDS.sleep(5);//休息5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
总结:普通的同步方法为对象锁,static修饰的同步方法为类锁。
六、集合类不安全问题
1.单线程安全
public class ListTest {
public static void main(String[] args){
List list = new ArrayList();
for (int i =1 ; i<=10 ; i++ ){
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}
}
}
2.多线程不安全
public class ListTest {
public static void main(String[] args){
List list = new ArrayList();
for (int i =1 ; i<=10 ; i++ ){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},"线程"+i).start();
}
}
}
执行失败:报出java.util.ConcurrentModificationException并发修改异常,线程不安全
3.多线程出现不安全的原因
线程安全:多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全:不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
List接口下面有两个实现,一个是ArrayList,另外一个是vector。
ArrayList类
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
vector类
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
根据源码可知,因为Vector的自带的方法前加了synchronized 关键字,Vector本身是线程安全的,即单独调用它的函数也是线程安全的(但是集合类的复合操作无法保证其线程安全性,详情参考),但是正因为有synchronized 的修饰,所以效率低,而ArrayList确是和它相反的,效率高,但是不安全。
一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:
- 在 Items[Size] 的位置存放此元素;
- 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 其实元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
4.解决方案
1.Vector类
List<String> list = new Vector<>();
源码
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
保证了add方法的线程安全。保证了数据的一致性,但由于加锁导致访问性能大大降低。
2.Collections工具类
用Collections工具类将线程不安全的ArrayList类转换为线程安全的集合类。
List<String> list = Collections.synchronizedList(new ArrayList<>());
3.CopyOnWriteArrayList类(写入复制,读写分离)
CopyOnWrite 写入时复制 ---- COW :计算机程序设计领域的一 种优化策略;
多个线程调用的时候,list, 读取的时候是固定的
在写入的时候避免覆盖,造成数据问题,写的时候会先把原来的数据复制一份,所以读写分离
List<String> list = new CopyOnWriteArrayList<>();
为什么用CopyOnWriteArrayList,不用Vector?
因为Vector的add方法被synchronized修饰,所以性能低,而看CopyOnWrite源码可知用的是lock锁,效率高
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//写时复制
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//写完后,再set回去
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
同理Set也可以用Collections工具类和CopyOnWriteArrayset来解决
//1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
Hashset底层
public HashSet() {
map = new HashMap<>();
}
//set的本质就是不重复的key
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT为常量,不变的值
private static final Object PRESENT = new Object();
由源码可知hashset底层就是创建了一个HashMap,set的本质就是不重复的key,PRESENT为常量,不变的值
map也存在ConcurrentModificationException,线程的不安全问题,
解决方法,可以用工具类Collections.synchronizedMap
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
另一钟解决方案ConcurrentHashMap
public class MapTest {
public static void main(String[] args){
//Map<String,String> map = new HashMap<>();
//1.Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
Map<String,String> map = new ConcurrentHashMap<>();
for (int i =1 ; i<=20 ; i++ ){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,4));
System.out.println(map);
},"线程"+i).start();
}
}
}
七、Callable
1.callable与runnable不同点
(1).可以有返回值,
(2).可以抛出异常
(3).方法不同 run()->call()
(4).支持泛型,泛型和返回值类型相同
2.Callable 创建线程
(1)创建实现类对象
(2)将实现类对象作为参数,创建FutureTask 适配器
(3)将适配器作为参数,创建Thread
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new MyThread());//适配类
new Thread(futureTask,"A").start();
//获取callable的返回结果
Integer res = (Integer) futureTask.get();
System.out.println(res);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call方法已经执行");
return 2021;
}
}
3.细节
(1)有缓存
两个Thread都调用call方法,但是只有一个输出结果,原因是有一个结果会被缓存,效率高
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被缓存,效率高
(2)结果可能会有等待,会阻塞
call()方法内可能包含耗时的操作,futureTask.get()方法可能产生阻塞,可以把它放到最后,或者使用异步通信来处理
八、常用辅助类
1、CountDownLatch-计数器
方法一:countDownLatch.countDown();//数量-1
方法二: countDownLatch.await();//等待计数器归0然后再执行后面的
每次有线程调用countDown()数量-1 ,假设计数器变为0 , countDownLatch.await()就会被唤醒,继续执行!
场景:假设有一个教师有10个人,只有10个人都离开教师才关门
//计数器
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//总数为10
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"回家了");
countDownLatch.countDown();//数量-1
},"线程"+i).start();
}
countDownLatch.await();//等待计数器归0然后再执行下面的操作
System.out.println("教室内10个学生走完了,关门!!");
}
}
2、CyclicBarrier-加法计数器
场景:集齐7颗龙珠召唤成龙
//加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤成龙成功");
});
for (int i = 1; i <= 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
try {
cyclicBarrier.await();//等待
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
},"收集第"+i+"颗龙珠").start();
}
}
}
3、Semaphore-信号量
方法一: semaphore.acquire();//请求,获得,假设已经满了,就等待,等待被释放为止。
方法二: semaphore.release(); //释放 ,会将当前的信号释放+1,然后唤醒等待的线程
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
场景:6辆车抢3个停车位
public class SemaphoreDemo {
public static void main(String[] args){
//线程数量-》三个停车位 ,限流,流量入口
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
//请求
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"正在停车");
TimeUnit.SECONDS.sleep(3);//停3秒
System.out.println(Thread.currentThread().getName()+"已经离开");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},"车辆"+i).start();
}
}
}