《深入探索Java并发编程&从锁到并发工具的深入解析》(上)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 《深入探索Java并发编程&从锁到并发工具的深入解析》(上)

JUC是什么?

JUC就是java.util.concurrent下面的类包,专门用于多线程的开发

关于锁

传统锁synchronized

如下有三个线程A、B、C线程去争夺ticket资源,sale()方法前面用synchronized修饰以后,这个方法的调用对象 ticket就被上锁了,每个线程去用这个对象调用sale()方法的时候都会独立运行。如果你不加这个synchronized就会出现A、B、C线程争夺资源的情况。

在这里,我们说 的本质就是:队列。 线程排队去获得CPU执行线程,执行完毕下一个线程上。

package org.example;
/**
 * @author linghu
 * @date 2023/12/15 15:02
 */
public class Demo01 {
    public static void main(String[] args) {
        final Ticket ticket=new Ticket();
        //启动线程A
        new Thread(()->{
            for (int i=0;i<40;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i=0;i<40;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<40;i++){
                ticket.sale();
            }
        },"C").start();
    }
}
class Ticket{
    private int number=30;
    //卖票的方式
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票剩余"+
                    number+"张票");
        }
    }
}

加了synchronized运行如下图:

Lock锁

lock三部曲:

  • 创建锁
final Ticket ticket=new Ticket();
  • 加锁
lock.lock();
  • 解锁
lock.unlock();
package org.example;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author linghu
 * @date 2023/12/15 15:02
 */
public class Demo01 {
    public static void main(String[] args) {
        final Ticket ticket=new Ticket();
        //启动线程A
        new Thread(()->{
            for (int i=0;i<40;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i=0;i<40;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<40;i++){
                ticket.sale();
            }
        },"C").start();
    }
}
//lock三部曲
class Ticket{
    private int number=30;
    //1、创建锁
    Lock lock=new ReentrantLock();
    //卖票的方式
    public synchronized void sale(){
        //2、开启锁
        lock.lock();
        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票剩余"+
                        number+"张票");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //3、关闭锁
            lock.unlock();
        }
    }
}

上面的代码中,被lock三部曲锁上的代码,只能由一个线程执行。

synchronized和Lock的区别

生产者和消费者问题

synchronized版生产者和消费者问题
package org.example;
/**
 * @author linghu
 * @date 2023/12/16 16:45
 * A num+1
 * B num-1
 * 顺序:判断->业务->通知
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        //A:num+1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //B:num-1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
class Data{
    private int number=0;
    //+1
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1完毕了
        this.notify();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1完毕了
        this.notify();
    }
}

注:如果有四个线程,就会出现假唤醒问题。

我们添加如下四个线程A、B、C、D:

package org.example;
/**
 * @author linghu
 * @date 2023/12/16 16:45
 * A num+1
 * B num-1
 * 顺序:判断->业务->通知
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        //A:num+1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //B:num-1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Data{
    private int number=0;
    //+1
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1完毕了
        this.notify();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1完毕了
        this.notify();
    }
}

那么什么是虚假唤醒呢?

多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。

比如:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒。

为了防止虚假唤醒,我们在这里采用将if换为while:

package org.example;
/**
 * @author linghu
 * @date 2023/12/16 16:45
 * A num+1
 * B num-1
 * 顺序:判断->业务->通知
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        //A:num+1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //B:num-1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Data{
    private int number=0;
    //+1
    public synchronized void increment() throws InterruptedException {
        while (number!=0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1完毕了
        this.notify();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        while(number==0){
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,我-1完毕了
        this.notify();
    }
}
JUC版的生产者消费者问题

package org.example;
/**
 * @author linghu
 * @date 2023/12/16 16:45
 * A num+1
 * B num-1
 * 顺序:判断->业务->通知
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
      
    }
}
class Data2{
    private int number=0;
    //+1
    public  void increment() throws InterruptedException {
         while (number!=0){
            //等待
           
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
    }
    //-1
    public void decrement() throws InterruptedException {
         while (number==0){
            //等待
           
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
    }
}

我们用传统三剑客写的生产者消费者,现在把它删掉,准备用上面的代码进行改进,改成JUC版本的生产者消费者。

JUC的代码如下:

package org.example;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author linghu
 * @date 2023/12/16 16:45
 * A num+1
 * B num-1
 * 顺序:判断->业务->通知
 */
public class B {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        //A:num+1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data2.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        //B:num-1
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data2.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data2.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data2.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").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){
                //等待
                condition.await();
            }
            //业务代码...
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();//通知其他线程,我+1完毕
        } 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();
        }
    }
}

执行结果如下:

我们现在希望按照:A->B->C->D的顺序去进行执行,精准通知唤醒我们的线程!

如下代码中,我们实现了A->B->C->D的顺序,主要是靠 Condition监控器,我们设置了三个监控器:

Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

唤醒方式如下:

public void weakA()
    {
        lock.lock();
        try {
            while(num != 1)
            {
                conditionA.await();
            }
           //业务代码...
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void weakB()
    {
        lock.lock();
        try {
            while(num != 2)
            {
                conditionB.await();
            }
             //业务代码...
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void weakC()
    {
        lock.lock();
        try {
            while(num != 3)
            {
                conditionC.await();
            }
              //业务代码...
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

如下为完整代码:

package org.example;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author linghu
 * @date 2023/12/18 14:44
 */
class Aweaken
{
    ReentrantLock lock = new ReentrantLock();
    int num = 1;
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();
    public void weakA()
    {
        lock.lock();
        try {
            while(num != 1)
            {
                conditionA.await();
            }
            num = 2;
            System.out.println("现在是线程 "+Thread.currentThread().getName()+", 下一个应该是线程B");
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void weakB()
    {
        lock.lock();
        try {
            while(num != 2)
            {
                conditionB.await();
            }
            num = 3;
            System.out.println("现在是线程 "+Thread.currentThread().getName()+", 下一个应该是线程C");
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void weakC()
    {
        lock.lock();
        try {
            while(num != 3)
            {
                conditionC.await();
            }
            num = 1;
            System.out.println("现在是线程 "+Thread.currentThread().getName()+", 下一个应该是线程A");
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class C {
    public static void main(String[] args) {
        Aweaken b = new Aweaken();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                b.weakA();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                b.weakB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                b.weakC();
            }
        },"C").start();
    }
}

执行效果:

8锁现象

在Java并发编程中,锁是一种关键的同步机制,用于控制多个线程对共享资源的访问。然而,在某些情况下,使用锁可能会引发性能问题,其中一个典型例子就是JUC中的8锁现象,也被称为锁粗化。

那何为8锁呢?

8锁现象是一种并发编程中的现象,主要涉及到Java中的synchronized关键字。这个术语来源于对锁的八个问题的探讨,每个问题都围绕着锁的不同行为和效果展开。

  1. 标准情况下,两个线程先打印“发短信”还是“打电话”?
  2. “打电话”方法暂停4秒钟,两个线程先打印“发短信”还是“打电话”?
  3. 两个普通的锁方法,new一个对象调用,调用过程中间睡1秒,执行结果是什么?
  4. 两个普通的锁方法,new两个对象调用,调用过程中间睡1秒,执行结果是什么?
  5. 两个普通的锁方法,一个对象调用,一个类调用,调用过程中间睡1秒,执行结果是什么?
  6. 两个普通的锁方法,一个对象调用,一个类调用,但是类上加了synchronized关键字,调用过程中间睡1秒,执行结果是什么?
  7. 两个普通的锁方法,一个对象调用,一个类调用,但是类上加了synchronized关键字和方法上加了synchronized关键字,调用过程中间睡1秒,执行结果是什么?
  8. 两个普通的锁方法,一个对象调用,一个类调用,但是类上加了synchronized关键字和方法上加了synchronized关键字,但是方法上加了final关键字,调用过程中间睡1秒,执行结果是什么?

我们将上面的8个问题进行抽象:

案例一(先打印“发短信”)
  • 标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话。
  • sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话。
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone{
    // synchronized 锁的对象是方法的调用者!、
    // 两个方法用的是同一个锁,谁先拿到谁执行!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

运行效果:

💧案例一总结:synchronized 锁的对象是方法的调用者,两个方法用的是同一个锁,谁先拿到谁执行。

案例二(先打印“打电话”)
  • 增加了一个普通方法后!先执行发短信还是Hello? 普通方法。
  • 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
package org.example;
import java.util.concurrent.TimeUnit;
public class Test2  {
    public static void main(String[] args) {
        // 两个对象,两个调用者,两把锁!
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone2{
    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    // 这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

运行效果:

💧案例二总结:synchronized 锁的对象是方法的调用者,hello() 没有锁!不是同步方法,所以不受锁的影响。

案例三(先打印“发短信”)

增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?

两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?

package org.example;
import java.util.concurrent.TimeUnit;
public class Test3  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone3{
    // synchronized 锁的对象是方法的调用者!
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

运行效果:

💧案例三总结:两个对象的Class类模板只有一个。static 静态方法在类一加载就有了!锁的是Class。

案例四(先打印“打电话”)

1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?

1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?

package org.example;
import java.util.concurrent.TimeUnit;
public class Test4  {
    public static void main(String[] args) {
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
// Phone3唯一的一个 Class 对象
class Phone4{
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    // 普通的同步方法  锁的调用者
    public synchronized void call(){
        System.out.println("打电话");
    }
}

运行效果:

💧案例四总结:静态的同步方法 锁的是 Class 类模板,普通的同步方法 锁的调用者。

new this 具体的一个手机

static Class 唯一的一个模板

集合不安全

所谓集合不安全,就是指:在高并发的情况下,创建线程使用集合会报异常!

我们看一下常用集合线程安全问题:

  1. List不安全
  2. ArrayList不安全
  3. Set不安全
  4. Map不安全
List集合不安全

我们通过如下代码,循环30次创建线程,在线程中用到List集合,我还在业务代码那里 try-catch一下,捕捉出现的异常。

/**
 * @author linghu
 * @date 2023/12/19 11:38
 */
public class ListTest {
    public static void main(String[] args) {
        List<Object> arrayList = new ArrayList<>();
        for (int i=1;i<=30;i++){
            try {
                new Thread(()->{
                    arrayList.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

代码运行结果:

**结论:**List集合和ArrayList集合在多线程中使用不安全

List集合不安全的解决方案

我们可以采用 COW的思想解决这个不安全问题。所谓 COW是指 CopyOnWriteArrayList写入时复制!是计算机程序领域的一种优化策略。

解决方案:

  • new Vector<>()
  • new CopyOnWriteArrayList<>()

代码如下:

/**
 * @author linghu
 * @date 2023/12/19 11:38
 */
public class ListTest {
    public static void main(String[] args) {
        /**
         * 解决方案
         * 1、List<String> list = new Vector<>();
         * 2、List<String> list = new CopyOnWriteArrayList<>();
         */
//        List<Object> arrayList = new ArrayList<>();
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i=1;i<=30;i++){
            try {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

结论: CopyOnWriteArrayList写入时复制!能解决集合的线程安全问题!

总结
  • Vector底层使用的是 synchronized来实现的,所以效率特别低下。
  • CopyOnWriteArrayList使用的是 Lock锁,效率会更加高效。
Set集合不安全

Set和List同理可得:多线程情况下,普通的Set集合是线程不安全的。

如下代码,我在循环30次中执行线程,创建set集合:

package org.example;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
 * @author linghu
 * @date 2023/12/19 14:27
 */
public class SetTest {
    public static void main(String[] args) {
        /**
         * 解决方案
         */
        Set<String> set = new HashSet<>();
        for (int i=1;i<=30;i++){
            try {
                new Thread(()->{
                    set.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(set);
                },String.valueOf(i)).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行效果:

List集合不安全的解决方案

解决方案如下:

  • CopyOnWriteArraySet<>()
  • HashSet<>()

我们还是用了类似的原理:写入时复制

代码如下:

package org.example;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
 * @author linghu
 * @date 2023/12/19 14:27
 */
public class SetTest {
    public static void main(String[] args) {
        /**
         * 解决方案
         * 1、Set<String> set = new CopyOnWriteArraySet<>();
         */
//        Set<String> set = new HashSet<>();
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i=1;i<=30;i++){
            try {
                new Thread(()->{
                    set.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(set);
                },String.valueOf(i)).start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

结论: CopyOnWriteArraySet写入时复制!能解决集合的线程安全问题!

总结
  • HashSet的底层是HashMap
  • CopyOnWriteArraySet写入时复制!能解决集合的线程安全问题!

Callable

Callable的出现主要是为了弥补继承Thread或实现Runnable接口的线程执行完成后,无法获得线程执行后的结果。

Callable其实上手是非常简单容易得!

我们看通过Callable创建线程的方式如下:

class MyThread implements Callable<String>{
    //这里的泛型返回类型和call方法的返回类型是一样的
    @Override
    public String call() throws Exception {
        return "hi,call";
    }
}

上面,线程 MyThread通过继承接口Callable,这里的String其实是泛型。如下的call方法的类型和这个泛型相同。

如下代码,我们通过接口 Callable 创建了A、B线程执行 call方法。

package org.example;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * @author linghu
 * @date 2023/12/19 16:42
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //?怎么启动Callable
        MyThread myThread = new MyThread();
        //适配类
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//结果被缓存,效率高,结果只打印一次
        //获取Callable的返回结果
        String o = (String)futureTask.get();
        System.out.println(o);
    }
}
class MyThread implements Callable<String>{
    //这里的泛型返回类型和call方法的返回类型是一样的
    @Override
    public String call() throws Exception {
        return "hi,call";
    }
}

执行结果:

高并发常用-辅助类

在高并发场景中,常用辅助类如下:

  • CountDownLatch
  • CyclickBarrier
  • Semaphore

CountDownLatch

其实CountDownLatch的本质就是一个减法计数器

比如我们去游乐园坐激流勇进,有的时候游乐园里人不是那么多,这时,管理员会让你稍等一下,等人坐满了再开船,这样的话可以在一定程度上节约游乐园的成本。座位有多少,就需要等多少人,这就是 CountDownLatch 的核心思想,等到一个设定的数值达到之后,才能出发。

上面这幅图就很好理解了。可以看到,最开始 CountDownLatch 设置的初始值为 3,然后 T0 线程上来就调用 await 方法,它的作用是让这个线程开始等待,等待后面的 T1、T2、T3,它们每一次调用 countDown 方法,3 这个数值就会减 1,也就是从 3 减到 2,从 2 减到 1,从 1 减到 0,一旦减到 0 之后,这个 T0 就相当于达到了自己触发继续运行的条件,于是它就恢复运行了。

我们可以限制6个线程,等待他们都执行完毕,代码如下:

package org.example;
import java.util.concurrent.CountDownLatch;
/**
 * @author linghu
 * @date 2023/12/20 9:44
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6个线程
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i=1;i<=6;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Go out");
                countDownLatch.countDown();//数量-1,本质是阻塞
            },String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归0,然后往下执行~
        System.out.println("Close Door");
    }
}

总结
  • countDown()是减一操作
  • await是等待计数器归0操作

《深入探索Java并发编程&从锁到并发工具的深入解析》(下)+https://developer.aliyun.com/article/1625014

目录
相关文章
|
消息中间件 存储 前端开发
[笔记]C++并发编程实战 《四》同步并发操作(三)
[笔记]C++并发编程实战 《四》同步并发操作(三)
|
2月前
|
SQL 分布式计算 前端开发
《深入探索Java并发编程&从锁到并发工具的深入解析》(下)
《深入探索Java并发编程&从锁到并发工具的深入解析》(下)
13 0
|
3月前
|
存储 监控 算法
深入探究Java线程池:提升并发性能的利器
在当今高度并发的应用开发中,Java线程池作为一种广泛应用的并发编程技术,提供了一种优雅且高效的线程管理方案。本文深入探究Java线程池的相关技术,涵盖其核心概念、优势、常见类型(如FixedThreadPool、CachedThreadPool、SingleThreadExecutor、ScheduledThreadPool、ForkJoinPool及WorkStealingPool)、核心参数配置、异常处理与监控方法,以及性能调优的最佳实践,帮助读者更好地理解和应用线程池,从而提升并发性能。
|
4月前
|
存储 缓存 安全
聊一聊高效并发之线程安全
该文章主要探讨了高效并发中的线程安全问题,包括线程安全的定义、线程安全的类别划分以及实现线程安全的一些方法。
|
5月前
|
安全 Java 调度
Java面试题:Java内存优化、多线程安全与并发框架实战,如何在Java应用中实现内存优化?在多线程环境下,如何保证数据的线程安全?使用Java并发工具包中的哪些工具可以帮助解决并发问题?
Java面试题:Java内存优化、多线程安全与并发框架实战,如何在Java应用中实现内存优化?在多线程环境下,如何保证数据的线程安全?使用Java并发工具包中的哪些工具可以帮助解决并发问题?
65 0
|
5月前
|
Java 测试技术 容器
多线程编程基础与并发问题解决方案
多线程编程基础与并发问题解决方案
|
7月前
|
安全 Go 对象存储
C++多线程编程:并发与同步的实战应用
本文介绍了C++中的多线程编程,包括基础知识和实战应用。C++借助`&lt;thread&gt;`库支持多线程,通过`std::thread`创建线程执行任务。文章探讨了并发与同步的概念,如互斥锁(Mutex)用于保护共享资源,条件变量(Condition Variable)协调线程等待与通知,以及原子操作(Atomic Operations)保证线程安全。实战部分展示了如何使用多线程进行并发计算,利用`std::async`实现异步任务并获取结果。多线程编程能提高效率,但也需注意数据竞争和同步问题,以确保程序的正确性。
|
7月前
|
缓存 安全 算法
Java并发简介(什么是并发)
Java并发简介(什么是并发)
|
安全 Java 调度
Java并发编程学习1-并发简介
本篇介绍并发简介,带大家走近Java并发编程的世界
74 0
Java并发编程学习1-并发简介
|
存储 前端开发 Unix
[笔记]C++并发编程实战 《四》同步并发操作(二)
[笔记]C++并发编程实战 《四》同步并发操作(二)