五月激情久久_黄色国产_狠狠爱网址_av黄色在线观看_日韩a视频_一级少妇女片

欢迎您访问Java 8并发教程:利用同步关键字、锁和信号量同步访问共享变量!

Java 8并发教程:利用同步关键字、锁和信号量同步访问共享变量

更新时间:2025-05-09 09:13:12作者:佚名

原始文字:Java 8并发教程:同步

翻译:费隆

协议:CC BY-NC-SA 4.0

欢迎来到我的Java8并发教程的第二部分。本指南将教您如何在Java 8中使用简单易懂的代码示例在Java 8中进行编程。这是一系列教程的第二部分。在接下来的15分钟内,您将学习如何通过同步关键字,锁和信号量同步访问共享变量。

本文中显示的中心概念也适用于Java的较旧版本,但是代码示例适用于Java 8permit是什么意思?怎么读,并严重依赖Lambda表达式和新的并发功能。如果您还不熟悉Lambda,我建议您先阅读我的Java 8教程。

为简单起见,本教程的代码示例使用此处定义的两个辅助功能睡眠(秒)和停止(执行程序)。

同步

在上一章中,我们学会了如何通过执行器服务同时执行代码。当我们编写这种多线程代码时,我们需要特别注意同时访问共享变量。假设我们打算增加一个可以通过多个线程同时访问的整数。

我们使用rezement()方法定义计数字段以添加计数:

int count = 0;

void increment() { ? ?count = count + 1; }

当多个线程同时调用此方法时,我们将遇到大麻烦:

ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 10000)
 ? ?.forEach(i -> executor.submit(this::increment));
stop(executor);
System.out.println(count); ?// 9965

我们看不到计数为10000的结果,并且每次执行上述代码的实际结果都不同。原因是我们在不同的线程上共享可变变量,并且没有用于可变访问的同步机制,从而创造了种族条件。

添加一个值需要三个步骤:(1)读取当前值,(2)将此值添加到一个,(3)将新值写入变量。如果两个线程同时执行,则有可能同时执行两个线程,并且将读取相同的当前值。这将导致写作无效,因此实际结果将很小。在上面的示例中,异步对计数的并发访问丢失了35个增量操作,但是在自己执行代码时,您会看到不同的结果。

幸运的是,Java很久以前就支持了与同步关键字的线程同步。在增加计数时,我们可以使用同步固定上述比赛条件。

synchronized void incrementSync() {
 ? ?count = count + 1;
}

当我们同时调用regrementSync()时,我们获得了10000的预期结果。不再次出现比赛条件,并且在每个代码执行中的结果稳定:

ExecutorService executor = Executors.newFixedThreadPool(2);
IntStream.range(0, 10000)
 ? ?.forEach(i -> executor.submit(this::incrementSync));
stop(executor);
System.out.println(count); ?// 10000

同步关键字也可以在语句块中使用:

void incrementSync() {
? ?synchronized (this) { ? ? ? ?count = count + 1; ? ?} }

Java在内部使用所谓的“监视器”(也称为显示器锁或固有锁)来管理同步。监视器绑定到对象,例如,当使用同步方法时,每个方法都会为相应的对象共享同一监视器。

所有隐式监视器都实现了重进入功能。重新进入意味着锁定与当前线绑定。线程可以安全地获取相同的锁多次,而无需创建僵局(例如,同步方法调用同一对象的另一种同步方法)。

并发API支持各种显式锁,由锁定接口指定这些锁以替换同步隐式锁。锁支持多种细粒度控制方法,因此它们比隐式监视器具有更多的开销。

标准JDK中提供了一些锁的实现,并在以下各章中显示。

重新进入

重新输入锁类是一种静音类,其行为与通过同步但功能扩展的隐式监视器相同。就像其名称一样,此锁会像隐式监视器一样实现重新进入功能。

使用Reentrantlock后,让我们看一下上面的示例。

ReentrantLock lock = new ReentrantLock();
int count = 0;

void increment() { ? ?lock.lock();
? ?try { ? ? ? ?count++; ? ?} finally { ? ? ? ?lock.unlock(); ? ?} }

可以通过锁()获得锁,并通过unlock()释放。将您的代码包裹在一个尝试的障碍物中以确保在特殊情况下解锁,这一点非常重要。此方法是线程安全的,就像同步复制品一样。如果另一个线程已经收到锁,则再次调用锁()将阻止当前线程,直到锁定锁定为止。只有一个线程可以在任何给定时间内获取锁。

锁定到颗??丶С侄嘀址椒?,如以下示例:

executor.submit(() -> {
 ? ?lock.lock();
? ?try { ? ? ? ?sleep(1); ? ?} finally { ? ? ? ?lock.unlock(); ? ?} }); executor.submit(() -> { ? ?System.out.println("Locked: " + lock.isLocked()); ? ?System.out.println("Held by me: " + lock.isHeldByCurrentThread());
? ?boolean locked = lock.tryLock(); ? ?System.out.println("Lock acquired: " + locked); }); stop(executor);

第一个任务获得锁后的一秒钟,第二个任务获取了有关锁当前状态的不同信息。

Locked: true
Held by me: false
Lock acquired: false

Trylock()方法是锁定()方法的替代方法,该方法试图在不阻止当前线程的情况下固定锁定。在访问任何共享的可突变变量之前,必须使用布尔结果来检查是否已获取锁定。

ReadWritelock

ReadWritelock接口指定了另一种类型的锁定,包括一对锁定锁,用于读写访问。读写锁的想法是,只要没有线程编写变量,同时读取可变变量通常是安全的。因此,只要没有螺纹固定写锁定,就可以同时由多个线程保存读取锁。这可以改善性能和吞吐量,因为读取比写作更频繁。

ExecutorService executor = Executors.newFixedThreadPool(2);
Map map = new HashMap<>();
ReadWriteLock lock = new ReentrantReadWriteLock();
executor.submit(() -> {
 ? ?lock.writeLock().lock();
? ?try { ? ? ? ?sleep(1); ? ? ? ?map.put("foo", "bar"); ? ?} finally { ? ? ? ?lock.writeLock().unlock(); ? ?} });

暂停一秒钟后,上面的示例首先获取写锁以在地图上添加新值。在完成此任务之前,启动了另外两个任务,试图阅读地图中的元素并暂停一秒钟:

Runnable readTask = () -> {
 ? ?lock.readLock().lock();
? ?try { ? ? ? ?System.out.println(map.get("foo")); ? ? ? ?sleep(1); ? ?} finally { ? ? ? ?lock.readLock().unlock(); ? ?} }; executor.submit(readTask); executor.submit(readTask); stop(executor);

执行此代码示例时permit是什么意思?怎么读,您会注意到两个读取任务需要等待写任务完成。写入锁定后,将同时执行两个读取任务,并同时打印结果。他们不需要等待彼此完成,因为只要没有其他线程获得写锁,就可以同步获得读取锁。

Stampedlock

Java 8带有一个名为Stampedlock的新锁,它也支持读写锁,就像上面的示例一样。与ReadWritelock不同,Stampedlock的锁定方法返回表示为长的标记。您可以使用这些标记释放锁定,或检查锁是否有效。此外,Stampedlock支持另一种称为乐观锁定的模式。

让我们使用Stampedlock而不是ReadWritelock重写上面的示例:

ExecutorService executor = Executors.newFixedThreadPool(2);
Map map = new HashMap<>();
StampedLock lock = new StampedLock();
executor.submit(() -> {
? ?long stamp = lock.writeLock();
? ?try { ? ? ? ?sleep(1); ? ? ? ?map.put("foo", "bar"); ? ?} finally { ? ? ? ?lock.unlockWrite(stamp); ? ?} }); Runnable readTask = () -> {
? ?long stamp = lock.readLock();
? ?try { ? ? ? ?System.out.println(map.get("foo")); ? ? ? ?sleep(1); ? ?} finally { ? ? ? ?lock.unlockRead(stamp); ? ?} }; executor.submit(readTask); executor.submit(readTask); stop(executor);

通过ReadLock()或Writelock()获取读取锁或写锁定的标签,该标签可在以后在最后块中解锁。请记住,Stampedlock不会实现重新进入功能。每个锁定的呼叫都会返回一个新标签,并在没有可用锁定时将其阻止,即使同一线程已经接管了锁。因此,您需要额外的注意不要僵局。

像以前的ReadWritelock示例一样,两个读取任务都需要等待发布写锁。然后,两个读取任务同时将信息打印到控制台网校头条,因为只要没有线程获得写锁,多个读取操作就不会互相阻止。

以下示例显示了乐观的锁:

ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = new StampedLock();
executor.submit(() -> {
? ?long stamp = lock.tryOptimisticRead();
? ?try { ? ? ? ?System.out.println("Optimistic Lock Valid: " + lock.validate(stamp)); ? ? ? ?sleep(1); ? ? ? ?System.out.println("Optimistic Lock Valid: " + lock.validate(stamp)); ? ? ? ?sleep(2); ? ? ? ?System.out.println("Optimistic Lock Valid: " + lock.validate(stamp)); ? ?} finally { ? ? ? ?lock.unlock(stamp); ? ?} }); executor.submit(() -> {
? ?long stamp = lock.writeLock();
? ?try { ? ? ? ?System.out.println("Write Lock acquired"); ? ? ? ?sleep(2); ? ?} finally { ? ? ? ?lock.unlock(stamp); ? ? ? ?System.out.println("Write done"); ? ?} }); stop(executor);

通过调用TryOptimisticRead()获得乐观的读取锁,该锁总是在不阻止当前线程的情况下返回标签,而不管锁定是否实际可用。如果已收到写锁,则返回的标记等于0。您需要始终检查标记是否有效。

执行上述代码将产生以下输出:

Optimistic Lock Valid: true
Write Lock acquired
Optimistic Lock Valid: false
Write done
Optimistic Lock Valid: false

乐观的锁仅在获得锁后是有效的。与普通读取锁不同,乐观的锁不会阻止其他线程同时获得写锁。在第一个线程暂停一秒钟后,第二个线程在不等待释放乐观的读锁的情况下获取写锁。目前,乐观的读锁不再有效。即使释放写锁,乐观的读取锁仍然处于无效状态。

因此,使用乐观的锁时,您需要在访问任何共享变量后每次检查锁定,以确保读取锁定仍然有效。

有时,将读取锁转换为写入锁定是非常实用的,而无需重新解锁和锁定。为此目的,StampedLock提供了TryConvertTowritelock()方法,如以下目的:

ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = new StampedLock();
executor.submit(() -> {
? ?long stamp = lock.readLock();
? ?try {
? ? ? ?if (count == 0) { ? ? ? ? ? ?stamp = lock.tryConvertToWriteLock(stamp);
? ? ? ? ? ?if (stamp == 0L) { ? ? ? ? ? ? ? ?System.out.println("Could not convert to write lock"); ? ? ? ? ? ? ? ?stamp = lock.writeLock(); ? ? ? ? ? ?} ? ? ? ? ? ?count = 23; ? ? ? ?} ? ? ? ?System.out.println(count); ? ?} finally { ? ? ? ?lock.unlock(stamp); ? ?} }); stop(executor);

第一个任务将获取读取锁,并将计数字段的当前值打印到控制台。但是,如果当前值为零,我们希望将其分配给23。我们首先需要将读取锁转换为写入锁定,以避免从其他线程中破坏潜在的并发访问。对TryConvertTowriteLock()的调用不会阻止,但可能会返回零标记,表明当前没有写锁。在这种情况下,我们调用Writelock()阻止当前线程,直到有一个可用的写锁。

信号

除锁外,并发API还支持计数信号量。但是,锁通常用于互斥变量或资源的互斥访问,信号量可以维护整体访问权限。这在某些不同的情况下非常有用,例如,当您需要限制程序的一部分并发访问总数时。

这是一个示例,演示了如何限制通过睡眠模拟的长期运行任务的访问(5):

ExecutorService executor = Executors.newFixedThreadPool(10);
Semaphore semaphore = new Semaphore(5);
Runnable longRunningTask = () -> {
? ?boolean permit = false;
? ?try { ? ? ? ?permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
? ? ? ?if (permit) { ? ? ? ? ? ?System.out.println("Semaphore acquired"); ? ? ? ? ? ?sleep(5); ? ? ? ?} else { ? ? ? ? ? ?System.out.println("Could not acquire semaphore"); ? ? ? ?} ? ?} catch (InterruptedException e) {
? ? ? ?throw new IllegalStateException(e); ? ?} finally {
? ? ? ?if (permit) { ? ? ? ? ? ?semaphore.release(); ? ? ? ?} ? ?} } IntStream.range(0, 10) ? ?.forEach(i -> executor.submit(longRunningTask)); stop(executor);

执行人可以同时运行10个任务,但是我们使用Size 5的信号量,因此我们将同时访问5限制为5。在特殊情况下,使用try-Finally代码块合理地释放信号量很重要。

执行上述代码会产生以下结果:

Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore

信号量限制了对长达5个线程模拟(5)模拟的长期运行任务的访问。随后的每个TryAcquire()调用将打印结果,该结果无法在等待一秒钟的等待时间之后获得控制台的信号量。

这是我系列并发教程的第二部分。将来会发布更多零件,因此请等待。和以前一样,您可以在GitHub上找到此文档的所有示例代码,因此请随时订购此存储库并自己尝试。

为您推荐

英语词汇量对成绩影响大,掌握pictures含义及用法很关键

pictures英语怎么读今天我们要掌握的英语单词就是pictures。上面为同学们解释了pictures英语怎么读,并且整理了pictures的相关知识点,希望对同学们有帮助。

2025-05-09 09:53

英语学习超有趣!掌握特定词汇用法及语言现象很关键

如果你把它翻译成“越过山丘”,从本义出发是没有错的,如果你要穿过某座山,是可以这么说的。river.(我没有沿着河走,而是翻过了小山)。既然本义是翻过山丘,那么比喻一个人的巅峰状态或者事业翻越过顶点,开始走下坡路还是有点道理的,我们汉语不是有“物极必反”的说法吗?

2025-05-07 21:33

thankfully什么意思_thankfully怎么读_thankfully翻译_thankfully用法_thankfully词组_同反义词

thankfully的基本释义为 基本解释 adv. (用以表示高兴或宽慰)幸亏;高兴地,感激地等等。贝语网校(www.nenqi.cn)为您提供thankfully发音,英语单词thankfully的音标,thankfully中文意思,thankfully的过去式,thankfully双语例句等相关英语知识。

2025-05-07 12:40

thankful什么意思_thankful怎么读_thankful翻译_thankful用法_thankful词组_同反义词

thankful的基本释义为 基本解释 adj. 感谢的,感激的;欣慰的等等。贝语网校(www.nenqi.cn)为您提供thankful发音,英语单词thankful的音标,thankful中文意思,thankful的过去式,thankful双语例句等相关英语知识。

2025-05-07 12:40

thais什么意思_thais怎么读_thais翻译_thais用法_thais词组_同反义词

thais的基本释义为 基本解释 n. 泰国人,泰国语( Thai的名词复数 );[电影]泰爱斯等等。贝语网校(www.nenqi.cn)为您提供thais发音,英语单词thais的音标,thais中文意思,thais的过去式,thais双语例句等相关英语知识。

2025-05-07 12:39

textbooks什么意思_textbooks怎么读_textbooks翻译_textbooks用法_textbooks词组_同反义词

textbooks的基本释义为 基本解释 n. 教科书,课本( textbook的名词复数 )等等。贝语网校(www.nenqi.cn)为您提供textbooks发音,英语单词textbooks的音标,textbooks中文意思,textbooks的过去式,textbooks双语例句等相关英语知识。

2025-05-07 12:38

加载中...
主站蜘蛛池模板: 91视频免费在线看 | 国产欧美日韩三区 | 一级作爱视频免费观看 | 久久福利在线 | 久久久久国产成人免费精品免费 | 国产一区二区三区高清视频 | 亚洲精品视频在线观看视频 | 在线观看麻豆 | 国产精品夜夜春夜夜爽久久小 | 久草色香蕉 | 国产成人精品一区二 | 91亚洲精品久久 | 久久123区| 91成人短视频在线观看 | 91视频分类| 亚洲第一天堂久久 | 91免费影片 | 午夜手机在线视频 | 偷拍成人一区亚洲欧美 | 免费人成网 | 91精品在线观看视频 | 公和我在厨房添好爽了在线观看 | 久一精品| 国产 日韩 欧美 精品 | 91免费视频观看 | 日本成做爰免费中文字幕 | 国产精品乱码久久久久久久 | 亚洲天堂91 | 91视频免费版污 | 亚洲国产成人久久综合一区,久久久国产99 | 99只有精品 | 国产高清在线观看 | 国产一区二区三区成人 | 99久久久久久国产精品 | 91亚洲精选 | 91视频社区 | 夜夜操操操 | 91av电影在线观看 | 干片网| 亚洲撸| 91免费版网站 |