java 并发锁_Java并发教程–锁定:内在锁

java 并发锁

在之前的文章中,我们回顾了在不同线程之间共享数据的一些主要风险(例如原子性和可见性 )以及如何设计类以安全地共享( 线程安全的设计 )。 但是,在许多情况下,我们将需要共享可变数据,其中一些线程将写入而其他线程将充当读取器。 可能的情况是,只有一个域,与其他域无关,需要在不同线程之间共享。 在这种情况下,您可以使用原子变量。 对于更复杂的情况,您将需要同步。



1.咖啡店的例子

让我们从一个简单的示例开始,例如CoffeeStore 。 此类开设了一家商店,客户可以在此购买咖啡。 客户购买咖啡时,会增加一个计数器,以便跟踪所售商品的数量。 商店还注册谁是最后一个来商店的客户。

public class CoffeeStore {private String lastClient;private int soldCoffees;private void someLongRunningProcess() throws InterruptedException {Thread.sleep(3000);}public void buyCoffee(String client) throws InterruptedException {someLongRunningProcess();lastClient = client;soldCoffees++;System.out.println(client + " bought some coffee");}public int countSoldCoffees() {return soldCoffees;}public String getLastClient() {return lastClient;}
}

在以下程序中,四个客户决定来商店购买咖啡:

public static void main(String[] args) throws InterruptedException {CoffeeStore store = new CoffeeStore();Thread t1 = new Thread(new Client(store, "Mike"));Thread t2 = new Thread(new Client(store, "John"));Thread t3 = new Thread(new Client(store, "Anna"));Thread t4 = new Thread(new Client(store, "Steve"));long startTime = System.currentTimeMillis();t1.start();t2.start();t3.start();t4.start();t1.join();t2.join();t3.join();t4.join();long totalTime = System.currentTimeMillis() - startTime;System.out.println("Sold coffee: " + store.countSoldCoffees());System.out.println("Last client: " + store.getLastClient());System.out.println("Total time: " + totalTime + " ms");
}private static class Client implements Runnable {private final String name;private final CoffeeStore store;public Client(CoffeeStore store, String name) {this.store = store;this.name = name;}@Overridepublic void run() {try {store.buyCoffee(name);} catch (InterruptedException e) {System.out.println("interrupted sale");}}
}

主线程将使用Thread.join()等待所有四个客户端线程完成。 一旦客户离开,我们显然应该算出我们商店中售出的四种咖啡,但是您可能会得到意想不到的结果,如上面的一种:

Mike bought some coffee
Steve bought some coffee
Anna bought some coffee
John bought some coffee
Sold coffee: 3
Last client: Anna
Total time: 3001 ms

我们丢了一杯咖啡,最后一个客户(John)也不是那个(Anna)。 原因是由于我们的代码未同步,因此线程交错。 我们的buyCoffee操作应该原子化。

2.同步如何工作

同步块是由锁保护的代码区域。 当线程进入同步块时,它需要获取其锁,并且一旦获取,它就不会释放它,直到退出该块或引发异常。 这样,当另一个线程尝试进入同步块时,只有所有者线程释放它后,它才能获取其锁。 这是Java机制,可确保仅在给定时间在线程上执行同步的代码块,从而确保该块内所有动作的原子性。

好的,所以您使用锁来保护同步块,但是什么是锁? 答案是任何Java对象都可以用作锁,称为内在锁。 现在,我们将看到使用同步时这些锁的一些示例。

3.同步方法

同步方法由两种类型的锁保护:

  • 同步实例方法 :隐式锁定为“ this”,这是用于调用该方法的对象。 此类的每个实例将使用自己的锁。
  • 同步静态方法 :锁是Class对象。 此类的所有实例将使用相同的锁。

和往常一样,用一些代码可以更好地看到这一点。

首先,我们将同步一个实例方法。 它的工作方式如下:我们有一个类的实例由两个线程(线程1和线程2)共享,另一个实例由第三个线程(线程3)使用:

public class InstanceMethodExample {private static long startTime;public void start() throws InterruptedException {doSomeTask();}public synchronized void doSomeTask() throws InterruptedException {long currentTime = System.currentTimeMillis() - startTime;System.out.println(Thread.currentThread().getName() + " | Entering method. Current Time: " + currentTime + " ms");Thread.sleep(3000);System.out.println(Thread.currentThread().getName() + " | Exiting method");}public static void main(String[] args) {InstanceMethodExample instance1 = new InstanceMethodExample();Thread t1 = new Thread(new Worker(instance1), "Thread-1");Thread t2 = new Thread(new Worker(instance1), "Thread-2");Thread t3 = new Thread(new Worker(new InstanceMethodExample()), "Thread-3");startTime = System.currentTimeMillis();t1.start();t2.start();t3.start();}private static class Worker implements Runnable {private final InstanceMethodExample instance;public Worker(InstanceMethodExample instance) {this.instance = instance;}@Overridepublic void run() {try {instance.start();} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + " interrupted");}}}
}

由于doSomeTask方法是同步的,因此您希望在给定的时间只有一个线程将执行其代码。 但这是错误的,因为它是一个实例方法。 不同的实例将使用不同的锁,如输出所示:

Thread-1 | Entering method. Current Time: 0 ms
Thread-3 | Entering method. Current Time: 1 ms
Thread-3 | Exiting method
Thread-1 | Exiting method
Thread-2 | Entering method. Current Time: 3001 ms
Thread-2 | Exiting method

由于线程1和线程3使用不同的实例(因此使用了不同的锁),因此它们都同时进入该块。 另一方面,线程2使用与线程1相同的实例(和锁)。 因此,它必须等到线程1释放锁。

现在,让我们更改方法签名并使用静态方法。 除以下行外, StaticMethodExample具有相同的代码:

public static synchronized void doSomeTask() throws InterruptedException {

如果执行main方法,将得到以下输出:

Thread-1 | Entering method. Current Time: 0 ms
Thread-1 | Exiting method
Thread-3 | Entering method. Current Time: 3001 ms
Thread-3 | Exiting method
Thread-2 | Entering method. Current Time: 6001 ms
Thread-2 | Exiting method

由于同步方法是静态的,因此它由Class对象锁保护。 尽管使用了不同的实例,所有线程仍需要获取相同的锁。 因此,任何线程都必须等待上一个线程释放锁。

4.回到咖啡店的例子

我现在修改了Coffee Store示例以使其方法同步。 结果如下:

public class SynchronizedCoffeeStore {private String lastClient;private int soldCoffees;private void someLongRunningProcess() throws InterruptedException {Thread.sleep(3000);}public synchronized void buyCoffee(String client) throws InterruptedException {someLongRunningProcess();lastClient = client;soldCoffees++;System.out.println(client + " bought some coffee");}public synchronized int countSoldCoffees() {return soldCoffees;}public synchronized String getLastClient() {return lastClient;}
}

现在,如果我们执行该程序,我们将不会失去任何销售:

Mike bought some coffee
Steve bought some coffee
Anna bought some coffee
John bought some coffee
Sold coffee: 4
Last client: John
Total time: 12005 ms

完善! 好吧,真的是吗? 现在程序的执行时间为12秒。 您肯定已经注意到在每次销售期间都会执行someLongRunningProcess方法。 它可以是与销售无关的操作,但是由于我们同步了整个方法,所以现在每个线程都必须等待它执行。 我们可以将这段代码放在同步块之外吗? 当然! 下一节将介绍同步块。

5.同步块

上一节向我们展示了我们可能并不总是需要同步整个方法。 由于所有同步代码都强制对所有线程执行进行序列化,因此我们应最小化同步块的长度。 在我们的咖啡店示例中,我们可以省去长时间运行的过程。 在本节的示例中,我们将使用同步块:

在SynchronizedBlockCoffeeStore中 ,我们修改buyCoffee方法,以将长时间运行的进程排除在同步块之外:

public void buyCoffee(String client) throws InterruptedException {someLongRunningProcess();synchronized(this) {lastClient = client;soldCoffees++;System.out.println(client + " bought some coffee");}
}public synchronized int countSoldCoffees() {return soldCoffees;}public synchronized String getLastClient() {return lastClient;}

在上一个同步块中,我们将“ this”用作其锁。 它与同步实例方法中的锁相同。 当心使用另一个锁,因为我们正在此类的其他方法( countSoldCoffeesgetLastClient )中使用此锁。

让我们看看执行修改后的程序的结果:

Mike bought some coffee
John bought some coffee
Anna bought some coffee
Steve bought some coffee
Sold coffee: 4
Last client: Steve
Total time: 3015 ms

在保持代码同步的同时,我们大大减少了程序的时间。

6.使用私人锁

上一节对实例对象使用了锁定,但是您可以将任何对象用作其锁定。 在本节中,我们将使用私人锁,看看使用私人锁会有什么风险。

在PrivateLockExample中 ,我们有一个由私有锁(myLock)保护的同步块:

public class PrivateLockExample {private Object myLock = new Object();public void executeTask() throws InterruptedException {synchronized(myLock) {System.out.println("executeTask - Entering...");Thread.sleep(3000);System.out.println("executeTask - Exiting...");}}
}

如果一个线程进入executeTask方法将获取myLock锁。 在由相同的myLock锁保护的此类中进入其他方法的任何其他线程,都必须等待才能获取它。

但是,现在让我们想象一下,有人想要扩展此类以添加自己的方法,并且由于需要使用相同的共享数据,因此这些方法也需要同步。 由于该锁在基类中是私有的,因此扩展类将无法访问它。 如果扩展类同步其方法,则将通过“ this”进行保护。 换句话说,它将使用另一个锁。

MyPrivateLockExample扩展了先前的类,并添加了自己的同步方法executeAnotherTask

public class MyPrivateLockExample extends PrivateLockExample {public synchronized void executeAnotherTask() throws InterruptedException {System.out.println("executeAnotherTask - Entering...");Thread.sleep(3000);System.out.println("executeAnotherTask - Exiting...");}public static void main(String[] args) {MyPrivateLockExample privateLock = new MyPrivateLockExample();Thread t1 = new Thread(new Worker1(privateLock));Thread t2 = new Thread(new Worker2(privateLock));t1.start();t2.start();}private static class Worker1 implements Runnable {private final MyPrivateLockExample privateLock;public Worker1(MyPrivateLockExample privateLock) {this.privateLock = privateLock;}@Overridepublic void run() {try {privateLock.executeTask();} catch (InterruptedException e) {e.printStackTrace();}}}private static class Worker2 implements Runnable {private final MyPrivateLockExample privateLock;public Worker2(MyPrivateLockExample privateLock) {this.privateLock = privateLock;}@Overridepublic void run() {try {privateLock.executeAnotherTask();} catch (InterruptedException e) {e.printStackTrace();}}}
}

该程序使用两个工作线程,分别执行executeTaskexecuteAnotherTask 。 输出显示线程如何交错,因为它们没有使用相同的锁:

executeTask - Entering...
executeAnotherTask - Entering...
executeAnotherTask - Exiting...
executeTask - Exiting...

7.结论

我们已经使用Java的内置锁定机制回顾了内部锁定的使用。 这里的主要关注点是需要使用共享数据的同步块。 必须使用相同的锁。

这篇文章是Java Concurrency Tutorial系列的一部分。 单击此处阅读本教程的其余部分。

  • 您可以在Github上找到源代码。

翻译自: https://www.javacodegeeks.com/2014/09/java-concurrency-tutorial-locking-intrinsic-locks.html

java 并发锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/339653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux 命令 ppt,Linux基本命令()讲解.ppt

第2章 Linux 基本命令 2.1 系统管理命令 在 Linux/UNIX 操作系统中,所有事物都被当作文件来处理:硬件设备(包括键盘和终端)、目录、命令本身,当然还有文件。 实际上是 Linux/UNIX 的能力和灵活性的基础。Linux操作系统命令分为文件管理、文件…

Java14:使用Java 14的新记录联接数据库表

您是否知道可以使用Java 14的预览记录功能将数据库表连接到Java Stream中? 阅读这篇简短的文章,并了解如何使用Speedment Stream ORM完成它。 我们将从如何设置您的项目开始。 设定 下载Java 14 。 转到Speedment Initializer并下载您的项目骨架&#x…

linux 读取内存颗粒,Linux虚拟内存地址转化成物理内存地址

背景现代手机这种SOC(system on chip),因为功耗、Modem等功能soc上集成了很多core,他们还可以是独立的系统在运转。比如ADSP简介ADSP(Application Digital Signal Processing)就是高通的Hexagon DSP ,就是独立运转的一个coresystem。这样做不仅可以使用soc上的专用核…

primefaces_PrimeFaces扩展中的全新JSF组件

primefacesPrimeFaces扩展团队很高兴宣布即将推出的3.0.0主要版本的几个新组件。 我们的新提交者Francesco Strazzullo为该项目提供了“ Turbo Boost”,并带来了至少6个已成功集成的 JSF组件! 当前的开发状态是OpenShift上的deployet – 请查看展示柜。以…

linux数字设定法设定权限,Linux chmod命令详解和使用实例(改变文件或目录的访问权限)...

Linux系统中的每个文件和目录都有访问许可权限,用它来确定谁可以通过何种方式对文件和目录进行访问和操作。文件或目录的访问权限分为只读,只写和可执行三种。以文件为例,只读权限表示只允许读其内容,而禁止对其做任何的更改操作。…

Java 8 Stream中间操作(方法)示例

Java 8 Streams中间操作的完整指南。 所有内置Stream API中间操作(方法)的列表以及示例。 1.概述 在本教程中,我们将学习什么是 Java 8 Stream 中的中间操作 。 所有这些操作都在java.util.stream.Stream包中 。 在上一教程中,我…

linux服务 运维案例,linux运维实战练习案例-2015年12月20日-12月31日

1、创建一个10G的文件系统,类型为ext4,要求开机可自动挂载至单独数据/data目录;[[email protected] /]# cat /proc/partitionsmajor minor #blocks name8 0 52428800 sda8 1 204800 sda18 2 4096000 sda28 …

使用Quarkus调试容器中的系统测试(视频)

如果您能够借助容器在本地进行端到端测试应用程序,则可以提高开发效率。 在下面的视频中,我将展示如何使用Quarkus在Docker容器中调试本地系统测试。 这是我关于有效测试的视频课程的Quarkus扩展。 要了解全部情况,还可以查看以下资源&…

linux中memcpy实现分析,ARM64 的 memcpy 优化与实现

如何优化 memcpy 函数Linux 内核用到了许多方式来加强性能以及稳定性,本文探讨的 memcpy 的汇编实现方式就是其中的一种,memcpy 的性能是否强大,拷贝延迟是否足够低都直接影响着整个系统性能。通过对拷贝函数的理解可以加深对整个系统设计的一…

ejb生命周期_EJB 3.x:生命周期和并发模型(第2部分)

ejb生命周期这是两部分系列的第二篇。 第一部分介绍了有状态和无状态EJB的生命周期以及并发行为。 我将在本文中介绍Singleton EJB 。 Singleton模式可以说是最常用(有时被滥用!)的模式。 单吨又爱它! Java EE使我们无需编写显…

linux修改文件没有备份文件,linux文件或目录权限修改后如何恢复(备份了权限就能恢复)...

操作系统 RHEL5如果你在linux上执行了如下操作chmod -R 777 / 或者 chmod -R 700 /那么恭喜你,你的系统即将崩溃,重启之后,你进不了图形界面,而且很多服务都起不来为什么呢?因为linux中,系统的有些文件和目…

JDK 14 / JEP 305模式匹配“ Smart Casts”实例

我通常将Java代码中instanceof运算符的存在视为“ 红色标志 ”,这意味着在某些情况下使用instanceof不一定是错误的,但是使用它有时表示可以以一种更干净的方式解决设计问题,如所述本文末尾引用的一些资源中的内容(包括有关Java以…

linux美化原理,x-window字体原理及美化

x-window字体原理及美化发布时间:2006-10-07 01:25:15来源:红联作者:caldo1. 简介在我必须处理的一堆讨厌事中,有一项就是没完没了的 X 缺省字体和字体设定 (我专指 XFree86,其它的 X 也许比较好)。有些程序缺省使用固定宽度字体 (fixed width fonts)&am…

易流即时配送_即时大数据流处理=即时风暴

易流即时配送在Ubuntu背后的公司Canonical,每6个月进行一次技术工作,以第一手测试我们的工具并向其他人展示新想法。 这次,我创建了一个即时大数据解决方案,更具体地讲是“即时风暴”。 Storm现在是Apache基金会的一部分&#xf…

c语言有参有类最小公倍数,【C语言】写一个函数,并调用该函数求两个整数的最大公约数和最小公倍数...

程序分析:在数学中,两个数的最小公倍数两个数的乘积/两数的最大公约数。求两个数的最大公约数,运用辗转相除法:已知两个整数M和N,假定M>N,则求M%N。如果余数为0,则N即为所求;如果…

csp真题字符串匹配c语言,CCF CSP认证考试历年真题 模板生成系统 C语言实现

试题编号:201509-3试题名称:日期计算 时间限制:1.0s 内存限制:256.0MB问题描述:成成最近在搭建一个网站,其中一些页面的部分内容来自数据库中不同的数据记录,但是页面的基本结构是相同的。例如&…

osgi架构与linux_OSGi:进入微服务架构的门户

osgi架构与linux在构建可扩展,可靠的分布式系统的背景下,“模块化”和“微服务体系结构”这两个术语如今经常出现。 众所周知,Java平台本身在模块化方面很弱( Java 9将通过交付Jigsaw项目来解决此问题),这为…

引入我们全新的YouTube频道进行视频课程编程

嘿,极客们, 收到社区的反馈并紧贴行业发展趋势,我们非常高兴宣布推出全新的Youtube频道 ! 在我们的频道上,我们将主持与Java编程有关的视频课程,但通常也会进行软件开发。 我们将介绍代码演练以及完整的…

田忌赛马c语言程序设计,还是杭电1052田忌赛马

已结贴√问题点数:20 回复次数:2还是杭电1052田忌赛马//昨天那个算法漏洞挺大,但我重新构思了,但运行到312ms还是wa了。我测试了许多数据,结果是对的,郁闷了,谁能救救我啊?#include&…

ArrayList clone()– ArrayList深拷贝和浅拷贝

示例程序以ArrayList克隆方法为例。 学生对象上的ArrayList深层复制和浅层复制示例。 1.简介 ArrayList clone()– ArrayList深复制和浅复制 。 ArrayList clone()方法用于创建list的浅表副本 。 在新列表中,仅复制对…