JUC中的常见类

简介: JUC中的常见类

一.ReentrantLock

ReentrantLock是juc包中提供的一种锁,用来保证线程安全,其相对于synchronized存在一些不同点

上锁方式:ReentrantLock是手动进行上锁和解锁的,这就意味着相对于synchronized,reentrantlock更容易发生死锁现象,(当上锁之后,在解锁之前发生异常,这时候解锁无法进行,其他线程想要获取锁,这时候会发生死锁现象),为了避免死锁现象的产生,我们必须将reentrantLock上锁到解锁的代码放入trycatch finally代码块中,保证解锁的执行

ReentrantLock reentrantLock1 = new ReentrantLock();

       try {

           reentrantLock1.lock();

       }catch (Exception e){

           e.printStackTrace();

       }finally {

           reentrantLock1.unlock();

       }

需要注意的是ReentrantLock()中有一个tryLock()方法,它有一定的等待时间,如果在等待时间内获取不到锁,就放弃索取该锁,去执行其他任务

2.相对于synchronized,其支持公平锁:

ReentrantLock reentrantLock = new ReentrantLock(true);

9a5d2aac734727e02ed3ebd78620d909.png

3.阻塞和唤醒的方式:我们知道,synchronized使用的是Object类中的wait()、notify()和notifyall()的方法进行阻塞和唤醒,而ReentrantLock进行阻塞和解锁的方式如下:

//创建对象

       ReentrantLock reentrantLock = new ReentrantLock(true);

       //需要手动进行上锁和解锁

       try {

           reentrantLock.lock();

           //创建condition对象

           Condition condition = reentrantLock.newCondition();

           condition.await();

           condition.signal();

           condition.signalAll();

reentrantLock对象利用newCondition()方法,创建阻塞对象(在底层创建一个条件对象),使用其await()、signal()、和signalAll()方法进行阻塞和唤醒,但是synchronized在底层维护一个阻塞队列和就绪队列,但是ReentrantLock中的每一个condition对象都会维护一个阻塞队列,不同线程会根据条件的不同进入不同的阻塞队列,最终维护一个就绪队列和多个阻塞队列

442cb110bc08e929b9b1f9340eba7d0c.png

4.ReentrantLock支持读写锁:我们可以利用ReentrantReadWriteLock类创建读锁和写锁,其中读锁是共享锁,写锁是独占锁

ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

           //获取读锁

           ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();

           //获取写锁

           ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

二.原子类

原子类是利用CAS来来保证线程安全的实现方式,其性能要比加锁操作高很多


image.png

我们写以下代码来体会其作用:

public class TestAtomic {

   public static void main(String[] args) throws InterruptedException {

       AtomicInteger atomicInteger = new AtomicInteger();

        Thread t1 =new Thread(()->{

                   for (int i=0;i<50000  ;++i){

                       atomicInteger.getAndIncrement();

                   }

                });

        t1.start();

         Thread t2 =new Thread(()->{

             for (int i = 0; i < 50000; i++) {

                 atomicInteger.getAndIncrement();

             }

                 });

         t2.start();

         t1.join();

         t2.join();

       System.out.println(atomicInteger);

   }

}

image.png

其内部机制:利用CAS+自旋保证原子性

image.png

我们没有使用synchronized进行上锁,却没有产生原子性问题,所以它的作用就像它的名字一样:保持原子性,我们去说明其中的几个方法:

image.png

三.JUC中的工具类

3.1 Semaphore(信号量)

semaphore用来记录可用资源的个数,在本质上说是一个计数器

可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源.

当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)

当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)

如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.


import java.util.concurrent.Semaphore;

import java.util.concurrent.TimeUnit;

 

/**

* @author tongchen

* @create 2023-02-08 16:06

*/

public class SemaphoreTest {

   public static void main(String[] args) {

       //创建信号量

       Semaphore semaphore = new Semaphore(5);

       //创建任务

    Runnable runnable=new Runnable() {

           @Override

           public void run() {

               //消耗资源

               try {

                   semaphore.acquire();

                   System.out.println( Thread.currentThread().getName()+"获取到资源了");

                   //执行任务

                   System.out.println(Thread.currentThread().getName()+"在执行任务");

                   //释放资源

                   TimeUnit.SECONDS.sleep(2);

                   System.out.println(Thread.currentThread().getName()+"释放资源了");

                   semaphore.release();

               } catch (InterruptedException e) {

                   throw new RuntimeException(e);

               }

 

           }

       };

    //通过for循环不断创建线程

       for (int i = 0; i <10 ; i++) {

            Thread thread=new Thread(runnable,i+"");

            thread.start();

       }

 

 

   }

}

3.2 CountDownLatch

同时等待 N 个任务执行结束.

好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.TimeUnit;

 

/**

* @author tongchen

* @create 2023-02-08 16:21

*/

public class CountDownLatchTest {

   public static void main(String[] args) throws InterruptedException {

       //创建10个任务

       CountDownLatch countDownLatch = new CountDownLatch(10);

       Runnable runnable=new Runnable() {

           @Override

           public void run() {

               //准备就绪

               System.out.println(Thread.currentThread().getName()+"准备就绪");

               //开始运行

               System.out.println(Thread.currentThread().getName()+"开始运行");

               try {

                   TimeUnit.SECONDS.sleep(2);

                   System.out.println(Thread.currentThread().getName()+"运行结束");

                   countDownLatch.countDown();

 

               } catch (InterruptedException e) {

                   throw new RuntimeException(e);

               }

           }

       };

       for (int i = 0; i < 10; i++) {

           Thread thread=new Thread(runnable,"任务"+i);

           thread.start();

       }

       //等待任务全部执行结束

       countDownLatch.await();

       System.out.println("任务全部执行结束了......");

   }

}

a47b1939aac5359e867fb4763f4b7cb9.png

7c06f5b47f394ad61ee81e2fcde408f2.png

3.3线程安全的集合类

当我们在多线程中使用集合类,同样会产生线程安全问题:

import java.util.ArrayList;

import java.util.Arrays;

 

/**

* @author tongchen

* @create 2023-02-08 16:34

*/

public class ArraylistWithMultithreading {

   public static void main(String[] args) {

       //创建集合

       ArrayList<Integer> arrayList = new ArrayList<>();

       //创建任务并加入多线程

       for (int i = 0; i < 10; i++) {

           int x=i;

           Thread  thread=new Thread(()->{

                   arrayList.add(x);

           });

           thread.start();

           System.out.println(arrayList);

       }

       System.out.println("-----------------------");

       System.out.println(arrayList);

   }

}

6402f89350ac988d2e01f85f53220afe.png

我们如何解决集合类中的线程安全问题呢?

  1. 手动加锁,使用synchronized包裹代码块或者使用reentrantlock手动加锁释放锁(一定要注意线程问题存在很多,我们在不同线程中进行读和写的操作都要记得加锁)

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CopyOnWriteArrayList;

import java.util.concurrent.locks.ReentrantLock;

 

/**

* @author tongchen

* @create 2023-02-08 16:34

*/

public class ArraylistWithMultithreading {

  static Object loker=new Object();

   public static void main(String[] args) {

       //创建集合

       ArrayList<Integer> arrayList = new ArrayList<>();

       List<Integer> list = Collections.synchronizedList(arrayList);

       ReentrantLock reentrantLock = new ReentrantLock();

       CopyOnWriteArrayList<Integer> integers = new CopyOnWriteArrayList<>(arrayList);

       //创建任务并加入多线程

       for (int i = 0; i < 11; i++) {

 

 

               int x=i;

               Thread  thread=new Thread(()->{

 

                   reentrantLock.lock();

                       arrayList.add(x);

                   reentrantLock.unlock();

 

 

               });

           thread.start();

           reentrantLock.lock();

           System.out.println(arrayList);

           reentrantLock.unlock();

 

       }

       System.out.println("-----------------------");

   }

}

  1. 使用vector等线程安全的集合
  2. 使用工具类:使用Conllections类,将集合包裹

 

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CopyOnWriteArrayList;

 

/**

* @author tongchen

* @create 2023-02-08 16:34

*/

public class ArraylistWithMultithreading {

  static Object loker=new Object();

   public static void main(String[] args) {

       //创建集合

       ArrayList<Integer> arrayList = new ArrayList<>();

       List<Integer> list = Collections.synchronizedList(arrayList);

       CopyOnWriteArrayList<Integer> integers = new CopyOnWriteArrayList<>(arrayList);

       //创建任务并加入多线程

       for (int i = 0; i < 11; i++) {

 

 

               int x=i;

               Thread  thread=new Thread(()->{

 

                       list.add(x);

 

 

               });

               thread.start();

 

 

           System.out.println(list);

       }

       System.out.println("-----------------------");

       System.out.println(list);

   }

}

使用CopyOnWriteArraylist

如果是读Arraylist,不需要进行加锁,但是如果是对ArrayList进行写操作时,

此时使用 CopyOnWriteArrayList就是把这个ArrayList'给复制了一份,先修改

副本,修改之后引用再指向副本,保证修改的同时对于读操作是没有任何影响的,读的时候先读旧的版本,不会出现读到一个没修改完的中间状态,适用于多读少写的业务场景

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.List;

import java.util.concurrent.CopyOnWriteArrayList;

 

/**

* @author tongchen

* @create 2023-02-08 16:34

*/

public class ArraylistWithMultithreading {

  static Object loker=new Object();

   public static void main(String[] args) throws InterruptedException {

       //创建集合

       ArrayList<Integer> arrayList = new ArrayList<>();

       List<Integer> list = Collections.synchronizedList(arrayList);

       CopyOnWriteArrayList<Integer> integers = new CopyOnWriteArrayList<>(arrayList);

       //创建任务并加入多线程

       for (int i = 0; i < 5; i++) {

 

 

               int x=i;

               Thread  thread=new Thread(()->{

 

                       integers.add(x);

 

 

               });

               thread.start();

           

 

 

           System.out.println(integers);

       }

       System.out.println("-----------------------");

       System.out.println(integers);

   }

}

相关文章
|
8月前
|
消息中间件 监控 Java
JUC第二十六讲:JUC工具类: CountDownLatch详解
JUC第二十六讲:JUC工具类: CountDownLatch详解
|
18天前
|
Java
Java并发Futures和Callables类
Java程序`TestThread`演示了如何在多线程环境中使用`Futures`和`Callables`。它创建了一个单线程`ExecutorService`,然后提交两个`FactorialService`任务,分别计算10和20的阶乘。每个任务返回一个`Future`对象,通过`get`方法获取结果,该方法会阻塞直到计算完成。计算过程中模拟延迟以展示异步执行。最终,打印出10!和20!的结果。
25 10
|
18天前
|
安全 Java API
JUC的常见类
JUC的常见类
27 0
|
11月前
|
存储 安全 算法
一天一个 JUC 工具类 -- 并发集合
使用JUC工具包中的并发集合,我们可以避免手动处理锁和同步的复杂性,从而降低出现线程安全问题的概率。这些并发集合通过内部采用高效的算法和数据结构来优化并发操作,从而提供更好的性能和扩展性。
|
10月前
|
安全 Java
Java中的线程同步与同步器
Java中的线程同步与同步器
116 1
|
10月前
|
存储 安全 Java
JUC并发编程(JUC核心类、TimeUnit类、原子操作类、CASAQS)附带相关面试题
1.JUC并发编程的核心类,2.TimeUnit(时间单元),3.原子操作类,4.CAS 、AQS机制
42 0
|
11月前
|
存储 安全 Java
一天一个 JUC 工具类 -- AQS
AbstractQueuedSynchronizer(AQS)是Java中用于构建锁和同步器的抽象基类。它是Java并发工具包(java.util.concurrent)中实现高级线程同步控制的关键组件之一。AQS提供了一种基于等待队列的同步器框架,允许开发者构建自定义的同步器。在这篇文章中我们将从源码分析和底层原理的角度来介绍AQS。
|
11月前
|
人工智能 移动开发 Java
【Java基础】线程同步类 CountDownLatch
CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。 CountDownLatch 基于AQS构建同步器: AQS - AbstractQueuedSynchronizer ,即抽象的队列同步器,是一种用来**构建锁和同步器**的框架。
|
12月前
|
安全 Java 程序员
【JavaEE】Callable接口(NO.6线程创建方法)-JUC的常见类-与线程安全有关集合类
JavaEE & Callable接口(NO.6线程创建方法) & JUC的常见组件 & 与线程安全有关类和集合类
33 0
|
Java API 索引
【JUC基础】08. 三大工具类
JUC包中包含了三个非常实用的工具类:CountDownLatch(倒计数器),CyclicBarrier(循环栅栏),Semaphore(信号量)。
109 0