一.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);
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对象都会维护一个阻塞队列,不同线程会根据条件的不同进入不同的阻塞队列,最终维护一个就绪队列和多个阻塞队列
4.ReentrantLock支持读写锁:我们可以利用ReentrantReadWriteLock类创建读锁和写锁,其中读锁是共享锁,写锁是独占锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();//获取读锁ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();//获取写锁ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
二.原子类
原子类是利用CAS来来保证线程安全的实现方式,其性能要比加锁操作高很多
我们写以下代码来体会其作用:
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);}
}
其内部机制:利用CAS+自旋保证原子性
我们没有使用synchronized进行上锁,却没有产生原子性问题,所以它的作用就像它的名字一样:保持原子性,我们去说明其中的几个方法:
三.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() {@Overridepublic 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() {@Overridepublic 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("任务全部执行结束了......");}
}
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 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);}
}
我们如何解决集合类中的线程安全问题呢?
手动加锁,使用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 arrayList = new ArrayList<>();List list = Collections.synchronizedList(arrayList);ReentrantLock reentrantLock = new ReentrantLock();CopyOnWriteArrayList 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("-----------------------");}
}
使用vector等线程安全的集合
使用工具类:使用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 arrayList = new ArrayList<>();List list = Collections.synchronizedList(arrayList);CopyOnWriteArrayList 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 arrayList = new ArrayList<>();List list = Collections.synchronizedList(arrayList);CopyOnWriteArrayList 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);}
}