欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > JAVA EE(10)——线程安全——synchronized JUC(java.util.concurrent) 的常见类 线程安全的集合类

JAVA EE(10)——线程安全——synchronized JUC(java.util.concurrent) 的常见类 线程安全的集合类

2025/3/16 16:22:43 来源:https://blog.csdn.net/2401_89167985/article/details/146267990  浏览:    关键词:JAVA EE(10)——线程安全——synchronized JUC(java.util.concurrent) 的常见类 线程安全的集合类

一.synchronized锁升级

上篇博文介绍了各种锁策略,那么在此基础上我再对常用的synchronized的优化策略进行讲解

synchronized (锁对象) {//其他代码
}

当我们使用synchronized对某一代码块加锁的时候,synchronized并不会在第一时间加锁,而是经历了(偏向锁——>轻量级锁——>重量级锁)这样的锁升级过程。
偏向锁
当一个线程第一次访问同步块时,JVM会尝试将该线程的ID记录在锁对象的对象头中,并标记为偏向锁。之后,当该线程再次进入同步块时,直接进入同步块
在这里插入图片描述
偏向锁——>轻量级锁
如果有其他线程尝试获取该锁,偏向锁会被撤销,升级为轻量级锁
在这里插入图片描述
轻量级锁——>重量级锁
当thread1等待了很久(自旋了很多次)lock依然没有释放,或者此时又来了很多线程。JVM看到这里的竞争太大,会考虑把lock升级为重量级锁

二.JUC(java.util.concurrent) 的常见类

2.1ReentrantLock

可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果,保证线程安全。ReentrantLock需要手动上锁和解锁,而synchronized是自动加锁和解锁。
(1)更加灵活

//拿不到锁就死等
public void lock() {sync.lock();
}
//尝试获取锁,获取失败后立即返回false,不会阻塞当前线程
public boolean tryLock() {return sync.nonfairTryAcquire(1);
}
//尝试在指定的时间内获取锁,如果在超时之前成功获取到锁,则返回true,否则返回false
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

因为需要手动释放锁,为了避免忘记释放锁,一般搭配finally使用

ReentrantLock lock = new ReentrantLock(); 
-----------------------------------------
lock.lock();   
try {    //其他代码
} finally {    lock.unlock()    
}  

(2)可实现公平锁

//通过构造方法可以选择实例化非公平锁还是公平锁
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

(3)更强大的唤醒机制
synchronized是通过Object的wait/notify实现等待-唤醒,每次唤醒的是一个随机等待的线程ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程

2.2原子类

原子类内部用的是CAS实现,所以性能要比加锁实现 i++高很多,原子类有以下几个

AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicLong
AtomicReference
AtomicStampedReference

以AtomicInteger为例,常用方法有:
addAndGet(int delta); i += delta;
decrementAndGet(); --i;
getAndDecrement(); i–;
incrementAndGet(); ++i;
getAndIncrement(); i++;

2.3 Semaphore

信号量,用来表示 “可用资源的个数”,本质上就是一个计数器
可以用它来代替阻塞队列,来实现生产者——消费者模式

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Semaphore;class producer_consumer_semaphore_Test{private static final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1000);private static final int capacity = 5;//未占用资源5个private static final Semaphore empty = new Semaphore(capacity);//已占用资源0个private static final Semaphore fill = new Semaphore(0);//实现互斥锁,非0即1private static final Semaphore mutex = new Semaphore(1);private static class producer implements Runnable {@Overridepublic void run() {int n = 0;try {while (true) {System.out.println("producer:" + ++n);empty.acquire();mutex.acquire();queue.add(n);mutex.release();fill.release();Thread.sleep(1000);}}catch (InterruptedException e){throw new RuntimeException(e);}}}private static class consumer implements Runnable{@Overridepublic void run() {try {while (true) {fill.acquire();mutex.acquire();System.out.println("consumer:" + queue.take());mutex.release();empty.release();Thread.sleep(1000);}}catch (InterruptedException e){throw new RuntimeException(e);}}}public void start(){Thread ThreadProducer = new Thread(new producer());Thread ThreadConsumer = new Thread(new consumer());ThreadProducer.start();ThreadConsumer.start();}
}public class Producer_Consumer_Semaphore {public static void main(String[] args) {producer_consumer_semaphore_Test producerConsumerSemaphore = new producer_consumer_semaphore_Test();producerConsumerSemaphore.start();}
}

2.4 CountDownLatch

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

import java.util.Random;
import java.util.concurrent.CountDownLatch;public class Main {public static void main(String[] args) throws InterruptedException {int count = 10;//一共十个任务CountDownLatch latch = new CountDownLatch(count);for (int i = 1; i <= count; i++) {int n = i;Thread thread = new Thread(()->{Random random = new Random();int time = (random.nextInt(5) + 1) * 1000;System.out.println("线程:" + n + "开始执行");System.out.println("线程:" + n + "执行完毕");try {Thread.sleep(time);} catch (InterruptedException e) {throw new RuntimeException(e);}//相当于count--latch.countDown();});thread.start();}//等待count减为0//await方法无参数就是死等//有参数可以传入时间latch.await();System.out.println("所有线程执行完毕");}
}

三.线程安全的集合类

我们学过的大多数集合类时=是线程不安全的,线程安全的只有Stack,Vector,Hashtable这三个。但是这三个缺点很明显,一旦实例出对象,无论在单线程还是多线程环境下,所进行的操作都需要加锁和解锁,这就严重降低了性能。那么,如何保证线程安全的同时又保证性能不受到严重影响,我总结出以下几点。

3.1多线程环境使用ArrayList

(1)使用ArrayList进行增删查改的时候手动加锁(synchronized),这个方式很好理解,这里就不过多赘述。

synchronized(锁对象){arrayList.add(元素)
}

(2)Collections.synchronizedList(new ArrayList)
该方法可以将List接口的实现类从线程不安全转换为线程安全,例如下面的代码:

public class Main {public static void main(String[] args) {//实例化普通的顺序表List<Integer> array = new ArrayList<>();//将array转换为线程安全的safeArrayList<Integer> safeArray = Collections.synchronizedList(array);}
}

因为读写操作都进行了加锁,所以适用于读和写操作频率相对均衡的场景
(3)CopyOnWriteArrayList
CopyOnWrite的意思是:写时拷贝
学到现在,我们知道触发线程安全问题需要多线程对同一个共享变量进行"写"操作,而并发"读"操作是线程安全的
通过CopyOnWriteArrayList实例化出来的对象在进行读操作时不会加锁,当进行增删查改等写操作时会在原数组基础上拷贝一个副本,在副本上进行写操作,执行完毕后修改引用指向副本
在这里插入图片描述
读操作没有额外开销,但频繁地写操作会频繁地拷贝数组,不加锁的本意是节省加锁和解锁的开销,但如果拷贝的开销都大于加锁解锁那就得不偿失了,所以CopyOnWriteArrayList适用于读多写少的环境

3.2多线程环境使用队列

1.使用队列进行增删查改的时候手动加锁(synchronized)
2.BlockingQueue阻塞队列

3.3多线程环境使用哈希表

HashMap线程不安全,Hashtable性能不行,Java标准库就引入ConcurrentHashMap,下面来讲讲ConcurrentHashMap到底做出了哪些优化
(1)缩小锁的粒度
在这里插入图片描述
基于以上的问题,ConcurrentHashMap缩小了锁的粒度
在这里插入图片描述
(2)合理使用CAS
哈希表的增加/删除元素的操作会让useSize++/–,这个自增/自减操作就可以使用CAS来代替
(3)针对扩容进行优化
当负载因子>0.75(默认情况下)时,会把哈希表扩容为原来的2倍,这涉及到两个操作。第一步,创建一个2倍大的哈希表;第二部,将原哈希表上面的元素重新哈希到新的表上,当元素过多时,这一步会非常耗时。
因为哈希表的增删查改操作的时间复杂度理论上是O(1),这样的速度是十分快的,假如此时一共有一千万个元素,如果插入某一元素时触发扩容操作,那么这一次插入操作就非常耗时。站在用户层面,大部分时间运行流程,但时不时就非常卡顿,这不利于用户的体验。

所以,针对扩容操作,ConcurrentHashMap采用的是分布扩容的方式。(这里的数据都是假设而已,理解思想即可)
例如,要对拥有一千万元素的哈希表进行扩容,每一次增删查改操作都只重新哈希一万个元素,进行一千次操作就扩容完毕了。

那么这会不会导致哈希表一直在扩容的路上?
答案是:不会,因为分布扩容总共进行一千次就完毕了,而触发一次扩容操作则需要百万数量级的数据,这显然是低频的。
当进行分布扩容时,此时存在旧/新两张哈希表,那么这个时候的增删查改操作就怎么进行的?
增加:直接添加到新表
删除:将旧/新表的目标元素都删除
修改:通常发生在旧表
查询:旧/新表的进行查询

四.小结

线程安全问题介绍到这里就差不多了,从下节开始进入文件IO的学习

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词