多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修改,就会导致修改的状态不一致.

用一个实际的例子来说明线程同步的必要性:

package cn.outofmemory.locks;

public class LockDemo implements Runnable {
private int counter = 0; public void run() {
int loopTimes = 10000;
while (loopTimes > 0) {
 counter ++;
loopTimes --;
}
} public static void main(String[] args) throws InterruptedException {
LockDemo demo = new LockDemo(); Thread[] threads = new Thread[]{
new Thread(demo), new Thread(demo),new Thread(demo), new Thread(demo),new Thread(demo)
}; for (Thread t : threads) {
t.start();
} for (Thread t : threads) {
t.join();
} System.out.println("demo's counter is " + demo.counter);
}
}

这段代码中的LockDemo类实现了Runnable接口,在run方法中对其私有变量counter递加了10000次。在main方法中我们首先初始化了一个LockDemo对象,然后初始化了5个线程,这5个线程公用一个LockDemo的实例。
然后我们一次启动这5个线程,然后通过join等待所有线程结束,最后输出demo实例的counter值来。

运行程序,我这儿得到这样一个输出结果:

demo's counter is 44041

本来5个线程每个线程递加10000次,应该得到的结果是50000,而实际的结果是44041.

如果你也运行此程序有可能会得到不一样的结果。这取决于这5个线程造成了多少次的冲突。从我的输出结果看,这段程序的5个线程造成了大约6000次的内存争用冲突。
在实际应用中,这是不可用的。

改进程序,避免冲突

我们可以分析一下,这5个线程的冲突出现在什么地方,他们公用了demo对象,同时对demo对象的成员变量counter做递加,也就是说冲突出现在对counter递加这一步上。
我们在这一步操作上加上synchronzied关键字,让5个线程执行到对counter++这步代码时单独运行,应该就可以解决问题了。

修改后的run方法代码:

	public void run() {
int loopTimes = 10000;
while (loopTimes > 0) {
synchronized (this) {
counter ++;
}
loopTimes --;
}
}

我们再次运行程序会得到如下确定的输出结果:

demo's counter is 50000

这次得到的结果是符合我们的预期的,我们通过synchronized关键字解决了问题。

synchronized关键字是jvm虚拟机的关键字,在java.util.concurrent.locks命名空间中还有一个Lock接口,和Lock接口的实现类ReentrantLock(可重入锁)。 ReentrantLock可以实现和synchronized关键字相同的功能,而且更为灵活,在极端的情况下性能会更好一些。

我们看下使用可重入锁ReentrantLock解决线程同步的方法:

	private final Lock lock = new ReentrantLock();

	public void run() {
int loopTimes = 10000;
while (loopTimes > 0) {
try {
lock.lock();
counter ++;
} finally {
lock.unlock();
}
loopTimes --;
}
}

我们在LockDemo中添加了一个final的成员变量lock,它是一个ReentrantLock的实例。 在run方法中,在counter++这行代码两边加上了try .. finally ..语句,
当线程执行到try块之后,首先通过lock.lock()获得锁,获得锁之后再执行counter++,最后在finally语句块中通过lock的unlock方法释放锁。

我们可以运行修改后的代码,输出如下:

demo's counter is 50000

输出结果符合逻辑预期。

synchronized获得的内部锁存在一定的局限

1. 不能中断一个正在试图获得锁的线程

2. 试图获得锁时不能像trylock那样设定超时时间

3. 每个锁只有单一的条件,不像condition那样可以设置多个

synchronzied关键字和可重入锁ReentrantLock选择的最佳实践:

1. 如果synchronized关键字适合程序,尽量使用它,可以减少代码出错的几率和代码数量
2. 如果特别需要Lock/Condition结构提供的独有特性时,才使用他们
3. 许多情况下可以使用java.util.concurrent包中的一种机制,它会为你处理所有的加锁情况

推荐阅读:
支持

2 顶

反对

0 踩

synchronized关键字,Lock接口以及可重入锁ReentrantLock的更多相关文章

  1. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  2. 17_重入锁ReentrantLock

    [概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...

  3. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  4. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  5. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  6. Java 显示锁 之 重入锁 ReentrantLock(七)

    ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...

  7. Java中可重入锁ReentrantLock原理剖析

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...

  8. 多线程通信的两种方式? (可重入锁ReentrantLock和Object)

    (一)Java中线程协作的最常见的两种方式: (1)利用Object的wait().notify()和notifyAll()方法及synchronized (2)使用Condition.Reentra ...

  9. Java多线程——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...

随机推荐

  1. 通过git rebase修改commit message

    今天发现一个项目的git commit message中的单词拼错了,需要修改一下.但这样简单的修改,需要通过git rebase才能完成. 首先要git rebase到需要修改message的那个c ...

  2. Reading Notes of Acceptance Test Engineering Guide

    The Acceptance Test Engineering Guide will provide guidance for technology stakeholders (developers, ...

  3. 降龙十八掌之一:(亢龙有悔)SQL Server Profiler和数据库引擎优化顾问

    简介 说到Sql的[性能工具]真是强大,SQL Server Profiler的中文意思是SQL Server事件探查,这个到底是做什么用的呢?我们都知道探查的意思大多是和监视有关,其实这个SQL S ...

  4. [ACM_图论] Domino Effect (POJ1135 Dijkstra算法 SSSP 单源最短路算法 中等 模板)

    Description Did you know that you can use domino bones for other things besides playing Dominoes? Ta ...

  5. 微软Dynamics 使用葡萄城的Wijmo 5提供移动端用户界面

    近日,全球最大的控件提供商葡萄城公司宣布: 葡萄城近日与微软公司达成合作,将Wijmo 产品线的HTML5和JaveScript 控件融合到微软Dynamics CRMOnline 2016版中. 随 ...

  6. C#课外实践——校园二手平台(技术篇1)

    前面分享了这次的课外实践的心得,这次,就分享一下从这次的课外实践的过程中学到的知识技能吧.虽然有句话说的好,不要做没有准备的战争,但是,我想说的是,生活中有很多的事情是不允许我们有准备的.遇到事情必须 ...

  7. asp.net mvc 如何调用微信jssdk接口:分享到微信朋友(圈)| 分享到qq空间

    如何在asp.net mvc 项目里, 调用微信jssdk接口,现实功能: 分享到微信朋友(圈)| 分享到qq空间 1 创建一个Action,准备一些数据,初始化数据(签名): /// <sum ...

  8. SVM=LASSO?

    SVM和LASSO是机器学习里两个非常经典的模型,每个模型都有大量的文献进行研究.其中去年出版的这本书——<Regularization, Optimization, Kernels, and ...

  9. js运动:多div变宽、二级菜单

    定时器及运动函数. 多div变宽: <!-- Author: XiaoWen Create a file: 2016-12-13 09:36:30 Last modified: 2016-12- ...

  10. jQuery的简单入门练习

    <html> <head> <meta charset="utf-8"> <title>jQuery的练习</title> ...