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

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

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

相关文章

小程序在wxml页面中取整

小程序无法像html中,在页面中直接parseInt() index.wxml {{price | Int}} 小程序还有另一种处理方法 wxs 是一种类似于js脚本的东西 filters.wxs var filters {toFix: function (value) {return parseFloat(value)} } module.exports {toFix: filters.toFix } …

2019.7.16考试总结

对于这个狗屎成绩我不想说什么,,,,,前两次考炸也就算了,主要因为不会,这次考成这狗屎,是因为手残眼瘸大脑间歇性抽搐 T1:我是菜鸡,我是蒟蒻,我好菜…

PrimeFaces Extensions中的全新JSF组件

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

Application Verifier

老徐 says Application Verifier can help to check the memory leak issue of the notepad app.转载于:https://www.cnblogs.com/backpacker/archive/2011/11/16/2250648.html

15 个最新的 CSS3 教程

1. 创建一个漂亮的图标 这个教程将教你如何用纯CSS3创建一个图中的图标2. CSS3 图片样式 这个教程将教你如何使用 box-shadow, border-radius和transition。3. CSS3 Transition 的模糊效果4. 实用的CSS3圆角表格5. 创建纯CSS3的票式标签6. 原始的鼠标浮动效果 这个教程将创建缩…

运行时类加载以支持不断变化的API

我维护一个IntelliJ插件 ,可以改善编写Spock规范的体验。 这个项目的挑战是在单个代码库中支持多个不兼容的IntelliJ API版本。 回想起来,该解决方案很简单(这是狂野的适配器模式的一个示例),但最初它需要一些思想和示…

急救: Autodesk MapGuide Studio - Preview在MapGuide Open Source环境不能进行中文标注

MapGuide环境: 从官方mapguide.osgeo.org下载的最新版Mapguide Open Source1.1 和 MapGuide Open Source Web Server Extension开发环境: vs2005 .net2问题详述:对于图层Layer1. 选中后实体可以查询中文属性信息。具体设置在Properties displayed in Viewer&#xf…

解决新版本webpack vue-cli生成文件没有dev.server.js问题

新版本webpack生成的dev.server.js 在webpack.dev.conf.js中webpack.dev.conf.jsconst axios require(axios) const express require(express) const app express() const apiRoutes express.Router() app.use(/api, apiRoutes)然后找到devserver 这里可以配置路由devServe…

C++内存管理——指针数组

C/C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。但二者有着本质的区别:数组:要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是…

ArcGIS中的WKID(转)

ArcGIS中的WKID link: https://www.cnblogs.com/liweis/p/5951032.html 提到坐标系统,大家多少能明白一些,但在运用时,有些朋友搞得不是非常清楚,以后专门来总结。在实地生产项目中,使用较多的2000中国大地坐标系&…

Nashorn如何在新层面上影响API的发展

在上一篇关于如何将jOOQ与Java 8和Nashorn结合使用的文章之后,我们的一位用户发现了使用jOOQ API的缺陷, 如用户组中所述 。 本质上,缺陷可以总结如下: Java代码 package org.jooq.nashorn.test;public class API {public stati…

flex弹性盒子

注意事项 1.设为Flex布局之后,子元素的float,clear和vertical-align属性都讲失效 2.采用Flex布局的元素,称为Flex容器(Flex container),所有的子元素成为容器成员,称为Flex项目(Fle…

Oracle Golden Gate 系列十三 -- 配置GG进程检查点(checkpoint) 说明

一.Checkpoints 理论说明有关GG的Checkpoints 在系列一, GG的架构中以说明:OracleGolden Gate 系列一 --GG 架构 说明http://blog.csdn.net/tianlesoftware/article/details/6925907这里在单独拿出来说明一下,因为这是一个较为重要的概念。Ch…

开始JBoss BPM流程的3种基本方法

这一集提示和技巧将帮助您了解根据需要启动流程实例的最佳方法。 规划项目可能包括流程项目,但是您是否考虑过可以启动流程的各种方式? 也许您的JBoss BPM Suite在您的体系结构中本地运行,也许您在云中运行,但是无论它在哪里&am…

用asp.net编写冒泡排序法

这里写了一个冒泡排序的函数. int[] a newint[10] { 12,564,95,44,69,499,693,6746,6496,124}; for(inti0;i<a.Length;i) for(intj i1; j <10; j) { int min a[i]; if (a[i] > a[j]) //升序排列 …

使用Gradle构建和应用AST转换

最近&#xff0c;我想在Gradle项目中构建并应用本地ast转换。 虽然我可以找到一些有关如何编写转换的示例&#xff0c;但找不到完整的示例来显示完整的构建过程。 转换必须单独编译&#xff0c;然后放在类路径中&#xff0c;因此其源代码不能简单地放在Groovy源代码树的其余部分…

7月17日每日一答

1 什么是闭包函数&#xff0c;闭包函数满足什么样的条件&#xff1f;请写一个常见的闭包函数。 所谓的函数闭包本质是函数的嵌套和高阶函数。我们来看看要实现函数闭包要满足什么条件&#xff08;缺一不可&#xff09;&#xff1a; 1)必须嵌套函数 2)内嵌函数必须引用一个定义在…

vi 常用命令

一、vi简介 vi编辑器是所有Unix及Linux系统下标准的编辑器&#xff0c;它的强大不逊色于任何最新的文本编辑器&#xff0c;这里只是简单地 介绍一下它的用法和一小部分指令。由于对Unix及Linux系统的任何版本&#xff0c;vi编辑器是完全相同的&#xff0c;因此您可以在其 他…

Servlet基础(一)

JavaEE&#xff1a;企业级开发技术<一.基础概念>j2ee:jdk1.1--1.4 ----->> j2ee1.1 1.2 javaee:jdk--5,6,7 --->>javaee 5,6,7javaee与servlet,jsp Servlet:前后台传递数据&#xff0c;基于网络的HTTP请求的处理 实现需要借助web容器 J…

BZOJ1706奶牛接力跑

这个东西思路还是不错的。 解法就是把矩阵幂的加法改成取min&#xff0c;乘法改成加法就好&#xff0c;和floyed是一样的。这样的话&#xff0c;矩阵操作一次就相当于松弛了一次最短路。 建矩阵的过程也比较简单&#xff0c;可以离散化&#xff0c;当然下面有另一种更优秀的打法…