- 浏览: 307744 次
- 性别:
- 来自: 武汉
最新评论
-
r463579217:
给一下代码demo呀
Java多线程总结之聊一聊Queue -
水土第一:
LZ 中间文章里面可能有单词拼写错误,小弟补一下。。。
pa ...
Java多线程总结之由synchronized说开去 -
xy_z487:
>> 回调函数:A调用B,同时传A给B。B执行完会 ...
深入浅出Java回调机制 -
xuxiaoyinliu:
THANKS 第一次遇到这种错误,原来是这样
String的valueOf方法传入null -
sinat_25176913:
赞赞赞,一直还在想为什么得到的是一个"null&qu ...
String的valueOf方法传入null
更新完毕,结贴,以后有新的想法再开新帖
这几天不断添加新内容,给个大概的提纲吧,方面朋友们阅读,各部分是用分割线隔开了的:
最重要一条:
synchronized是针对对象的隐式锁使用的,注意是对象!
举个小例子,该例子没有任何业务含义,只是为了说明synchronized的基本用法:
好了,就这么简单。
myFunction()方法是个同步方法,隐式锁是谁的?答:是该方法所在类的对象。
看看怎么使用的:myClass.myFunction();很清楚了吧,隐式锁是myClass的。
说的在明白一点,线程想要执行myClass.myFunction();就要先获得myClass的锁。
下面总结一下:
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。(注:这个可以认为是对Class对象起作用)
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是this,即是当前对象。当然这个括号里可以是任何对象,synchronized对方法和块的含义和用法并不本质不同;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
synchronized可能造成死锁,比如:
假设场景:线程A调用methodOne(),获得lock1的隐式锁后,在获得lock2的隐式锁之前线程B进入运行,调用methodTwo(),抢先获得了lock2的隐式锁,此时线程A等着线程B交出lock2,线程B等着lock1进入方法块,死锁就这样被创造出来了。
下面举一个有业务含义的例子帮助理解,并展示一下synchronized与wait()、notifyAll()的使用。
这里先介绍一下这两个方法:
wait()/notify():调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
好了,再来看看synchronized与这两个方法之间的关系:
1.有synchronized的地方不一定有wait,notify
2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类,而是每一个对象都具有的方法(事实上,这两个方法是Object类里的),而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。
慢着,让我们思考一下Java这个设计是否合理?前面说了,锁是针对对象的,wait()/notify()的操作是与对象锁相关的,那么把wait()/notify()设计在Object中也就是合情合理的了。
恩,再想一下,为什么有wait,notify的地方必有synchronized?
synchronized方法中由当前线程占有锁。另一方面,调用wait()notify()方法的对象上的锁必须为当前线程所拥有。因此,wait()notify()方法调用必须放置在synchronized方法中,synchronized方法的上锁对象就是调用wait()notify()方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。
好了,以上准备知识充足了,现在说例子:银行转账,同一时刻只有一个人可以转账。
那么我们自然想到在Bank类中有一个同步的转账方法:
现在有一个问题,如果一个人获得了使用银行的锁,但是余额不足怎么办?
好,那我们进行改进:
这样就满足需求了。
可见,用对象锁来管理试图进入synchronized方法的线程,
另外,由条件判断来管理已经进入同步方法中的线程即当前线程
这里还补充两点:
1. 调用wait()方法前的判断最好用while,而不用if;因为while可以实现被唤醒后线程再次作条件判断;而if则只能判断一次
2. 用notifyAll()优先于notify()。
另外注意一点:
能调用wait()/notify()的只有当前线程,前提是必须获得了对象锁,就是说必须要进入到synchronized方法中。
-------------------------------------我是分割线----------------------------------------
补充一点JMM的相关知识,对理解线程同步很有好处。
(说明一下:以下内容参考了一些网上零零碎碎的帖子,非照搬且无商业目的,请勿跨省。)
JVM中(留神:马上讲到的这两个存储区只在JVM内部与物理存储区无关)存在一个主内存(Main Memory),Java中所有的变量存储在主内存中,所有实例和实例的字段都在此区域,对于所有的线程是共享的(相当于黑板,其他人都可以看到的)。每个线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中变量的拷贝,(相当于自己笔记本,只能自己看到),工作内存由缓存和堆栈组成,其中缓存保存的是主存中的变量的copy,堆栈保存的是线程局部变量。线程对所有变量的操作都是在工作内存中进行的,线程之间无法直接互相访问工作内存,变量的值得变化的传递需要主存来完成。在JMM中通过并发线程修改的变量值,必须通过线程变量同步到主存后,其他线程才能访问到。
看看这个图是不是更形象
好啦,下面来看线程对某个变量的操作步骤:
1.从主内存中复制数据到工作内存
2.执行代码,对数据进行各种操作和计算
3.把操作后的变量值重新写回主内存中
现在举个例子,设想两个棋手要通过两个终端显示器(Working Memory)对奕,而观众要通过服务器大屏幕(Main Memory )观看他们的比赛过程。这两个棋手相当于是同步中的线程,观众相当于其它线程。棋手是无法直接操作服务器的大屏幕的,他只能看到自己的终端显示器,只能先从服务器上将当前结果先复制到自己的终端上(步骤1),然后在自己的终端上操作(步骤2),将操作的结果记录在终端上,然后在某一时刻同步到服务器上(步骤3)。他所能看到的结果就是从服务器上复制到自己的终端上的内容,而要想把自己操作后的结果让其他人看到必须同步到服务器上才行。至于什么时候同步,那要看终端和服务器的通信机制。
回到这三个步骤,这个顺序是我们希望的,但是,JVM并不保证第1步和第3步会严格按照上述次序立即执行。因为根据java语言规范的规定,线程的工作内存和主存间的数据交换是松耦合的,什么时候需要刷新工作内存或者什么时候更新主存的内容,可以由具体的虚拟机实现自行决定。由于JVM可以对特征代码进行调优,也就改变了某些运行步骤的次序的颠倒,那么每次线程调用变量时是直接取自己的工作存储器中的值还是先从主存储器复制再取是没有保证的,任何一种情况都可能发生。同样的,线程改变变量的值之后,是否马上写回到主存储器上也是不可保证的,也许马上写,也许过一段时间再写。那么,在多线程的应用场景下就会出现问题了,多个线程同时访问同一个代码块,很有可能某个线程已经改变了某变量的值,当然现在的改变仅仅是局限于工作内存中的改变,此时JVM并不能保证将改变后的值立马写到主内存中去,也就意味着有可能其他线程不能立马得到改变后的值,依然在旧的变量上进行各种操作和运算,最终导致不可预料的结果。
这可如何是好呢?还好有synchronized和volatile:
1.多个线程共有的字段应该用synchronized或volatile来保护.
2.synchronized负责线程间的互斥.即同一时候只有一个线程可以执行synchronized中的代码.
synchronized还有另外一个方面的作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,不过此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!
3.volatile负责线程中的变量与主存储区同步.但不负责每个线程之间的同步.
volatile的含义是:线程在试图读取一个volatile变量时,会从主内存区中读取最新的值。现在很清楚了吧。
-------------------------------------我也是分割线----------------------------------------
说到synchronized,那就再来谈谈ThreadLocal。
在JDK的API文档中ThreadLocal的定义第一句道出:This class provides thread-local variables. 好,这个类提供了线程本地的变量。只看这一句,让我们结合到上面JMM的知识我们来分析一下理一下头绪:
我们已经知道了synchronized的含义是同步,也就是针对的是主存中的变量,只不过多线程执行时为了实现同步就需要每个线程在操作这个变量时要完成那三个步骤(对,就是主存与线程工作内存之间完成交互的那三步),我们很自然想到:
1. 使用目的:需要有某些变量在多个线程中共享,有共享才会需要同步。
2. 执行效率:直观上感觉一下,同步的执行效率肯定不高,事实上也的确是这样,为什么?看看那三步多麻烦。
好,现在再来看看ThreadLocal的定义,我们能想到什么?
首先让我们思考一个问题,并不是所有多线程程序都需要共享啊,这个时候还用同步那一套岂不是很多余?让每个线程维护自己的变量不就OK了,反正又不需要共享。对,ThreadLocal就是干这个事的。另一方面,那不用多说,性能上肯定优越喽。
小结一下:对比synchronized和ThreadLocal首先要清楚,两者的使用目的不同,关键点就在是否需要共享变量。就是说,ThreadLocal根本不是同步。再说啰嗦一点:ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别,synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。两者处于不同的问题域。这个都不清晰的话说再多都没用,只会更糊涂。
好了,上个例子看看ThreadLocal怎么用的(注:该例子来源于http://www.iteye.com/topic/81936这篇老帖子还是很值得看的):
ThreadLocal通过一个Map来为每个线程都持有一个变量副本,用ThreadLocal对象以键值对的方式来维护这些线程独立变量 。
-------------------------------呦这么巧,我也是分割线----------------------------------
既然说到了synchronized,顺便说说ReentrantLock吧。
ReentrantLock不熟悉?没事,concurrent包里的ArrayBlockingQueue知道吧,去看看源码,发现了吧,里面全是ReentrantLock。
好,言归正传,ReentrantLock是何方神圣?先这么说吧,你可以认为ReentrantLock是具有和synchronized类似功能的性能功能加强版同步锁。
让我们先来看看synchronized有什么缺点:
1. 只有一个condition与锁相关联,这个condition是什么?就是synchronized对针对的对象锁。
2. 多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。
针对synchronized的一系列缺点,JDK5提供了ReentrantLock,目的是为同步机制进行改善。下面来看看它是怎么改善上面这两个缺点的:
1. 一个ReentrantLock可以有多个Condition实例。
举个例子,还是刚才说的ArrayBlockingQueue类,看看源码(节选):
这里notEmpty和notFull作为lock的两个条件是可以分别负责管理想要加入元素的线程和想要取出元素的线程。例如put()方法在元素个数达到最大限制时会使用notFull条件把试图继续插入元素的线程都扔到等待集中,而执行了take()方法时如果顺利进入extract()则会空出空间,这时notFull负责随机的通知被其扔到等待集中的线程执行插入元素的操作。(这里没给出put方法,有兴趣的童鞋可以去查查源码,其实和take方法很类似)
2. ReentrantLock提供了lockInterruptibly()方法可以优先考虑响应中断,而不是像synchronized那样不响应interrupt()操作。
解释一下响应中断是什么意思:比如A、B两线程去竞争锁,A得到了锁,B等待,但是A有很多事情要处理,所以一直不返回。B可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。在这种情况下,synchronized的做法是,B线程中断自己(或者别的线程中断它),我不去响应,继续让B线程等待,你再怎么中断,我全当耳边风。而lockInterruptibly()的做法是,B线程中断自己(或者别的线程中断它),ReentrantLock响应这个中断,不再让B等待这个锁的到来。
有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。
由于ReentrantLock在提供了多样的同步功能(除了可响应中断,还能设置时间限制),因此在同步比较激烈的情况下,性能比synchronized大大提高。
不过,在同步竞争不激烈的情况下,synchronized还是非常合适的(因为JVM会进行优化,具体不清楚怎么优化的)。因此不能说ReentrantLock一定更好,只是两者适合情况不同而已,在同步竞争不激烈时用synchronized,激烈时用ReentrantLock。换句话说,ReentrantLock的可伸缩性可并发性要更好一些。除非您对 ReentrantLock的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用 synchronized。(这里推荐一个帖子http://www.ibm.com/developerworks/cn/java/j-jtp10264/)
再补充一点,使用ReentrantLock时,切记要在finally中释放锁,这是与synchronized使用方式很大的一个不同。对于synchronized,JVM会自动释放锁,而ReentrantLock需要你自己来处理。给个代码片段吧:
推荐一个帖子,讲ReentrantLock还不错我参考了的http://yanxuxin.iteye.com/blog/566713
更新完毕,结贴,以后有新的想法再开新帖
还没有安卓开发的经验,抱歉
多谢提醒
这方面还真没怎么深入研究过,多谢提醒,正好补补这部分知识
恩 你说的更准确一些
互相学习
恩 是的 块加锁也是针对对象 本文里this只是一个比较特殊的对象
这几天不断添加新内容,给个大概的提纲吧,方面朋友们阅读,各部分是用分割线隔开了的:
- synchronized与wait()/notify()
- JMM与synchronized
- ThreadLocal与synchronized
- ReentrantLock与synchronized
最重要一条:
synchronized是针对对象的隐式锁使用的,注意是对象!
举个小例子,该例子没有任何业务含义,只是为了说明synchronized的基本用法:
Class MyClass(){ synchronized void myFunction(){ //do something } } public static void main(){ MyClass myClass = new MyClass(); myClass.myFunction(); }
好了,就这么简单。
myFunction()方法是个同步方法,隐式锁是谁的?答:是该方法所在类的对象。
看看怎么使用的:myClass.myFunction();很清楚了吧,隐式锁是myClass的。
说的在明白一点,线程想要执行myClass.myFunction();就要先获得myClass的锁。
下面总结一下:
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。(注:这个可以认为是对Class对象起作用)
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是this,即是当前对象。当然这个括号里可以是任何对象,synchronized对方法和块的含义和用法并不本质不同;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
synchronized可能造成死锁,比如:
class DeadLockSample{ public final Object lock1 = new Object(); public final Object lock2 = new Object(); public void methodOne(){ synchronized(lock1){ ... synchronized(lock2){...} } } public void methodTwo(){ synchronized(lock2){ ... synchronized(lock1){...} } } }
假设场景:线程A调用methodOne(),获得lock1的隐式锁后,在获得lock2的隐式锁之前线程B进入运行,调用methodTwo(),抢先获得了lock2的隐式锁,此时线程A等着线程B交出lock2,线程B等着lock1进入方法块,死锁就这样被创造出来了。
下面举一个有业务含义的例子帮助理解,并展示一下synchronized与wait()、notifyAll()的使用。
这里先介绍一下这两个方法:
wait()/notify():调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
好了,再来看看synchronized与这两个方法之间的关系:
1.有synchronized的地方不一定有wait,notify
2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类,而是每一个对象都具有的方法(事实上,这两个方法是Object类里的),而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。
慢着,让我们思考一下Java这个设计是否合理?前面说了,锁是针对对象的,wait()/notify()的操作是与对象锁相关的,那么把wait()/notify()设计在Object中也就是合情合理的了。
恩,再想一下,为什么有wait,notify的地方必有synchronized?
synchronized方法中由当前线程占有锁。另一方面,调用wait()notify()方法的对象上的锁必须为当前线程所拥有。因此,wait()notify()方法调用必须放置在synchronized方法中,synchronized方法的上锁对象就是调用wait()notify()方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。
好了,以上准备知识充足了,现在说例子:银行转账,同一时刻只有一个人可以转账。
那么我们自然想到在Bank类中有一个同步的转账方法:
public Class Bank(){ float account[ACCOUNT_NUM]; ... public synchronized void transfer(from, to, amount){ //转账 } }
现在有一个问题,如果一个人获得了使用银行的锁,但是余额不足怎么办?
好,那我们进行改进:
public Class Bank(){ float account[ACCOUNT_NUM]; ... public synchronized void transfer(int from, int to, float amount){ while(account[from]){ wait(); } account[from] -= amount; account[to] += amount; notifyAll(); } }
这样就满足需求了。
可见,用对象锁来管理试图进入synchronized方法的线程,
另外,由条件判断来管理已经进入同步方法中的线程即当前线程
这里还补充两点:
1. 调用wait()方法前的判断最好用while,而不用if;因为while可以实现被唤醒后线程再次作条件判断;而if则只能判断一次
2. 用notifyAll()优先于notify()。
另外注意一点:
能调用wait()/notify()的只有当前线程,前提是必须获得了对象锁,就是说必须要进入到synchronized方法中。
-------------------------------------我是分割线----------------------------------------
补充一点JMM的相关知识,对理解线程同步很有好处。
(说明一下:以下内容参考了一些网上零零碎碎的帖子,非照搬且无商业目的,请勿跨省。)
JVM中(留神:马上讲到的这两个存储区只在JVM内部与物理存储区无关)存在一个主内存(Main Memory),Java中所有的变量存储在主内存中,所有实例和实例的字段都在此区域,对于所有的线程是共享的(相当于黑板,其他人都可以看到的)。每个线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中变量的拷贝,(相当于自己笔记本,只能自己看到),工作内存由缓存和堆栈组成,其中缓存保存的是主存中的变量的copy,堆栈保存的是线程局部变量。线程对所有变量的操作都是在工作内存中进行的,线程之间无法直接互相访问工作内存,变量的值得变化的传递需要主存来完成。在JMM中通过并发线程修改的变量值,必须通过线程变量同步到主存后,其他线程才能访问到。
看看这个图是不是更形象
好啦,下面来看线程对某个变量的操作步骤:
1.从主内存中复制数据到工作内存
2.执行代码,对数据进行各种操作和计算
3.把操作后的变量值重新写回主内存中
现在举个例子,设想两个棋手要通过两个终端显示器(Working Memory)对奕,而观众要通过服务器大屏幕(Main Memory )观看他们的比赛过程。这两个棋手相当于是同步中的线程,观众相当于其它线程。棋手是无法直接操作服务器的大屏幕的,他只能看到自己的终端显示器,只能先从服务器上将当前结果先复制到自己的终端上(步骤1),然后在自己的终端上操作(步骤2),将操作的结果记录在终端上,然后在某一时刻同步到服务器上(步骤3)。他所能看到的结果就是从服务器上复制到自己的终端上的内容,而要想把自己操作后的结果让其他人看到必须同步到服务器上才行。至于什么时候同步,那要看终端和服务器的通信机制。
回到这三个步骤,这个顺序是我们希望的,但是,JVM并不保证第1步和第3步会严格按照上述次序立即执行。因为根据java语言规范的规定,线程的工作内存和主存间的数据交换是松耦合的,什么时候需要刷新工作内存或者什么时候更新主存的内容,可以由具体的虚拟机实现自行决定。由于JVM可以对特征代码进行调优,也就改变了某些运行步骤的次序的颠倒,那么每次线程调用变量时是直接取自己的工作存储器中的值还是先从主存储器复制再取是没有保证的,任何一种情况都可能发生。同样的,线程改变变量的值之后,是否马上写回到主存储器上也是不可保证的,也许马上写,也许过一段时间再写。那么,在多线程的应用场景下就会出现问题了,多个线程同时访问同一个代码块,很有可能某个线程已经改变了某变量的值,当然现在的改变仅仅是局限于工作内存中的改变,此时JVM并不能保证将改变后的值立马写到主内存中去,也就意味着有可能其他线程不能立马得到改变后的值,依然在旧的变量上进行各种操作和运算,最终导致不可预料的结果。
这可如何是好呢?还好有synchronized和volatile:
1.多个线程共有的字段应该用synchronized或volatile来保护.
2.synchronized负责线程间的互斥.即同一时候只有一个线程可以执行synchronized中的代码.
synchronized还有另外一个方面的作用:在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,不过此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!
3.volatile负责线程中的变量与主存储区同步.但不负责每个线程之间的同步.
volatile的含义是:线程在试图读取一个volatile变量时,会从主内存区中读取最新的值。现在很清楚了吧。
-------------------------------------我也是分割线----------------------------------------
说到synchronized,那就再来谈谈ThreadLocal。
在JDK的API文档中ThreadLocal的定义第一句道出:This class provides thread-local variables. 好,这个类提供了线程本地的变量。只看这一句,让我们结合到上面JMM的知识我们来分析一下理一下头绪:
我们已经知道了synchronized的含义是同步,也就是针对的是主存中的变量,只不过多线程执行时为了实现同步就需要每个线程在操作这个变量时要完成那三个步骤(对,就是主存与线程工作内存之间完成交互的那三步),我们很自然想到:
1. 使用目的:需要有某些变量在多个线程中共享,有共享才会需要同步。
2. 执行效率:直观上感觉一下,同步的执行效率肯定不高,事实上也的确是这样,为什么?看看那三步多麻烦。
好,现在再来看看ThreadLocal的定义,我们能想到什么?
首先让我们思考一个问题,并不是所有多线程程序都需要共享啊,这个时候还用同步那一套岂不是很多余?让每个线程维护自己的变量不就OK了,反正又不需要共享。对,ThreadLocal就是干这个事的。另一方面,那不用多说,性能上肯定优越喽。
小结一下:对比synchronized和ThreadLocal首先要清楚,两者的使用目的不同,关键点就在是否需要共享变量。就是说,ThreadLocal根本不是同步。再说啰嗦一点:ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别,synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。两者处于不同的问题域。这个都不清晰的话说再多都没用,只会更糊涂。
好了,上个例子看看ThreadLocal怎么用的(注:该例子来源于http://www.iteye.com/topic/81936这篇老帖子还是很值得看的):
public class ThreadLocalDemo implements Runnable { private final static ThreadLocal studentLocal = new ThreadLocal(); //ThreadLocal对象在这 public static void main(String[] agrs) { TreadLocalDemo td = new TreadLocalDemo(); Thread t1 = new Thread(td,"a"); Thread t2 = new Thread(td,"b"); t1.start(); t2.start(); } public void run() { accessStudent(); } public void accessStudent() { String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName+" is running!"); Random random = new Random(); int age = random.nextInt(100); System.out.println("thread "+currentThreadName +" set age to:"+age); Student student = getStudent(); //每个线程都独立维护一个Student变量 student.setAge(age); System.out.println("thread "+currentThreadName+" first read age is:"+student.getAge()); try { Thread.sleep(5000); } catch(InterruptedException ex) { ex.printStackTrace(); } System.out.println("thread "+currentThreadName +" second read age is:"+student.getAge()); } protected Student getStudent() { Student student = (Student)studentLocal.get(); //从ThreadLocal对象中取 if(student == null) { student = new Student(); studentLocal.set(student); //如果没有就创建一个 } return student; } protected void setStudent(Student student) { studentLocal.set(student); //放入ThreadLocal对象中 } }
ThreadLocal通过一个Map来为每个线程都持有一个变量副本,用ThreadLocal对象以键值对的方式来维护这些线程独立变量 。
-------------------------------呦这么巧,我也是分割线----------------------------------
既然说到了synchronized,顺便说说ReentrantLock吧。
ReentrantLock不熟悉?没事,concurrent包里的ArrayBlockingQueue知道吧,去看看源码,发现了吧,里面全是ReentrantLock。
好,言归正传,ReentrantLock是何方神圣?先这么说吧,你可以认为ReentrantLock是具有和synchronized类似功能的性能功能加强版同步锁。
让我们先来看看synchronized有什么缺点:
1. 只有一个condition与锁相关联,这个condition是什么?就是synchronized对针对的对象锁。
2. 多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。这种情况对于大量的竞争线程会造成性能的下降等后果。
针对synchronized的一系列缺点,JDK5提供了ReentrantLock,目的是为同步机制进行改善。下面来看看它是怎么改善上面这两个缺点的:
1. 一个ReentrantLock可以有多个Condition实例。
举个例子,还是刚才说的ArrayBlockingQueue类,看看源码(节选):
public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable { ... private final ReentrantLock lock; private final Condition notEmpty; private final Condition notFull; ... public ArrayBlockingQueue(int capacity, boolean fair) { ... lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); //为该ReentrantLock设置了两个Condition } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { try { while (count == 0) notEmpty.await(); //这里针对notEmpty这个condition,如果队列为空则线程等待这个条件 } catch (InterruptedException ie) { notEmpty.signal(); throw ie; } E x = extract(); return x; } finally { lock.unlock(); } } private E extract() { final E[] items = this.items; E x = items[takeIndex]; items[takeIndex] = null; takeIndex = inc(takeIndex); --count; notFull.signal(); //这里针对notFull这个condition,唤醒因该条件而等待的线程 return x; } ... }
这里notEmpty和notFull作为lock的两个条件是可以分别负责管理想要加入元素的线程和想要取出元素的线程。例如put()方法在元素个数达到最大限制时会使用notFull条件把试图继续插入元素的线程都扔到等待集中,而执行了take()方法时如果顺利进入extract()则会空出空间,这时notFull负责随机的通知被其扔到等待集中的线程执行插入元素的操作。(这里没给出put方法,有兴趣的童鞋可以去查查源码,其实和take方法很类似)
2. ReentrantLock提供了lockInterruptibly()方法可以优先考虑响应中断,而不是像synchronized那样不响应interrupt()操作。
解释一下响应中断是什么意思:比如A、B两线程去竞争锁,A得到了锁,B等待,但是A有很多事情要处理,所以一直不返回。B可能就会等不及了,想中断自己,不再等待这个锁了,转而处理其他事情。在这种情况下,synchronized的做法是,B线程中断自己(或者别的线程中断它),我不去响应,继续让B线程等待,你再怎么中断,我全当耳边风。而lockInterruptibly()的做法是,B线程中断自己(或者别的线程中断它),ReentrantLock响应这个中断,不再让B等待这个锁的到来。
有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。
由于ReentrantLock在提供了多样的同步功能(除了可响应中断,还能设置时间限制),因此在同步比较激烈的情况下,性能比synchronized大大提高。
不过,在同步竞争不激烈的情况下,synchronized还是非常合适的(因为JVM会进行优化,具体不清楚怎么优化的)。因此不能说ReentrantLock一定更好,只是两者适合情况不同而已,在同步竞争不激烈时用synchronized,激烈时用ReentrantLock。换句话说,ReentrantLock的可伸缩性可并发性要更好一些。除非您对 ReentrantLock的某个高级特性有明确的需要,或者有明确的证据(而不是仅仅是怀疑)表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用 synchronized。(这里推荐一个帖子http://www.ibm.com/developerworks/cn/java/j-jtp10264/)
再补充一点,使用ReentrantLock时,切记要在finally中释放锁,这是与synchronized使用方式很大的一个不同。对于synchronized,JVM会自动释放锁,而ReentrantLock需要你自己来处理。给个代码片段吧:
//synchronized public synchronized void increment() { count++; }
//ReentrantLock public void increment() { lock.lockInterruptibly();//上锁 try { count++; } finally { lock.unlock();//手动释放锁 } }
推荐一个帖子,讲ReentrantLock还不错我参考了的http://yanxuxin.iteye.com/blog/566713
更新完毕,结贴,以后有新的想法再开新帖
评论
15 楼
水土第一
2016-09-26
LZ 中间文章里面可能有单词拼写错误,小弟补一下。。。
package thread; import java.util.Random; /** * Author: Rison.Gao * TIME: 2016-09-26 17:26 * Version: 1.0 */ public class ThreadLocalDemo implements Runnable { private final static ThreadLocal studentLocal = new ThreadLocal(); //ThreadLocal对象在这 public ThreadLocalDemo() { } public static void main(String[] args) { ThreadLocalDemo td = new ThreadLocalDemo(); Thread t1 = new Thread(td, "a"); Thread t2 = new Thread(td, "b"); t1.start(); t2.start(); } public void run() { accessStudent(); } public void accessStudent() { String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " is running!"); Random random = new Random(); int age = random.nextInt(100); System.out.println("thread " + currentThreadName + " set age to:" + age); Student student = getStudent(); //每个线程都独立维护一个Student变量 student.setAge(age); System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge()); try { Thread.sleep(5000); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge()); } protected Student getStudent() { Student student = (Student) studentLocal.get(); //从ThreadLocal对象中取 if (student == null) { student = new Student(); studentLocal.set(student); //如果没有就创建一个 } return student; } protected void setStudent(Student student) { studentLocal.set(student); //放入ThreadLocal对象中 } } class Student { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
14 楼
yjheeq
2011-07-22
有一点疑问,Threadlocal既然是用来隔离变量,那为什么不在每一个线程都new一个新的对象出来呢?这样,从根本上就是两个不同的对象了,也不会产生数据混乱啊。
通过你的文章,我的理解了threadlocal是什么东西,但不知道在什么情况下用
通过你的文章,我的理解了threadlocal是什么东西,但不知道在什么情况下用
13 楼
java中的菜鸟
2011-07-13
唉!技术差就是这样...我看了没心思看下去...一片不懂
12 楼
ghostdo
2011-07-13
[b][/b][i][/i][u][/u]
引用
11 楼
HelloSure
2011-07-12
天边飞 写道
楼主您好,刚从java转anroid,楼主高手可以帮着解决一个问题吗,谢谢!:http://doumiw.com/market/community/t!showTopic.do?topicId=24
还没有安卓开发的经验,抱歉
10 楼
HelloSure
2011-07-12
nanxi2009 写道
嗯,还有个概念就是ThreadLocal,可以解决线程级的变量副本。
多谢提醒
9 楼
nanxi2009
2011-07-11
嗯,还有个概念就是ThreadLocal,可以解决线程级的变量副本。
8 楼
HelloSure
2011-07-11
Technoboy 写道
既然介绍了synchronized,就有必要介绍synchronized关键字与JMM的关系。
这方面还真没怎么深入研究过,多谢提醒,正好补补这部分知识
7 楼
HelloSure
2011-07-11
jzinfo 写道
“是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 ”
更确切的说是针对该类的Class对象进行锁。
更确切的说是针对该类的Class对象进行锁。
恩 你说的更准确一些
6 楼
HelloSure
2011-07-11
a545807638 写道
学习了下
互相学习
5 楼
HelloSure
2011-07-11
windlike 写道
synchronized块的加锁目标不一定非是this
恩 是的 块加锁也是针对对象 本文里this只是一个比较特殊的对象
4 楼
Technoboy
2011-07-11
既然介绍了synchronized,就有必要介绍synchronized关键字与JMM的关系。
3 楼
jzinfo
2011-07-11
“是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 ”
更确切的说是针对该类的Class对象进行锁。
更确切的说是针对该类的Class对象进行锁。
2 楼
a545807638
2011-07-11
学习了下
1 楼
windlike
2011-07-10
synchronized块的加锁目标不一定非是this
发表评论
-
关于继承的例子
2011-11-19 15:13 1227继承是再普通不过的概念,但是你真的能玩的转吗? 父类Perso ... -
Object类分析equals、hashcode、clone
2011-11-17 21:57 1845Object类中的equals: publ ... -
成员变量的初始化
2011-11-16 16:15 1068Java会对成员变量进行自动初始化,并且在构造方法执行之前完成 ... -
关于java多线程的一篇不错的入门级文章
2011-11-14 22:42 1933虽然都是老生常谈,而且入门级,但是讲的很不错。 一、理解多线 ... -
多线程常用方法比较汇总
2011-11-15 23:07 1593从操作系统的角度讲,o ... -
多线程例子:yield
2011-11-14 20:59 1594public class Test { public ... -
String的valueOf方法传入null
2011-11-12 20:42 13789这个问题很有意思 Object obj = null; ... -
多线程例子:join
2011-11-09 23:06 1361package sure; import java. ... -
多线程例子:wait与notify、sleep
2011-11-09 22:15 3331package sure; import org.s ... -
JAVA编程经验汇总
2011-11-04 21:04 1236都是一些小的点,不完 ... -
以ConcurrentHashMap为例小议并发集合类
2011-08-09 22:15 5093为了引出并发集合类ConcurrentHashMap,有必要先 ... -
聊一下Java代理那点事
2011-08-06 20:08 2008代理模式 代理模式的作用是:为其他对象提供一种代理以控制对 ... -
说说volatile关键字
2011-08-05 16:29 2340Java语言规范中指出:为 ... -
小议时序调度Timer和Quartz
2011-07-28 21:15 7214本文不是用来讲授入门手把手ABC小例子的,算是自己这段时间对T ... -
关于Java包装类装箱拆箱的小例子
2011-07-27 09:50 1464简单来说:装箱就是把值类型转变为引用类型,拆箱就是把引用类型转 ... -
深入浅出Java回调机制
2011-07-21 21:24 83306前几天看了一下Spring的部分源码,发现回调机制被大量使用, ... -
Java多线程总结之聊一聊Queue
2011-07-17 23:13 36852上个星期总结了一下synchronized相关的知识,这次将Q ... -
关于递归
2011-06-18 21:27 109算法中有调用自身,则是递归 递归算法必须是逐步有规律简化的, ... -
java的内部类与匿名类
2011-06-18 13:19 1806提起Java内部类(Inner Class)可能很多人不太熟悉 ... -
Java线程同步机制synchronized关键字的理解
2011-06-18 08:49 37由于同一进程的多个线 ...
相关推荐
java多线程中synchronized关键字的用法 解压密码 www.jiangyea.com
Java线程:概念与原理 2 一、操作系统中线程和进程的概念 2 二、Java中的线程 3 三、Java中关于线程的名词解释 3 四、线程的状态转换和生命周期 4 Java线程:创建与启动 7 Java线程:线程名称的设定及获取 10 Java...
一个多线程访问的同一个资源,java synchronized互斥锁的用法,android和此用法一致。
2. Java多线程学习(二)synchronized关键字(1) 3. Java多线程学习(二)synchronized关键字(2) 4. Java多线程学习(三)volatile关键字 5. Java多线程学习(四)等待/通知(wait/notify)机制 6. Java多...
你还在用synchronized?线程安全相关知识深入剖析
单线程程序 多线程程序 Thread类的run方法和start方法 线程的启动 线程的启动(1)——利用Thread类的子类 线程的启动(2)——利用Runnable接口 线程的暂时停止 线程的共享互斥 synchronized方法 synchronized阻挡 ...
一个简单的多线程代码示例,Java实现,用于实现同一时刻,只允许一个线程调用执行的代码块或类,即synchronized的如何使用(多线程实现),实现 Runnable
Java线程及同步(synchronized)样例代码
Java多线程synchronized关键字详解(六)共5页.pdf.zip
包括java的Thread类,同步块(synchronized),可重入锁,Object方法以及对象监视器等内容。
Java多线程(Synchronized+Volatile+JUC 并发工具原理+线程状态+CAS+线程池)
1.讲解了Java多线程的基础, 包括Thread类的核心API的使用。2.讲解了在多线程中对并发访问的控制, 主要就是synchronized的使用, 由于此关键字在使用上非常灵活, 所以书中用了很多案例来介绍此关键字的使用, 为...
多线程注意:wait()方法的调用要有判定条件常用 while () obj.wait(timeout, nanos); ... // Perform action appropriate to condition } synchronized会影响共享数据,但对其他语句的执行不会有规律了!
JAVA中要想解决“脏数据”的问题,最简单的方法就是使用synchronized关键字来使run方法同步,看下面的代码,只要在void和public之间加上synchronized关键字
主要介绍了java多线程之线程,进程和Synchronized概念初解,涉及进程与线程的简单概念,实现多线程的方式,线程安全问题,synchronized修饰符等相关内容,具有一定借鉴价值,需要的朋友可以参考下。
本压缩包,总共包含两个文档,JAVA多线程编程详解-详细操作例子和 Java多线 程编程总结 例如,runnable、thread、stop()、 suspend、yield、setPriority()、getPriority()、synchronized、wait()、join、线程池同步...
java锁机制Synchronized java锁机制Synchronized java锁机制Synchronized java锁机制Synchronized
java的线程同步机制synchronized关键字的理解_.docx
本篇文章提供了20道高难度的Java多线程编程面试题及详细解析,旨在帮助开发者展示出卓越的并发编程能力。在当今高并发的应用场景下,对多线程编程的理解和应用是评估面试者的重要指标。通过这些高难度问题,您将全面...