飞道的博客

JUC(一)

292人阅读  评论(0)




1、JUC简介

指java.util包下的三个工具类

java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks

实现多线程共有三种方式,我们传统的是:

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

使用实现Runnable几口的方式,代码没有返回值,效率相比较于实现Callable也比较低




2、线程和进程

一个进程包含多个线程

Java默认有两个线程:main线程、GC线程

Java是不能够开启线程的:他的底层调用的start0()是一个native方法,由底层的C++方法编写

private native void start0();

并发和并行

并发:CPU单核,多线个程操作同一个资源,通过快速交替的方式,达到一种并行的假象

并行:CPU多核,多个线程同时执行

我们可以使用代码的方式查看我们的电脑核数

package pers.mobian.demo01;

public class test01 {
    public static void main(String[] args) {
        //打印我们电脑的核数
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

并发编程的本质:充分利用CPU的资源

线程的状态

根据源码可得,状态共六种:

public enum State {
    //新生态
    NEW,
    //运行
    RUNNABLE,
    //阻塞
    BLOCKED,
    //等待(死等)
    WAITING,
    //超时等待
    TIMED_WAITING,
    //终止
    TERMINATED;
}

wait和sleep的区别

1、来自不同的类

wait:来自Object类

sleep:来自Thread类

2、所的释放不同

wait:会释放锁(清醒着的人好说话)

sleep:不会释放锁(睡着了的人,你说好话求情,他听不见)

3、使用的范围是不同的

wait:只能在同步代码块中使用

sleep:可以在任何地方使用

4、概念上的不同

sleep是Thread类的静态方法。sleep的作用是让线程休眠指定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件时恢复线程执行。wait是Object的方权法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。

5、是否会抛出异常

很多地方都说wait()不会抛出异常,但是我自己在实验的时候,也是需要抛出异常的,这个点有待斟酌…




3、Lock锁(重点)

3.1、传统的synchronized锁:队列锁

利用synchronized锁资源的代码实现:

package pers.mobian.demo01;

public class test01 {
    public static void main(String[] args) throws InterruptedException {
        // 并发:多线程操作同一个资源类,
        Ticket ticket = new Ticket();
        // @FunctionalInterface 函数式接口
        new Thread(() -> {
            for (int i = 1; i < 20; i++) {
                //把资源类丢入线程
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 20; i++) {
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 20; i++) {
                ticket.sale();
            }
        }, "C").start();
    }
}

class Ticket {
    //共卖40张票
    private int number = 30;

    // 卖票的方式 
    public synchronized void sale() {
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);
        }
    }
}

3.2、Lock接口

根据JDK帮助文档,我们可得:

Lock锁是一个接口,其所有的实现类为:

ReentrantLock(可重入锁)ReentrantReadWriteLock.ReadLock(可重入读写锁中的读锁)ReentrantReadWriteLock.WriteLock(可重入读写锁中的写锁)

该锁的使用方式

//通过接口实例化Lock锁的具体实现类
Lock l = ...; 
//加锁
l.lock(); 
try { 
    // access the resource protected by this lock 
} 
finally { 
    //解锁
    l.unlock(); 
} 

将上面的synchronized锁方法进行改写

public class lock {
    public static void main(String[] args) throws InterruptedException {
        //System.out.println(Runtime.getRuntime().availableProcessors());
        // 并发:多线程操作同一个资源类, 把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        // @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 }
        new Thread(() -> {
            for (int i = 1; i < 20; i++) {
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i < 20; i++) {
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i < 20; i++) {
                ticket.sale();
            }
        }, "C").start();

    }
}

class Ticket2 {

    private int number = 30;
    //1.实例化锁
    Lock l = new ReentrantLock();

    public void sale() {

        //2.加锁
        l.lock();
        try {
            //3.编写业务代码
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.解锁
            l.unlock();
        }
    }
}

补充:公平锁非公平锁

当我们实例化ReentrantLock(可重入锁)时,可以根据我们传递得参数,继而使用公平锁和非公平锁。

public ReentrantLock() {
    //无参构造非公平锁,默认使用非公平锁
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    //传递为false时,使用公平锁
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁:大家都十分得公平,遵循先来后到

非公平锁:大家不是同样的公平,可以出现插队的现象


3.3、synchronized和Lock锁的区别

1、synchronized是Java内置的关键字;Lock锁是Java的一个类

2、synchronized无法获取到锁的状态;Lock锁可以判断是否获取到了锁

3、synchronized会自动的释放锁;Lock必须要手动的去释放锁,如果不释放,就会造成死锁

4、synchronized中,线程一在获得锁的情况下阻塞了,第二个线程就只能傻傻的等着;Lock锁出现这种情况,可以使用tryLock()尝试获取锁

5、synchronized是不可中断的、非公平的、可重入锁;Lock锁是非公平的(可以自己设置)、可判断的、可重入锁

6、synchronized适合锁少量的同步代码;Lock锁适合锁大两的同步代码

7、synchronized有代码块锁和方法锁;Lock只有代码块锁

8、使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(拥有更多的子类)




4、生产者和消费者问题

4.1、生产者和消费者:synchronized版

代码实现:

package pers.mobian.providerconsumer;

public class SynTest01 {
    public static void main(String[] args) {

        Num num = new Num();
        //编写两个线程类
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();       
    }
}


class Num {

    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            //业务代码
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            //业务代码
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程
        this.notifyAll();
    }
}

在我们编写的一个简单的生产者和消费者的类中,如果我们出现两个以上的线程类的时候,就又会出现资源的不同步问题。

我们查阅JDK的帮助文档可得:

它推荐我们使用while,于是我们就可以将我们的代码修改为

package pers.mobian.providerconsumer;

public class SynTest01 {
    public static void main(String[] args) {

        Num num = new Num();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();


    }
}


class Num {

    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            //业务代码
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            //业务代码
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName() + "=>" + number);
        //通知其他线程
        this.notifyAll();
    }
}

总结:使用while代替if就可以防止虚假唤醒


4.2、JUC版的生产者和消费者

两种方式的转变:

修改synchronized锁的测试代码:

package pers.mobian.providerconsumer;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest01 {
    public static void main(String[] args) {

        Num2 num = new Num2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }
}

class Num2 {

    private int number = 0;

    //实例化锁
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();


    //+1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0) {
                //业务代码
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                //业务代码
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            //通知其他线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

对比于传统的synchronized版本的形式,JUC版本使用起来更加的复杂,不过随之而来的是更多的优势!!!


4.3、Conditional实现精准的通知唤醒

参考JDK帮助文档可得:

对应的编写我们的业务代码:

package pers.mobian.providerconsumer;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest02 {
    public static void main(String[] args) {

        Num3 num = new Num3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.printA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.printB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    num.printC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();
    }
}


class Num3 {

    private int number = 2;
    private Lock lock = new ReentrantLock();
    //用这三个conditional对象,来标记不同
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();


    //具体方法的实现步骤:业务判断 --> 执行 --> 通知
    //A
    public void printA() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number != 1) {
                condition1.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName() + "=>AAAAAAAAAAA");
            //通知其他线程
            condition2.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    //B
    public void printB() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number != 2) {
                condition2.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBBBB");
            //通知其他线程
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    //C
    public synchronized void printC() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number != 3) {
                condition3.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCC");
            //通知其他线程
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}




5、关于锁的八种现象

我们传统的线程模板

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            for (int i = 0; i < 45; i++) {
                phone.call();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 45; i++) {
                phone.send();
            }
        }, "B").start();
    }
}

class Phone {
    public void send() {
        System.out.println("发短信");
    }
    public void call() {
        System.out.println("打电话");
    }
}

这种访问方式,是随机访问的

下文会使用sleep方法,主要的目的是为了方法实际的运行顺序

现象一:我们对方法使用的synchronized关键字,先执行打电话

现象二:让我们的线程sleep4秒钟,结果依然还是先执行打电话

package pers.mobian.lock8;

import java.util.concurrent.TimeUnit;

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.call();
        }, "A").start();
        new Thread(() -> {
            phone.send();
        }, "B").start();
    }
}

class Phone {
    public synchronized void send() {
        System.out.println("发短信");
    }

    public synchronized void call() {
        //现象二时添加,让线程先睡4秒种
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }
}

结论:synchronized锁的是我们方法的调用者,并且我们开启的两个线程方法使用的是同一把锁,那么就会出现谁先拿到,谁执行的现象。以至于我们让call方法sleep了四秒,依然是call方法先执行。


现象三:在之前代码的基础上,新添加一个普通的方法,此时先执行hello方法

现象四:我们新实例化一个Phone对象,使用不同得对象去调用方法,此时先执行发短信

package pers.mobian.lock8;

import java.util.concurrent.TimeUnit;

public class Test02 {
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

        new Thread(() -> {
            phone1.call();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
//            phone1.hello();
            phone2.send();
        }, "B").start();
    }
}

class Phone2 {
    public synchronized void send() {
        System.out.println("发短信");
    }

    public synchronized void call() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }
    public void hello() {
        System.out.println("hello");
    }
}

总结:现象三中,由于这是一个普通的方法,所以就没有锁的效果,又由于时间的延迟,导致先打印hello。现象四中,由于我们使用了不同的对象,以至于synchronized锁的对象不是同一个,所以先打印发短信。


现象五:在之前代码的基础上,我们在方法前添加static关键字,此时先执行打电话

现象六:我们再添加一个对象,使用不同的对象进行方法的打印,此时依然是先执行打电话

package pers.mobian.lock8;

import java.util.concurrent.TimeUnit;

public class Test03 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        new Thread(() -> {
            phone1.call();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            //phone1.send();
            phone2.send();
        }, "B").start();
    }
}

class Phone3 {
    public static synchronized void send() {
        System.out.println("发短信");
    }

    public static synchronized void call() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }
}

总结:当我们添加了static关键字以后,此时我们先打印打电话,不仅是因为我们拿到的是同一个对象的锁,还因为我们的锁是直接锁的该类的Class模板。当我们再新添加一个对象的时,由于我们使用了static修饰,直接锁到了模板上,所以依然是先执行打电话。


现象七:当我们去掉一个锁方法的static关键字以后,此时先打印发短信

现象八:我们再新建一个对象后,使用不同的对象去调用,此时依然是先打印发短信

package pers.mobian.lock8;

import java.util.concurrent.TimeUnit;

public class Test04 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(() -> {
            phone1.call();
        }, "A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
//            phone1.send();
            phone2.send();
        }, "B").start();
    }
}

class Phone4 {
    public synchronized void send() {
        System.out.println("发短信");
    }

    public static synchronized void call() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }
}

总结:现象七中,我们只添加一个static关键字,即模板Class中只有一部分被锁,则先打印出发短信。现象八中,我们又新建了一个对象,同理,由于我们锁的东西不一样(打电话锁模板,发短信锁对象),所以不冲突,那么就先执行发短信。

只有当锁的对象或者模板是同一个的时候,才能够借助调用的顺序来执行。




6、不安全的集合类

6.1、List

List是不安全的集合类

如何将它变得安全?有如下几种方法:

  1. 将ArrayList集合变成Vector集合
  2. 使用Collections.synchronizedList( new ArrayList<>() )的方式创建集合
  3. 使用CopyOnWriteArrayList<>()

第三种方式为写入时复制。它相比较于Vector,效率更加高效,因为Vector底层使用的是synchronized锁,而在JDK1.8中CopyOnWriyteArrayList底层是Lock锁(不过JDK11中貌似使用的又是synchronized锁)


6.2、Set

Set也是不安全的集合类

同样,将不安全的集合变成安全集合的方法:

  1. 使用Collections.synchronizedList( new HashSet<>() )的方式创建集合
  2. 使用CopyOnWriteArraySet<>()

补充:Set的底层是什么?(后面我也会写一些关于这三个集合的底层源码)

//默认的空参初始化方法
public HashSet() {
	map = new HashMap<>();
}

//使用HashSet的add方法,依然是调用HashMap的底层put方法
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

即HashSet的底层就是HashMap


6.3、Map

Map也是不安全的集合类

将不安全的集合变成安全集合的方法:

  1. 使用Collections.synchronizedMap( new HashMap() )的方式创建集合
  2. 使用ConcurrentHashMap<>()




7、Callable

由JDK帮助文档可得

相比较于之前的实现Runnable方法,实现Callable有哪些好处?

  1. Callable含有返回值
  2. Callable可以抛出异常
  3. Runnable使用的是run方法,而Callable则是call方法

由底层的源码可知,我们是没有办法直接调用这个实例的,我们需要在两者之间添加一层关系。

我们在Runnable的实现类中找到了一个FutureTask类,并且发现FutereTask是可以接受Callable对象的。

即,我们只需要创建一个FutureTask的实例对象,就可以用来接收实现Callable的线程类的实例对象,最后就只需要将FutureTask的实例放入线程中开启就可以了。

具体代码如下:

package pers.mobian.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Mythread mythread = new Mythread();
        FutureTask<String> stringFutureTask = new FutureTask<>(mythread);//适配类
        new Thread(stringFutureTask,"A").start();
        //如果再添加一条语句,结果还是打印一条结果,因为有缓存的存在,用于提高效率
        new Thread(stringFutureTask,"B").start();

        //这个get方法,可能会产生阻塞,应该放在代码的最后,或者使用异步通信来处理
        System.out.println(stringFutureTask.get());

    }
}

//传递的参数类型,就是我们需要返回时的参数类型
class Mythread implements Callable<String> {
    public String call() {
        System.out.println("成功调用call方法");
        return "OK";
    }
}




8、常用的辅助类

8.1、CountDownLatch

简单理解为减法计数器

测试代码实例:

package pers.mobian.add;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest01 {
    public static void main(String[] args) throws InterruptedException {
        //设置需要执行的任务的总数量
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown();//数量-1
            }, String.valueOf(i)).start();
        }
        //等待计数器归零以后,再继续向下执行
        countDownLatch.await();
        System.out.println("Close door");
    }
}

总结:countDownLatch.countDown()每执行一次,数量就会 -1 ,当数量变成0以后,countDownLatch.await()就会被唤醒,继而执行后面的方法


8.2、CyclicBarrier

减法器

package pers.mobian.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest01 {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            System.out.println("上了五天课,可以回家休息了");
        });

        for (int i = 1; i <= 5; i++) {
            //使用lambda表达式,相当于重新编写了一个类,是没有办法直接使用到上级的变量的
            //此时需要我们自己定义一个临时变量来接受
            int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"上了"+temp+"天课了");

                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

总结:当i变成5以后,就会执行cyclicBarrier对象中的接口方法。值得注意的是,如果我们设置的parties比较小的话,就会提前执行该方法


8.3、Semaphore

计数器

package pers.mobian.add;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreTest01 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 7; i++) {
            new Thread(() -> {
                try {
                    //获取
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "占位三秒钟");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("三秒钟到" + Thread.currentThread().getName() + "离开位置");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

总结:semaphore.acquire()假如我们的信号量已经满了,就会等待,直到semaphore释放;semaphore.release()会将当前的信号量释放,然后唤醒等待的线程。

作用:多个共享资源互斥的使用、并发限流、控制最大的线程数。


转载:https://blog.csdn.net/qq_44377709/article/details/105845427
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场