- 浏览: 432931 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (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 5037数据库为MySQL。1. 在Example类里,加入两个变 ... -
使用Spring MVC统一异常处理实战
2017-08-22 14:26 3781 描述 在J2EE项目的开 ... -
日志组件的关系梳理:如何正确使用它们
2017-08-07 14:25 768背景 由于现在开源框架日益丰富,好多开源框架使用的 ... -
Java中“引用”的几种类型
2017-07-18 17:09 650一. 概述: 强引用(S ... -
Spring和Mybatis整合时无法读取properties的处理方案
2016-11-29 11:39 1781config.properties配置文件信息 ... -
Protobuf使用
2016-07-12 11:49 2230ProtoBuf的官方下载包并不包含jar文件,需要用户自 ... -
jmeter读取外部配置文件
2016-06-06 10:30 0配置文件有两类: 一、路径相关配置文件,只需要了解清楚jm ... -
@SuppressWarnings抑制警告的关键字
2016-05-16 15:45 1997关键字 用途 all to suppress a ... -
Apache的DbUtils框架学习
2016-04-01 19:47 809一、commons-dbutils简介 co ... -
Dubbo与Zookeeper、SpringMVC整合和使用(负载均衡、容错)
2016-03-30 20:13 737互联网的发展,网站 ... -
Java GC 详解
2016-03-30 19:54 7681、基本回收算法 (1) 引用计数(Reference ... -
JVM(Java虚拟机)优化大全和案例实战
2016-03-30 19:53 534堆内存设置 原理 JVM堆内存分为2块:Perman ... -
Spring事务的传播行为和隔离级别
2016-02-20 22:32 950http://blog.csdn.net/paincupi ... -
java中什么是bridge method(桥接方法)
2016-01-31 19:19 615在看spring-mvc的源码的时候,看到在解析handle ... -
@SuppressWarnings的使用、作用、用法
2016-01-06 16:45 1580在java编译过程中会出现很多警告,有很多是安全的,但是每次 ... -
fastjson遇到的无限递归的问题
2015-09-13 18:09 4038fastjson是用反射的,如果在实体类里 ... -
当spring 容器初始化完成后执行某个方法
2015-08-11 14:56 2303在做web项目开发中,尤其是企业级应用开发的时候,往往会在工 ... -
javac命令初窥
2015-07-30 14:05 2024注:以下红色标记的参数在下文中有所讲解。 用法: ja ... -
JDK各版本地址下载
2015-07-17 13:09 13921. 总地址:http://www.oracle.com/ ... -
jdk1.5-1.9新特性
2015-07-17 13:02 18651.51.自动装箱与拆箱:2.枚举(常用来设计单例模式)3. ...
相关推荐
《汪文君JAVA多线程编程实战》是一本专注于Java多线程编程的实战教程,由知名讲师汪文君倾力打造。这本书旨在帮助Java开发者深入理解和熟练掌握多线程编程技术,提升软件开发的效率和质量。在Java平台中,多线程是...
《Java多线程编程实战指南-核心篇》是一本深入探讨Java并发编程的书籍,旨在帮助读者掌握在Java环境中创建、管理和同步线程的核心技术。Java的多线程能力是其强大之处,使得开发者能够在同一时间执行多个任务,提高...
这份“Java多线程编程指南”深入探讨了这一主题,为中级到高级的Java开发者提供了宝贵的资源。 首先,多线程的基础概念是理解整个主题的关键。线程是程序执行的最小单元,每个线程都有自己的程序计数器、虚拟机栈、...
《深入学习:Java多线程编程》是一本专注于Java并发技术的专业书籍,旨在帮助开发者深入理解和熟练运用Java中的多线程编程。Java多线程是Java编程中的核心部分,尤其在现代高性能应用和分布式系统中不可或缺。理解并...
Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升系统效率。在本教程中,我们将深入探讨Java中的多线程设计模式、并发核心编程概念以及线程池的工作原理和种类。 首先,让我们了解什么...
### Java多线程编程经验 #### 一、Java线程:概念与原理 现代操作系统都是多任务操作系统,其中多线程是一种重要的实现多任务的方式。线程是进程内的一个执行单位,一个进程可以包含多个线程。例如,在Java应用...
### Java多线程编程详解与实战案例 #### 理解多线程概念与Java内存模型 多线程,作为现代编程中的一项关键技术,允许在单一应用程序中并发执行多个指令流,每个这样的指令流被称为一个线程。在Java中,线程被视为...
Java多线程编程深入详解 多线程编程是Java编程语言中的一种重要技术,用于提高程序的执行效率和响应速度。在本文中,我们将深入探讨Java多线程编程的基础知识和高级技术。 什么是多进程和多线程? 在计算机科学中...
Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,提高了系统的效率和响应性。在Java中,多线程的实现主要通过两种方式:继承Thread类和实现Runnable接口。理解并掌握多线程的使用对于任何Java开发者...
Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升系统效率。在本实例源码中,包含17个章节和上百个实例,旨在深入讲解Java多线程的核心概念和实际应用。 一、线程基础知识 在Java中,...
Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升程序的效率和响应性。本文将详细解析Java中实现多线程的两种主要方式,并讨论线程的基本概念和内存模型。 首先,理解多线程的概念至关...
### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...
### Java多线程编程环境中单例模式的实现 #### 概述 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,单例模式的应用非常广泛,特别是在资源管理、日志记录、...
Java中的多线程编程则基于这种操作系统级别的并发模型。 Java虚拟机(JVM)为每一个Java应用程序启动一个进程,而在这个进程中,所有的代码执行都是通过线程来完成的。默认情况下,Java程序的main方法在一个称为...
在多线程编程领域,Java作为一门广泛使用的编程语言,其内置的多线程模型一直是学习和应用的重点。本文将深入探讨Java多线程模型的相关知识点,包括线程与进程的区别、线程的实现原理、线程的创建方法以及线程的阻塞...
### Java多线程编程知识点详解 #### 一、线程基础概述 - **定义与特点:** - **线程**是一种比进程更细粒度的执行单元,它允许在一个进程中并发执行多个任务。 - **轻量级进程**:线程有时被称为轻量级进程,...
### Java多线程编程总结 #### 一、Java线程:概念与原理 - **操作系统中线程和进程的概念** 当前的操作系统通常都是多任务操作系统,多线程是一种实现多任务的方式之一。在操作系统层面,进程指的是内存中运行的...
Java多线程编程是一个深奥且复杂的话题,它涉及到内存模型、线程调度、同步机制等多个层面的知识。Java通过提供丰富的API和并发工具库,使得开发者能够有效地利用多线程技术来构建高效的并发应用程序。对于Java...
Java 多线程编程基础知识 Java 多线程编程精要之基础 Java 教程是 Java 程序中运用多线程的基本教程,旨在...Java 多线程编程的优点包括简洁的编程模型、高效的计算资源利用率、简单的线程间通讯和等待问题的解决。