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 并发锁

java 并发锁_Java并发教程–锁定:内在锁相关推荐

  1. java 并发锁_Java并发教程–重入锁

    java 并发锁 Java的synced关键字是一个很棒的工具–它使我们可以通过一种简单可靠的方式来同步对关键部分的访问,而且也不难理解. 但是有时我们需要对同步进行更多控制. 我们要么需要分别控制访 ...

  2. java投票锁_Java并发编程锁之独占公平锁与非公平锁比较

    Java并发编程锁之独占公平锁与非公平锁比较 公平锁和非公平锁理解: 在上一篇文章中,我们知道了非公平锁.其实Java中还存在着公平锁呢.公平二字怎么理解呢?和我们现实理解是一样的.大家去排队本着先来 ...

  3. java 共享锁 独占锁_Java并发编程锁之独占公平锁与非公平锁比较

    Java并发编程锁之独占公平锁与非公平锁比较 公平锁和非公平锁理解: 在上一篇文章中,我们知道了非公平锁.其实Java中还存在着公平锁呢.公平二字怎么理解呢?和我们现实理解是一样的.大家取排队本着先来 ...

  4. java lock 对象_Java并发编程锁系列之ReentrantLock对象总结

    Java并发编程锁系列之ReentrantLock对象总结 在Java并发编程中,根据不同维度来区分锁的话,锁可以分为十五种.ReentranckLock就是其中的多个分类. 本文主要内容:重入锁理解 ...

  5. java并发调用_Java并发教程–可调用,将来

    java并发调用 从Java的第一个发行版开始,Java的美丽之处之一就是我们可以轻松编写多线程程序并将异步处理引入我们的设计中. Thread类和Runnable接口与Java的内存管理模型结合在一 ...

  6. java并发队列_Java并发教程–阻塞队列

    java并发队列 如第3部分所述,Java 1.5中引入的线程池提供了核心支持,该支持很快成为许多Java开发人员的最爱. 在内部,这些实现巧妙地利用了Java 1.5中引入的另一种并发功能-阻塞队列 ...

  7. java公平索非公平锁_Java 并发编程中使用 ReentrantLock 替代 synchronized

    Java 5 引入的 Concurrent 并发库软件包中,提供了 ReentrantLock 可重入同步锁,用来替代 synchronized 关键字原语,并可提供更好的性能,以及更强大的功能.使用 ...

  8. java并发排它锁_Java并发编程进阶——锁(解析)

    一.锁是什么 java开发中进行并发编程时针对操作同一块区域时,如果不加锁会出现并发问题,数据不是自己预计得到的值.我觉得有点像mysql事务中脏读.不可重复读.幻读的问题.加锁的目的是为了保证同一时 ...

  9. java公平锁和非公平锁_java并发编程学习之再谈公平锁和非公平锁

    在java并发编程学习之显示锁Lock里有提过公平锁和非公平锁,我们知道他的使用方式,以及非公平锁的性能较高,在AQS源码分析的基础上,我们看看NonfairSync和FairSync的区别在什么地方 ...

最新文章

  1. linux修改vim配色,更改vim配色的具体操作 更改vim配色的图文教程
  2. 【Python3 爬虫】03_urllib.error异常处理
  3. 新站优化最应该考虑哪些方面
  4. 邻接表建立图(c语言)
  5. NumPy-快速处理数据--ndarray对象--多维数组的存取、结构体数组存取、内存对齐、Numpy内存结构...
  6. SAP Spartacus B2B 页面信息提示图标的弹出窗口显示实现逻辑
  7. 两个fetion飞信API
  8. 基于多源文档片段的神经网络排序模型(Neural Ranking Models with Multiple Document Fields)
  9. Java爬取网页源代码解析
  10. 基于预训练深度学习算法的番茄作物病害分类
  11. 对SP光刻机表示谨慎
  12. linux常见的三种shell,几种常见的Shell
  13. linux内核计算list的长度,linux内核list.h头文件分析(四)
  14. 针式PKM V5.78
  15. Smart3D-安装教程
  16. 19年6月英语六级第二套听力单词
  17. Android面试准备复习之Android知识点大扫描
  18. html字体制作,用@font-face实现网页特殊字符(制作自定义字体)
  19. “我是技术总监,你干嘛总问我技术细节?”
  20. ICO和区块链的关系

热门文章

  1. P3600-随机数生成器【dp,数学期望】
  2. 【KMP】重复子串(ybtoj KMP-2)
  3. Nacos(六)之Spring Boot集成
  4. 深度分析Java的ClassLoader机制(源码级别)
  5. 2016经典微小说:《轮回》
  6. jQuery最简单的留言功能^-^
  7. 在gitee上创建自己的仓库步骤
  8. Mybatis中使用Dao实现类实现增删改查【实际开发中使用代理dao】
  9. org.springframework.amqp.AmqpConnectException java.net.ConnectException的解决办法
  10. java编译提示错误信息_JAVA编译错误提示缺少“{”