一.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() {
@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("任务全部执行结束了......");
}
}
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);
}
}
我们如何解决集合类中的线程安全问题呢?
- 手动加锁,使用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("-----------------------");
}
}
- 使用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<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);
}
}