原文地址:http://developer.51cto.com/art/201509/490965.htm

一、场景

因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举 个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果 呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。

二、代码示例

1、未加同步

银行类:

package threadTest;  

/**
* @author ww
*
*/
public class Bank { private int count =0;//账户余额 //存钱
public void addMoney(int money){
count +=money;
System.out.println(System.currentTimeMillis()+"存进:"+money);
} //取钱
public void subMoney(int money){
if(count-money < 0){
System.out.println("余额不足");
return;
}
count -=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
} //查询
public void lookMoney(){
System.out.println("账户余额:"+count);
}
}

测试类:

package threadTest;  

public class SyncThreadTest {  

    public static void main(String args[]){
final Bank bank=new Bank(); Thread tadd=new Thread(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bank.addMoney(100);
bank.lookMoney();
System.out.println("\n"); }
}
}); Thread tsub = new Thread(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
while(true){
bank.subMoney(100);
bank.lookMoney();
System.out.println("\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
tsub.start(); tadd.start();
} }

代码很简单,我就不解释了,看看运行结果怎样呢?截取了其中的一部分,是不是很乱,有写看不懂。

余额不足
账户余额:0
余额不足
账户余额:100
1441790503354存进:100
账户余额:100
1441790504354存进:100
账户余额:100
1441790504354取出:100
账户余额:100
1441790505355存进:100
账户余额:100
1441790505355取出:100
账户余额:100

2、同步方法

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

测试类省略,代码也不列出来了

运行结果:

再看看运行结果:
余额不足
账户余额:0
余额不足
账户余额:0
1441790837380存进:100
账户余额:100
1441790838380取出:100
账户余额:0
1441790838380存进:100
账户余额:100
1441790839381取出:100
账户余额:0

3、同步代码块

package threadTest;  

/**
* @author ww
*
*/
public class Bank { private int count =0;//账户余额 //存钱
public void addMoney(int money){ synchronized (this) {
count +=money;
}
System.out.println(System.currentTimeMillis()+"存进:"+money);
} //取钱
public void subMoney(int money){ synchronized (this) {
if(count-money < 0){
System.out.println("余额不足");
return;
}
count -=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
} //查询
public void lookMoney(){
System.out.println("账户余额:"+count);
}
}

运行效果:

余额不足
账户余额:0
1441791806699存进:100
账户余额:100
1441791806700取出:100
账户余额:0
1441791807699存进:100
账户余额:100
效果和方法一差不多

4、使用特殊域变量(Volatile)实现线程同步

a.volatile关键字为域变量的访问提供了一种免锁机制
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

package threadTest;  

/**
* @author ww
*
*/
public class Bank { private volatile int count = 0;// 账户余额 // 存钱
public void addMoney(int money) { count += money;
System.out.println(System.currentTimeMillis() + "存进:" + money);
} // 取钱
public void subMoney(int money) { if (count - money < 0) {
System.out.println("余额不足");
return;
}
count -= money;
System.out.println(+System.currentTimeMillis() + "取出:" + money);
} // 查询
public void lookMoney() {
System.out.println("账户余额:" + count);
}
}

运行效果:

余额不足
账户余额:0
余额不足
账户余额:100
1441792010959存进:100
账户余额:100
1441792011960取出:100
账户余额:0
1441792011961存进:100
账户余额:100

是不是又看不懂了,又乱了。这是为什么呢?就是因为volatile不能保证原子操作导致的,因此volatile不能代替 synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰 的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步

5、使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

ReenTrantLock可重入锁(和synchronized的区别)总结

1)可重入性:

从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

2)锁的实现:

Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。

3)性能的区别:

在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

package threadTest;  

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
* @author ww
*
*/
public class Bank { private int count = 0;// 账户余额 //需要声明这个锁
private Lock lock = new ReentrantLock(); // 存钱
public void addMoney(int money) {
lock.lock();//上锁
try{
count += money;
System.out.println(System.currentTimeMillis() + "存进:" + money); }finally{
lock.unlock();//解锁
}
} // 取钱
public void subMoney(int money) {
lock.lock();
try{ if (count - money < 0) {
System.out.println("余额不足");
return;
}
count -= money;
System.out.println(+System.currentTimeMillis() + "取出:" + money);
}finally{
lock.unlock();
}
} // 查询
public void lookMoney() {
System.out.println("账户余额:" + count);
}
}

运行效果:

余额不足
账户余额:0
余额不足
账户余额:0
1441792891934存进:100
账户余额:100
1441792892935存进:100
账户余额:200
1441792892954取出:100
账户余额:100
效果和前两种方法差不多。

6、使用局部变量实现线程同步(ThreadLocal

package threadTest;  

/**
* @author ww
*
*/
public class Bank { private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ @Override
protected Integer initialValue() {
// TODO Auto-generated method stub
return 0;
} }; // 存钱
public void addMoney(int money) {
count.set(count.get()+money);
System.out.println(System.currentTimeMillis() + "存进:" + money); } // 取钱
public void subMoney(int money) {
if (count.get() - money < 0) {
System.out.println("余额不足");
return;
}
count.set(count.get()- money);
System.out.println(+System.currentTimeMillis() + "取出:" + money);
} // 查询
public void lookMoney() {
System.out.println("账户余额:" + count.get());
}
}

运行效果:

余额不足
账户余额:0
余额不足
账户余额:0
1441794247939存进:100
账户余额:100
余额不足
1441794248940存进:100
账户余额:0
账户余额:200
余额不足
账户余额:0
1441794249941存进:100
账户余额:300

看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变 量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面 的效果。

ThreadLocal与同步机制

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题
b.前者采用以”空间换时间”的方法,后者采用以”时间换空间”的方式

 

java多线程同步(转)的更多相关文章

  1. Java多线程同步问题的探究

    一.线程的先来后到——问题的提出:为什么要有多线程同步?Java多线程同步的机制是什么? http://www.blogjava.net/zhangwei217245/archive/2010/03/ ...

  2. 转:关于JAVA多线程同步

    转:http://lanvis.blog.163.com/blog/static/26982162009798422547/ 因为需要,最近关注了一下JAVA多线程同步问题.JAVA多线程同步主要依赖 ...

  3. java多线程同步

    一篇好文:java多线程机制同步原则 概括起来说,Java 多线程同步机制主要包含如下几点:1:如果一个类包含一个或几个同步方法,那么由此类生成的每一个对象都配备一个队列用来容纳那些等待执行同步的线程 ...

  4. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  5. Java多线程同步 synchronized 关键字的使用

    代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...

  6. Java多线程---同步与锁

    一,线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 二.同步和锁定 1.锁的原理 Java中每个对象都有一个内置锁. 当程序运行到非静态的synchronized同步方法上时,自动 ...

  7. Java多线程同步的方法

    一 synchronized关键字 1.synchronized实现原理: ---基于对象监视器(锁) java中所有对象都自动含有单一的锁,JVM负责跟踪对象被加锁的次数.如果一个对象被解锁,其计数 ...

  8. Java 多线程同步的五种方法

    一.引言 闲话不多说,进入正题. 二.为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常.举个例子 ...

  9. Java多线程同步问题:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.一个简单的Demo引发的血案 关于线程同步问题我们从一个 ...

随机推荐

  1. 9 random模块

    1.ramdom模块主要方法 random.random()函数是这个模块中最常用的方法了,它会生成一个随机的浮点数,范围是在0.0~1.0之间. random.uniform()正好弥补了上面函数的 ...

  2. 迷你MVVM框架 avalonjs 1.3.8发布

    avalon1.3.8主要是在ms-repeat. ms-each. ms-with等循环绑定上做重大性能优化,其次是对一些绑定了事件的指令添加了roolback,让其CG回收更顺畅. 重构ms-re ...

  3. 迷你MVVM框架 avalonjs 学习教程22、avalon性能大揭密

    avalon之所以能在页面处理1W个绑定(angular对应的数字是2000),出于两个重要设计--基于事件驱动的双向绑定链及智能CG回收机制. avalon的双向绑定链是通过Object.defin ...

  4. cocos2d-x 3.0 学习笔记: 一个可以拖拽的Label及schedule的应用

    #ifndef _DRAGLABEL_H_ #define _DRAGLABEL_H_ #include "cocos2d.h" USING_NS_CC; class DragLa ...

  5. ArcGIS案例学习笔记2_2

    ArcGIS案例学习笔记2_2 联系方式:谢老师,135_4855_4328,xiexiaokui#qq.com 时间:第二天下午 2018年8月12日 案例1:模型构建器,山顶点提取 背景:数据量大 ...

  6. SQL 数据库主键 ,外键

    主键 数据库主键是指表中一个列或列的组合,其值能唯一地标识表中的每一行.这样的一列或多列称为表的主键,通过它可强制表的实体完整性.当创建或更改表时可通过定义 PRIMARY KEY约束来创建主键.一个 ...

  7. SpringDataRedis事务 专题

    5.10.1. @Transactional SupportTransaction Support is disabled by default and has to be explicitly en ...

  8. maven配置国内镜像库

    https://www.cnblogs.com/xiongxx/p/6057558.html

  9. Manta

    安装python客户端: pip install manta import manta as pymanta# cat ${MANTA_PRIVATE_KEY_PATH} | tr '\n' '#' ...

  10. Eletron 打开文件夹,截图

    1.shell.openItem(fullPath) var fullpath = path.join(processPath)+Math.random()+".png"; she ...