多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如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包中的一种机制,它会为你处理所有的加锁情况

多线程里面的关键字,wait, notfiy, 锁(synchronized), lock接口的更多相关文章

  1. Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)

    多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...

  2. Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)

    关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨Lock对象. synchronize ...

  3. java中的锁之Lock接口与Condition接口

    一.Lock源码. 1.是一个接口.一共有6个方法. 2.方法详细如下: (1)当前线程尝试获取锁.结果分两种情况,一是成功获取到锁,则返回:二是获取锁失败,则一直等待.不响应中断请求. (2)当前线 ...

  4. 同步中的四种锁synchronized、ReentrantLock、ReadWriteLock、StampedLock

    目录 1.synchronized同步锁 2.ReentrantLock重入锁 3.ReadWriteLock读写锁 4.StampedLock戳锁(目前没找到合适的名字,先这么叫吧...) 5.总结 ...

  5. 同步中的四种锁synchronized、ReentrantLock、ReentrantReadWriteLock、StampedLock

    为了更好的支持并发程序,JDK内部提供了多种锁.本文总结4种锁. 1.synchronized同步锁 使用: synchronized本质上就2种锁: 1.锁同步代码块 2.锁方法 可用object. ...

  6. jdk1.5多线程Lock接口及Condition接口

    jdk1.5多线程的实现的方式: jdk1.5之前对锁的操作是隐式的 synchronized(对象) //获取锁 { } //释放锁 jdk1.5锁的操作是显示的:在包java.util.concu ...

  7. Android进阶——多线程系列之wait、notify、sleep、join、yield、synchronized关键字、ReentrantLock锁

    多线程一直是初学者最困惑的地方,每次看到一篇文章,觉得很有难度,就马上叉掉,不看了,我以前也是这样过来的.后来,我发现这样的态度不行,知难而退,永远进步不了.于是,我狠下心来看完别人的博客,尽管很难但 ...

  8. synchronized关键字,Lock接口以及可重入锁ReentrantLock

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

  9. synchronized关键字以及实例锁 类锁

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

随机推荐

  1. http进阶

    前言: 上一篇博文已经说到了,apache2.4简单的配置,端口,持久连接,MPM,DSO,路径下基于来源控制,页面特性,日志设置 安全域,虚拟主机等等. 一:URL URL是互联中获取标记资源的方式 ...

  2. EntityFramework Core 1.1+ Backing Fields(返回字段)

    前言 通过我发表的博文可知最近一段时间会将持续讲解EntityFramework Core特性,在此之前我提到过Backing Fields,回头翻了翻感觉写的还不够好,于是乎再来讲解一番,也是自己再 ...

  3. select函数的介绍和使用

    我们所使用的I/O模型一共有五种. 分别为阻塞I/O,非阻塞I/O,I/O复用,信号驱动I/O,异步I/O. 所谓I/O复用就是指管理多个I/O文件描述符,一般会使用(select,poll,epol ...

  4. WordPress评论时一键填入昵称、邮箱和网址

    现在很多博客都启用了多说,可是依然有很多博主坚守着wordpress或其主题自带的评论框,这样,每当我们访问这些博客时,发现精彩的内容或者 找到共鸣时.抑或只是想挑逗一下博主,准备在评论处爽爽的来一发 ...

  5. 关于Sql server数据 MD5加密

    最近在写一个web项目时,需要在数据库中将用户密码等一类信息进行加密处理.数据加密算法有许多  ,各有各的优缺点;在  http://www.cnblogs.com/yangywyangyw/arch ...

  6. 使用Jmeter自带的 Http 代理服务器录制脚本

    最近要测试某个模块的压力测试,所以使用Jmeter录制脚本 1.       打开JMeter工具 创建一个线程组(右键点击“测试计划”--->“添加”---->“线程组”) 创建一个ht ...

  7. IPFS: BitSwap协议(数据块交换)

    原创 2018-01-11 飞向未来 IPFS指南 BitSwap协议 IPFS节点之间是如何进行数据交换的?本文来讲一下这个问题. IPFS在BitTorrent的基础上实现了p2p数据交换协议:B ...

  8. 移动端HTML5性能优化

    移动端HTML5性能优化 [导读] 得益于智能手机的普及和各行各业互联网+的运动,移动端的市场占比疯狂增长. 2016年1月发布的2015年电商数据显示,2015年中国移动端网购交易额同比暴涨123 ...

  9. bat脚本:Java一键编译(Javac java)

    bat脚本:Java一键编译(Javac java) D:    是指D盘 javat是要编译的.java文件所在的文件夹 也就是D:\javat bat代码: :start COLOR 0A cls ...

  10. SSE(Server-sent events)技术在web端消息推送和实时聊天中的使用

    最近在公司闲着没事研究了几天,终于搞定了SSE从理论到实际应用,中间还是有一些坑的. 1.SSE简介 SSE(Server-sent events)翻译过来为:服务器发送事件.是基于http协议,和W ...