- 浏览: 422051 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (269)
- 原创 (7)
- Java (51)
- Java Concurrency (2)
- IDE (16)
- Linux (46)
- Database (23)
- NoSQL (35)
- Web服务器 (23)
- Log日志 (11)
- HTTP (11)
- HTML (2)
- XML (1)
- Test (7)
- Mina (0)
- Amoeba (4)
- Cobar (1)
- 序列化 (2)
- Python (5)
- PHP (1)
- Socket通信 (1)
- Network (3)
- Struts (2)
- Web前端 (10)
- Maven (6)
- SVN (15)
- Json (1)
- XMPP (2)
- Go (1)
- Other (4)
- 未整理 (5)
最新评论
-
u012374672:
[color=darkred][/color][flash=2 ...
Mongo的ORM框架的学习Morphia(annotations) -
b_l_east:
很有问题啊
利用redis的transaction功能,实现分布式下加锁
原文地址:http://blog.csdn.net/sunnydogzhou/article/details/6425686
Java的多线程编程模型1
Java多线程的类库封装在java.util.concurrent.*中,java1.4到1.5的变化就是引入了这个支持并发编程的类库。首先得感谢下大名鼎鼎人类库作者Doug Lea,牛人总是让人膜拜的。
1 什么是线程安全
A class is thread-safe if it behaves correctly when accessed from multiple threads, regardless of the scheduling or interleaving of the execution of those threads by the runtime environment, and with no additional synchronization or other coordination on the part of the calling code.
译成中文意思就是
多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。
从上面的解释可以看出
无状态对象永远是线程安全的
2 什么是原子性
多个线程执行一个操作时,其中任何一个线程要么完全执行完此操作,要么没有执行此操作的任何步骤,那么这个操作就是原子的
3 为什么线程不安全
看一个线程不安全的经典例子:
package zl.study.concurrency;
public class ReorderingDemo {
static int x = 0, y = 0, a = 0, b = 0;
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
x=y=a=b=0;
Thread one = new Thread() {
public void run() {
a = 1;
x = b;
}
};
Thread two = new Thread() {
public void run() {
b = 1;
y = a;
}
};
one.start();
two.start();
one.join();
two.join();
System.out.println(x + " " + y);
}
}
}
在这个例子中,如果你在一个单CPU的java测试环境中做测试,你可能只会得到一种结果(0,1),然而真的只有这样一种情况么?显然不是。JVM并不能保证线程的执行顺序,即使看起来你无数次测试都是(0,1),然而切换到别的环境中,出现其它结果(1,0)仍然是无法避免的。而在多CPU的结构中,就更不能保证了。线程可能在不同的CPU上执行,从而(0,0),(1,1)都是可能的。
4 为什么会出现这种情况
导致出现这些情况的原因有很多
Java内存分配
寄存器:我们在程序中无法控制
栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
堆:存放用new产生的数据
静态域:存放在对象中用static定义的静态成员
常量池:存放常量,比如.class信息,String
编译器优化
调整语句执行顺序
变量值存于寄存器而不是内存中
CPU自身的优化
并行或者按其他顺序执行
CPU本身的Cache会延迟变量的值刷新到内存的时间
在Java多线程编程模型1里面讲到了为什么线程不安全,那怎样才能做到线程安全了?
先来看线程工作是跟内存是怎么打交道的。
在并发的线程中,分为主内存和工作内存,主内存就是程序分配的内存,工作内存就是线程所占的内存。线程可能在工作内存中存储了某些主内存对象的副本。当线程操作某个主内存的对象时,先从主内存中将变量的值拷贝到工作内存中,然后在工作内存中改变这个值,最后将这个值刷到主内存中。
在<<java concurrency in pratise>>中提出了线程安全的思路
1) 不要在线程间共享变量
2) 如果不行,就要final变量
3) 还是不行,就用volatile或其它并发控制。
其实基本的思路是尽量减少共享变量,如果实在要用,则需要并发控制。
那非要用的时候怎么办了,这个时候就需要拿出happens-before规则来检查多线程的程序了。
(1)同一个线程中的每个Action都happens-before于出现在其后的任何一个Action(Program order rule)
(2)对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁(Monitor lock rule)
(3)对volatile字段的写入操作happens-before于每一个后续的同一个字段的读(Volatile variable rule.)
(4)Thread.start()的调用会happens-before于启动线程里面的动作(Thread start rule.)
(5)Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false(Thread termination rule.)
(6)一个线程A调用另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())(Interruption rule)
(7)一个对象构造函数的结束happens-before于该对象的finalizer的开始(Finalizer rule.)
(8)如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作(Transitivity)
如果你的程序能够满足上面的发则,那么恭喜你,不会在出现并发的问题了。
在java1.5之前,java在并发上面的建树不多,只提供了为数不多的方式来提供提高并发的效率。
其中synchronized关键字是使用最多的,这个看似简单的锁方式,效率奇差,所以那会,java程序员对于c++程序员的在java并发上的诟病总是无力回击。
在1.5之前,java提供的并发容器Vector,我们来看下具体的实现java.util.Vector
- public class Vector<E>
- extends AbstractList<E>
- implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- {
- public synchronized void copyInto(Object[] anArray) {
- System.arraycopy(elementData, 0 , anArray, 0 , elementCount);
- }
- public synchronized void trimToSize() {
- modCount++;
- int oldCapacity = elementData.length;
- if (elementCount < oldCapacity) {
- elementData = Arrays.copyOf(elementData, elementCount);
- }
- }
- public synchronized void ensureCapacity( int minCapacity) {
- modCount++;
- ensureCapacityHelper(minCapacity);
- }
- ... ...
- }
从中可以看出,Vector是把所有的方法前面的加上了synchronized关键字
在来看另外的一类静态方法,这类容器可以把List在包装一层,让后就可以作为并发的容器
- List list = Collections.synchronizedList( new ArrayList());
- ...
- synchronized (list) {
- Iterator i = list.iterator(); // Must be in synchronized block
- while (i.hasNext())
- foo(i.next());
- }
仔细分析会发现
- public static <T> List<T> synchronizedList(List<T> list) {
- urn (list instanceof RandomAccess ?
- new SynchronizedRandomAccessList<T>(list) :
- new SynchronizedList<T>(list));
- }
原来Collections.synchronizedList(List<T> list))这个方法最终会新建一个SynchronizedList<E>,它是继承自SynchronizedCollection<E>,来看SynchronizedList<E>的构造函数,
- SynchronizedList(List<E> list) {
- super (list);
- this .list = list;
- }
看看父类的详细的构成
- static class SynchronizedCollection<E> implements Collection<E>, Serializable {
- // use serialVersionUID from JDK 1.2.2 for interoperability
- private static final long serialVersionUID = 3053995032091335093L;
- final Collection<E> c; // Backing Collection
- final Object mutex; // Object on which to synchronize
- SynchronizedCollection(Collection<E> c) {
- if (c== null )
- throw new NullPointerException();
- this .c = c;
- mutex = this ;
- }
- SynchronizedCollection(Collection<E> c, Object mutex) {
- this .c = c;
- this .mutex = mutex;
- }
- public int size() {
- synchronized (mutex) { return c.size();}
- }
- public boolean isEmpty() {
- synchronized (mutex) { return c.isEmpty();}
- }
- public boolean contains(Object o) {
- synchronized (mutex) { return c.contains(o);}
- }
- public Object[] toArray() {
- synchronized (mutex) { return c.toArray();}
- }
- public <T> T[] toArray(T[] a) {
- synchronized (mutex) { return c.toArray(a);}
- }
- ... ...
- }
怎样,是不是有一种恍然大悟的感觉,原来在构造函数里面弄了一个 Object mutx = this,让后所有的方法在调用的时候都synchronized(mutex)
相同的方法有
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
public static <T> List<T> synchronizedList(List<T> list)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
在Java1.5之前,synchronized应该是最常用的java支持并发手段。那synchronized是怎么做到的了,从java1.0开始,java中的每个对象就一个内部锁。如果一个类的方法被synchronized关键字所修饰,那么这个对象的锁将保护整个方法。
举例来说:
public synchronized void method(){
method body
}
等价于
public void method(){
this.intrinsicLock.lock();
try{
method body;
}finally(){
this.intrinsicLock.unlock();
}
}
从上面的代码示例可以看出,synchronized的使用方式是比较简单的。这也导致了大量的初学者在碰到java编程的时候落入陷阱里,认为既然synhronized可以搞定一切,那么不管三七二十一,只要有并发可能性的地方,就加上synchronized的关键字,这显然是不对的。在java对象中,这个java对象只有这一个内部锁,其中一个synchronized方法获取到了这个锁,另外一个synchronized方法的调用将被阻塞。
即
class sync{
public synchronized void methodA(){};
public synchronized void methodB(){};
... ...
}
methodA 和methodB在初始就是互斥的,如果methodA和methodB进入互相等待,就很容易出现死锁的情况。那如果碰到这种情况,应该怎么做了?常用的方式是在方法内部新建一个无意义的对象,然后对这个无意义的对象加锅。
- package zl.study.concurrency.synchronize;
- public class Sync {
- private int i;
- public void plus(){
- Object dummy = new Object();
- synchronized (dummy){
- i++;
- }
- }
- public void minus(){
- Object dummy = new Object();
- synchronized (dummy){
- i--;
- }
- }
- }
另外需要注意的是将静态类声明为synchronized方法也是合法的。举例来说,如果Sync有一个static synchronized方法,那么这个方法被调用时,bank.class这个类对象本身在jvm中将被锁住。
Java的多线程编程模型5--从AtomicInteger开始
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
来看AtomicInteger提供的接口。
//获取当前的值
public final int get()
//取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement()
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta)
... ...
我们在上一节提到的CAS主要是这两个方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
这两个方法是名称不同,但是做的事是一样的,可能在后续的java版本里面会显示出区别来。
详细查看会发现,这两个接口都是调用一个unsafe的类来操作,这个是通过JNI实现的本地方法,细节就不考虑了。
下面是一个对比测试,我们写一个synchronized的方法和一个AtomicInteger的方法来进行测试,直观的感受下性能上的差异
- package zl.study.concurrency;
- import java.util.concurrent.atomic.AtomicInteger;
- public class AtomicIntegerCompareTest {
- private int value;
- public AtomicIntegerCompareTest( int value){
- this .value = value;
- }
- public synchronized int increase(){
- return value++;
- }
- public static void main(String args[]){
- long start = System.currentTimeMillis();
- AtomicIntegerCompareTest test = new AtomicIntegerCompareTest( 0 );
- for ( int i= 0 ;i< 1000000 ;i++){
- test.increase();
- }
- long end = System.currentTimeMillis();
- System.out.println("time elapse:" +(end -start));
- long start1 = System.currentTimeMillis();
- AtomicInteger atomic = new AtomicInteger( 0 );
- for ( int i= 0 ;i< 1000000 ;i++){
- atomic.incrementAndGet();
- }
- long end1 = System.currentTimeMillis();
- System.out.println("time elapse:" +(end1 -start1) );
- }
- }
结果
time elapse:31
time elapse:16
由此不难看出,通过JNI本地的CAS性能远超synchronized关键字
CAS,compare and swap的缩写,中文翻译成比较并交换。
我们都知道,在java语言之前,并发就已经广泛存在并在服务器领域得到了大量的应用。所以硬件厂商老早就在芯片中加入了大量直至并发操作的原语,从而在硬件层面提升效率。在intel的CPU中,使用cmpxchg指令。
在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式,因而java在并发的手段上也多了起来。而在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。
1. CAS:
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
2.非阻塞算法
如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作,就可以说该算法是无等待的。与此形成对比的是,无锁定算法要求仅 某个线程 总是执行操作。(无等待的另一种定义是保证每个线程在其有限的步骤中正确计 算自己的操作,而不管其他线程的操作、计时、交叉或速度。
3.用CAS来实现非阻塞算法
- package zl.study.concurrency;
- /**
- * AtomicInteger的模拟类,主要是用来测试CAS和非阻塞方式加锁
- * @author peter
- *
- */
- public class SimulatedAtomicInteger {
- private int value= 0 ;
- private int get(){
- return this .value;
- }
- /**
- * 模拟CAS
- * @param current
- * @param next
- * @return
- */
- private synchronized boolean compareAndSet( int current, int next){
- return current == next? true : false ;
- }
- /**
- * 模拟非阻塞算法
- * @return
- */
- public final int incrementAndGet() {
- for (;;) {
- int current = get();
- int next = current + 1 ;
- if (compareAndSet(current, next))
- return next;
- }
- }
- }
需要注意的是这个方法中的CAS是在jav代码实现的,这个并没有包含内存位置。在concurrent包中,是JNI的方式,内存位置也作为参数传入这个JNI方法中,在后面碰到了在做详细的介绍
在后面介绍java 5提供的并发工具时,我们还能经常看到类似于SimulatedAtomicInteger得写法,大家可以好好体会!
发表评论
-
MyBatis-generator使用,为Example添加分页
2017-11-01 16:10 4960数据库为MySQL。1. 在Example类里,加入两个变 ... -
使用Spring MVC统一异常处理实战
2017-08-22 14:26 3361 描述 在J2EE项目的开 ... -
日志组件的关系梳理:如何正确使用它们
2017-08-07 14:25 731背景 由于现在开源框架日益丰富,好多开源框架使用的 ... -
Java中“引用”的几种类型
2017-07-18 17:09 611一. 概述: 强引用(S ... -
Spring和Mybatis整合时无法读取properties的处理方案
2016-11-29 11:39 1741config.properties配置文件信息 ... -
Protobuf使用
2016-07-12 11:49 2191ProtoBuf的官方下载包并不包含jar文件,需要用户自 ... -
jmeter读取外部配置文件
2016-06-06 10:30 0配置文件有两类: 一、路径相关配置文件,只需要了解清楚jm ... -
@SuppressWarnings抑制警告的关键字
2016-05-16 15:45 1963关键字 用途 all to suppress a ... -
Apache的DbUtils框架学习
2016-04-01 19:47 758一、commons-dbutils简介 co ... -
Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)
2016-03-30 20:13 705互联网的发展,网站 ... -
Java GC 详解
2016-03-30 19:54 7371、基本回收算法 (1) 引用计数(Reference ... -
JVM(Java虚拟机)优化大全和案例实战
2016-03-30 19:53 500堆内存设置 原理 JVM堆内存分为2块:Perman ... -
Spring事务的传播行为和隔离级别
2016-02-20 22:32 910http://blog.csdn.net/paincupi ... -
java中什么是bridge method(桥接方法)
2016-01-31 19:19 558在看spring-mvc的源码的时候,看到在解析handle ... -
@SuppressWarnings的使用、作用、用法
2016-01-06 16:45 1522在java编译过程中会出现很多警告,有很多是安全的,但是每次 ... -
fastjson遇到的无限递归的问题
2015-09-13 18:09 3983fastjson是用反射的,如果在实体类里 ... -
当spring 容器初始化完成后执行某个方法
2015-08-11 14:56 2262在做web项目开发中,尤其是企业级应用开发的时候,往往会在工 ... -
javac命令初窥
2015-07-30 14:05 1952注:以下红色标记的参数在下文中有所讲解。 用法: ja ... -
JDK各版本地址下载
2015-07-17 13:09 13431. 总地址:http://www.oracle.com/ ... -
jdk1.5-1.9新特性
2015-07-17 13:02 17981.51.自动装箱与拆箱:2.枚举(常用来设计单例模式)3. ...
相关推荐
Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠 Java线程:线程的调度-优先级 Java线程:线程的调度-让步 Java线程...
详细的讲述了多线程的各种用法 Java线程:概念与原理 Java线程:创建与启动 Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠...
多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储...
网上找了份资料,是别人完成的Java实现多线程下载的功能。Java多线程的好处挺多的,可以充分利用CPU的资源,简化编程模型,简化异步事件的处理,使GUI更有效率,节约成本...
java内存模型,对初学者比较实用,理解后有助于多线程编程等
Java多线程设计模式,通过对多线程环境的分析,抽象出典型的多线程模型,结合Java编程语言的特性,总结出经典的多线程设计模式,通过本资料的学习,足以让您掌握如何使用JAVA编程技术来解决多线程问题,结合本书实例...
第4页 主要内容 8.1 Java线程模型 8.2 创建线程 8.3 同步与线程间通信 8.4 获取线程状态 8.5 本章小结 8.6 思考和练习 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第4页。 8.1 Java线程模型 Java对多...
java.util.concurrent包、synchronized关键字、Swing框架、Java内存模型等内容也均有涉及,不仅能够了解Java多线程的相关知识,还可加深对Java语言的理解。 本书适合以下读者阅读 a.对多线程感兴趣的人 b.对Java...
java多线程的创建,主流的几种创建方式都有详细的讲解。线程的交互以及线程的同步锁的问题都有具体的实例。java的内存模型,java会话都有讲解,如果是刚接触java多线程,可以下载来看看
深入讲解java并发编程技术,多线程、锁以及java内存模型等
虽然当前CPU主频在不断升高,但是X86架构的硬件已经成为瓶颈,这种架构的CPU主频最高为4G,事实上目前3.6G主频的CPU已经接近顶峰,多线程编程模型不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想
本资源为您提供了关于 Java 并发编程理论基础的精讲,涵盖了多线程编程的核心概念、基本原理以及在 Java 中的应用。通过深入学习,您将建立坚实的并发编程基础,能够更好地理解和应对多线程编程中的挑战。 并发编程...
java学习笔记_多线程网络编程.txt,包括多线程、网络编程、网络模型、TCP客户端和服务端代码
限制线程优先级和调度 Java 线程模型涉及可以动态更改的线程优先级。本质上,线程的优先级是从 1 到 10 之间的一个数字,数字越大表明任务越紧急。JVM 标准首先调用优先级较高的线程,然后才调用优先级较低的线程。...
● JMM的关键技术点都是围绕着多线程的原⼦性、可⻅性和有序性来创建的。所以,下⾯我们来⼀⼀ 介绍这三种特性。原子性、可见性、有序性。 正常情况下,如果我们不使⽤volatile,那么每条线程都会有⾃⼰的缓存,当...
大致内容包括: Java内存模型 Java内存交互协议 Java的线程 Netty的并发编程分析 正确的使用锁 volatile的正确使用 CAS指令和原子类 线程安全类 读写锁的应用
00 IBM developerWorks 中国 : Java 多线程与并发编程专题 02 Java 程序中的多线程 03 编写多线程的 Java 应用程序 04 如果我是国王:关于解决 Java编程语言线程问题的建议 (2) 05 构建Java并发模型框架 (2) 06...