java线程池同步

482,381 一月 独立访问用户
您目前处于:
Java深度历险(三)——Java线程:基本概念、可见性与同步
Java深度历险(三)——Java线程:基本概念、可见性与同步
相关厂商内容
相关赞助商
全球软件开发大会,4月23-25日,北京,!
一个线程被创建成功并启动之后,可以处在不同的状态中。这个线程可能正在占用CPU时间运行;也可能处在就绪状态,等待被调度执行;还可能阻塞在某个资源或是事件上。多个就绪状态的线程会竞争CPU时间以获得被执行的机会,而CPU则采用某种算法来调度线程的执行。不同线程的运行顺序是不确定的,多线程程序中的逻辑不能依赖于CPU的调度算法。
可见性(visibility)的问题是Java多线程应用中的错误的根源。在一个单线程程序中,如果首先改变一个变量的值,再读取该变量的值的时候,所读取到的值就是上次写操作写入的值。也就是说前面操作的结果对后面的操作是肯定可见的。但是在多线程程序中,如果不使用一定的同步机制,就不能保证一个线程所写入的值对另外一个线程是可见的。造成这种情况的原因可能有下面几个:
CPU 内部的缓存:现在的CPU一般都拥有层次结构的几级缓存。CPU直接操作的是缓存中的数据,并在需要的时候把缓存中的数据与主存进行同步。因此在某些时刻,缓存中的数据与主存内的数据可能是不一致的。某个线程所执行的写入操作的新值可能当前还保存在CPU的缓存中,还没有被写回到主存中。这个时候,另外一个线程的读取操作读取的就还是主存中的旧值。
CPU的指令执行顺序:在某些时候,CPU可能改变指令的执行顺序。这有可能导致一个线程过早的看到另外一个线程的写入操作完成之后的新值。
编译器代码重排:出于性能优化的目的,编译器可能在编译的时候对生成的目标代码进行重新排列。
现实的情况是:不同的CPU可能采用不同的架构,而这样的问题在多核处理器和多处理器系统中变得尤其复杂。而Java的目标是要实现&编写一次,到处运行&,因此就有必要对Java程序访问和操作主存的方式做出规范,以保证同样的程序在不同的CPU架构上的运行结果是一致的。Java内存模型()就是为了这个目的而引入的。则进一步修正了之前的内存模型中存在的问题。总得来说,Java内存模型描述了程序中共享变量的关系以及在主存中写入和读取这些变量值的底层细节。Java内存模型定义了Java语言中的、和等关键词对主存中变量读写操作的意义。Java开发人员使用这些关键词来描述程序所期望的行为,而编译器和JVM负责保证生成的代码在运行时刻的行为符合内存模型的描述。比如对声明为volatile的变量来说,在读取之前,JVM会确保CPU中缓存的值首先会失效,重新从主存中进行读取;而写入之后,新的值会被马上写入到主存中。而synchronized和volatile关键词也会对编译器优化时候的代码重排带来额外的限制。比如编译器不能把 synchronized块中的代码移出来。对volatile变量的读写操作是不能与其它读写操作一块重新排列的。
Java 内存模型中一个重要的概念是定义了&在之前发生(happens-before)&的顺序。如果一个动作按照&在之前发生&的顺序发生在另外一个动作之前,那么前一个动作的结果在多线程的情况下对于后一个动作就是肯定可见的。最常见的&在之前发生&的顺序包括:对一个对象上的监视器的解锁操作肯定发生在下一个对同一个监视器的加锁操作之前;对声明为volatile的变量的写操作肯定发生在后续的读操作之前。有了&在之前发生&顺序,多线程程序在运行时刻的行为在关键部分上就是可预测的了。编译器和JVM会确保&在之前发生&顺序可以得到保证。比如下面的一个简单的方法:
public void increase() {
this.count++;
这是一个常见的计数器递增方法,this.count++实际是this.count = this.count + 1,由一个对变量this.count的读取操作和写入操作组成。如果在多线程情况下,两个线程执行这两个操作的顺序是不可预期的。如果 this.count的初始值是1,两个线程可能都读到了为1的值,然后先后把this.count的值设为2,从而产生错误。错误的原因在于其中一个线程对this.count的写入操作对另外一个线程是不可见的,另外一个线程不知道this.count的值已经发生了变化。如果在increase() 方法声明中加上synchronized关键词,那就在两个线程的操作之间强制定义了一个&在之前发生&顺序。一个线程需要首先获得当前对象上的锁才能执行,在它拥有锁的这段时间完成对this.count的写入操作。而另一个线程只有在当前线程释放了锁之后才能执行。这样的话,就保证了两个线程对 increase()方法的调用只能依次完成,保证了线程之间操作上的可见性。
如果一个变量的值可能被多个线程读取,又能被最少一个线程锁写入,同时这些读写操作之间并没有定义好的&在之前发生&的顺序的话,那么在这个变量上就存在数据竞争(data race)。数据竞争的存在是Java多线程应用中要解决的首要问题。解决的办法就是通过synchronized和volatile关键词来定义好&在之前发生&顺序。
Java中的锁
当数据竞争存在的时候,最简单的解决办法就是加锁。锁机制限制在同一时间只允许一个线程访问产生竞争的数据的临界区。Java语言中的 synchronized关键字可以为一个代码块或是方法进行加锁。任何Java对象都有一个自己的监视器,可以进行加锁和解锁操作。当受到 synchronized关键字保护的代码块或方法被执行的时候,就说明当前线程已经成功的获取了对象的监视器上的锁。当代码块或是方法正常执行完成或是发生异常退出的时候,当前线程所获取的锁会被自动释放。一个线程可以在一个Java对象上加多次锁。同时JVM保证了在获取锁之前和释放锁之后,变量的值是与主存中的内容同步的。
Java线程的同步
在有些情况下,仅依靠线程之间对数据的互斥访问是不够的。有些线程之间存在协作关系,需要按照一定的协议来协同完成某项任务,比如典型的生产者-消费者模式。这种情况下就需要用到Java提供的线程之间的等待-通知机制。当线程所要求的条件不满足时,就进入等待状态;而另外的线程则负责在合适的时机发出通知来唤醒等待中的线程。Java中的java.lang.Object类中的//方法组就是完成线程之间的同步的。
在某个Java对象上面调用wait方法的时候,首先要检查当前线程是否获取到了这个对象上的锁。如果没有的话,就会直接抛出异常。如果有锁的话,就把当前线程添加到对象的等待集合中,并释放其所拥有的锁。当前线程被阻塞,无法继续执行,直到被从对象的等待集合中移除。引起某个线程从对象的等待集合中移除的原因有很多:对象上的notify方法被调用时,该线程被选中;对象上的notifyAll方法被调用;线程被中断;对于有超时限制的wait操作,当超过时间限制时;JVM内部实现在非正常情况下的操作。
从上面的说明中,可以得到几条结论:wait/notify/notifyAll操作需要放在synchronized代码块或方法中,这样才能保证在执行 wait/notify/notifyAll的时候,当前线程已经获得了所需要的锁。当对于某个对象的等待集合中的线程数目没有把握的时候,最好使用 notifyAll而不是notify。notifyAll虽然会导致线程在没有必要的情况下被唤醒而产生性能影响,但是在使用上更加简单一些。由于线程可能在非正常情况下被意外唤醒,一般需要把wait操作放在一个循环中,并检查所要求的逻辑条件是否满足。典型的使用模式如下所示:
private Object lock = new Object();
synchronized (lock) {
while (/* 逻辑条件不满足的时候 */) {
lock.wait();
} catch (InterruptedException e) {}
//处理逻辑
上述代码中使用了一个私有对象lock来作为加锁的对象,其好处是可以避免其它代码错误的使用这个对象。
通过一个线程对象的方法可以向该线程发出一个中断请求。中断请求是一种线程之间的协作方式。当线程A通过调用线程B的interrupt()方法来发出中断请求的时候,线程A 是在请求线程B的注意。线程B应该在方便的时候来处理这个中断请求,当然这不是必须的。当中断发生的时候,线程对象中会有一个标记来记录当前的中断状态。通过方法可以判断是否有中断请求发生。如果当中断请求发生的时候,线程正处于阻塞状态,那么这个中断请求会导致该线程退出阻塞状态。可能造成线程处于阻塞状态的情况有:当线程通过调用wait()方法进入一个对象的等待集合中,或是通过方法来暂时休眠,或是通过方法来等待另外一个线程完成的时候。在线程阻塞的情况下,当中断发生的时候,会抛出,代码会进入相应的异常处理逻辑之中。实际上在调用wait/sleep/join方法的时候,是必须捕获这个异常的。中断一个正在某个对象的等待集合中的线程,会使得这个线程从等待集合中被移除,使得它可以在再次获得锁之后,继续执行java.lang.InterruptedException异常的处理逻辑。
通过中断线程可以实现可取消的任务。在任务的执行过程中可以定期检查当前线程的中断标记,如果线程收到了中断请求,那么就可以终止这个任务的执行。当遇到 java.lang.InterruptedException的异常,不要捕获了之后不做任何处理。如果不想在这个层次上处理这个异常,就把异常重新抛出。当一个在阻塞状态的线程被中断并且抛出java.lang.InterruptedException异常的时候,其对象中的中断状态标记会被清空。如果捕获了java.lang.InterruptedException异常但是又不能重新抛出的话,需要通过再次调用interrupt()方法来重新设置这个标记。
Java语言规范(第三版)第17章:
Fixing the Java Memory Model,
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家加入到中与我们的编辑和其他读者朋友交流。
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
谈不上深入,更不是历险,只不过是一个简介而已
Re: 谈不上深入,更不是历险,只不过是一个简介而已
jiaheng peng
Re: 谈不上深入,更不是历险,只不过是一个简介而已
++线程安全
我觉得还行
xie pengfei
Re: ++线程安全
Re: ++线程安全
zhang kaitao
初学者一定会受益
早几年都可以没事取个这样的名字,这两年确认水深的人多了,名字也不好取了
Zeng Abrams
温故而知新
看看还行,但真不是深入历险
Xie Chaohong
本文标题写的很清楚了,大家为何还这么多质疑
Zhang Gavin
Re: 本文标题写的很清楚了,大家为何还这么多质疑
Java线程:基本概念、可见性与同步
Re: ++线程安全
Re: ++线程安全
Re: ++线程安全
Jiang Hatter
Re: ++线程安全
Jiang Hatter
初级程序员都能看出来是浅谈
Berg Super
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Ralph Winzinger
赞助商链接
InfoQ每周精要
通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7Java线程同步与信号量的奥秘_Linux编程_Linux公社-Linux系统门户网站
你好,游客
Java线程同步与信号量的奥秘
来源:Linux社区&
作者:zhangzhaokun
有关Java的线程锁与entry set和wait set的关系,抑或是说这个图包含了Java线程的奥秘,不懂这个图或者是没有见过这个图的话,只能说明对于Java的线程锁还是一知半解的。
(1)所有期待获得锁的线程,在锁已经被其它线程拥有的时候,这些期待获得锁的线程就进入了Object Lock的entry set区域。
(2)所有曾经获得过锁,但是由于其它必要条件不满足而需要wait的时候,线程就进入了Object Lock的wait set区域 。
(3)在wait set区域的线程获得Notify/notifyAll通知的时候,随机的一个Thread(Notify)或者是全部的Thread(NotifyALL)从Object Lock的wait set区域进入了entry set中。
(4)在当前拥有锁的线程释放掉锁的时候,处于该Object Lock的entryset区域的线程都会抢占该锁,但是只能有任意的一个Thread能取得该锁,而其他线程依然在entry set中等待下次来抢占到锁之后再执行。
相关资讯 & & &
& (07/09/:16)
& (11/08/:29)
& (07/20/:41)
& (03/07/:22)
& (07/21/:50)
& (06/26/:17)
图片资讯 & & &
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款package&com.class&MyThread&implements&java.lang.Runnable{&&&&private&int&threadId;&&&&public&MyThread(int&id)&&&&{&&&&&&&&this.threadId&=&&&&&}&&&&@Override&&&&public&synchronized&void&run()&&&&&{&&&&&&&&for&(int&i&=&0;&i&&&100;&++i)&&&&&&&&{&&&&&&&&&&&&System.out.println("Thread&ID:&"&+&this.threadId&+&"&:&"&+&i);&&&&&&&&}&&&&}}public&class&ThreadDemo{&&&&/**&&&&&*&@param&args&&&&&*&@throws&InterruptedException&&&&&&*/&&&&public&static&void&main(String[]&args)&throws&InterruptedException&&&&{&&&&&&&&for&(int&i&=&0;&i&&&10;&++i)&&&&&&&&{&&&&&&&&&&&&new&Thread(new&MyThread(i)).start();&&&&&&&&&&&&Thread.sleep(1);&&&&&&&&}&&&&}}
&&&&& 从上述代码段可以得知,要想实现线程的同步,则这些线程必须去竞争一个唯一的共享的对象锁。
&&&&& 基于这种思想,我们将第一段代码修改如下所示,在创建启动线程之前,先创建一个线程之间竞争使用的Object对象,然后将这个Object对象的引用传递给每一个线程对象的lock成员变量。这样一来,每个线程的lock成员都指向同一个Object对象。我们在run方法中,对lock对象使用synchronzied块进行局部封锁,这样就可以让线程去竞争这个唯一的共享的对象锁,从而实现同步。
代码package&com.class&MyThread&implements&java.lang.Runnable{&&&&private&int&threadId;&&&&private&Object&&&&&public&MyThread(int&id,&Object&obj)&&&&{&&&&&&&&this.threadId&=&&&&&&&&&this.lock&=&&&&&}&&&&@Override&&&&public&&void&run()&&&&&{&&&&&&&&synchronized(lock)&&&&&&&&{&&&&&&&&&&&&for&(int&i&=&0;&i&&&100;&++i)&&&&&&&&&&&&{&&&&&&&&&&&&&&&&System.out.println("Thread&ID:&"&+&this.threadId&+&"&:&"&+&i);&&&&&&&&&&&&}&&&&&&&&}&&&&}}public&class&ThreadDemo{&&&&/**&&&&&*&@param&args&&&&&*&@throws&InterruptedException&&&&&&*/&&&&public&static&void&main(String[]&args)&throws&InterruptedException&&&&{&&&&&&&&Object&obj&=&new&Object();&&&&&&&&for&(int&i&=&0;&i&&&10;&++i)&&&&&&&&{&&&&&&&&&&&&new&Thread(new&MyThread(i,&obj)).start();&&&&&&&&&&&&Thread.sleep(1);&&&&&&&&}&&&&}}
从第二段代码可知,同步的关键是多个线程对象竞争同一个共享资源即可,上面的代码中是通过外部创建共享资源,然后传递到线程中来实现。我们也可以利用类成员变量被所有类的实例所共享这一特性,因此可以将lock用静态成员对象来实现,代码如下所示:&代码package&com.class&MyThread&implements&java.lang.Runnable{&&&&private&int&threadId;&&&&private&static&Object&lock&=&new&Object();&&&&public&MyThread(int&id)&&&&{&&&&&&&&this.threadId&=&&&&&}&&&&@Override&&&&public&&void&run()&&&&&{&&&&&&&&synchronized(lock)&&&&&&&&{&&&&&&&&&&&&for&(int&i&=&0;&i&&&100;&++i)&&&&&&&&&&&&{&&&&&&&&&&&&&&&&System.out.println("Thread&ID:&"&+&this.threadId&+&"&:&"&+&i);&&&&&&&&&&&&}&&&&&&&&}&&&&}}public&class&ThreadDemo&{&&&&/**&&&&&*&@param&args&&&&&*&@throws&InterruptedException&&&&&&*/&&&&public&static&void&main(String[]&args)&throws&InterruptedException&&&&{&&&&&&&&for&(int&i&=&0;&i&&&10;&++i)&&&&&&&&{&&&&&&&&&&&&new&Thread(new&MyThread(i)).start();&&&&&&&&&&&&Thread.sleep(1);&&&&&&&&}&&&&}}
再来看第一段代码,实例方法中加入sychronized关键字封锁的是this对象本身,而在静态方法中加入sychronized关键字封锁的就是类本身。静态方法是所有类实例对象所共享的,因此线程对象在访问此静态方法时是互斥访问的,从而可以实现线程的同步,代码如下所示:代码package&com.class&MyThread&implements&java.lang.Runnable{&&&&private&int&threadId;&&&&&&&&public&MyThread(int&id)&&&&{&&&&&&&&this.threadId&=&&&&&}&&&&@Override&&&&public&&void&run()&&&&&{&&&&&&&&taskHandler(this.threadId);&&&&}&&&&private&static&synchronized&void&taskHandler(int&threadId)&&&&{&&&&&&&&for&(int&i&=&0;&i&&&100;&++i)&&&&&&&&{&&&&&&&&&&&&System.out.println("Thread&ID:&"&+&threadId&+&"&:&"&+&i);&&&&&&&&}&&&&}}public&class&ThreadDemo{&&&&/**&&&&&*&@param&args&&&&&*&@throws&InterruptedException&&&&&&*/&&&&public&static&void&main(String[]&args)&throws&InterruptedException&&&&{&&&&&&&&for&(int&i&=&0;&i&&&10;&++i)&&&&&&&&{&&&&&&&&&&&&new&Thread(new&MyThread(i)).start();&&&&&&&&&&&&Thread.sleep(1);&&&&&&&&}&&&&}}&
随笔 - 607
评论 - 1555
引用 - 117Java线程同步如何在不同线程中调用,Java教程,Java案例,Java实例
     本站短域名:珠江路.cn、zjlu.net
        
     
       
您的位置: &&
&& Java线程同步如何在不同线程中调用
Java线程同步如何在不同线程中调用
Java线程同步需要我们大家不断的学习,但是在学习的时候有些重要的代码还是需要注意,下面我们就来详细的看看Synchronized,顾名思义,代表Java线程同步&&。那么,在java编程中如何使用它呢?
我们首先来看这样一个情况:对于同一个变量synDemo,我们分别在2个不同的线程中调用synDemo.synMethord1()与synDemo.synMethord2()&&。
package&blogs./**&*&*&@author&Aaron.Guo&*&*/ &public&class&Tester&{&public&static&void&main(String[]&args)&{&final&SynDemo&synDemo&=&new&SynDemo();&Thread&thread1&=&new&Thread()&{&@Override&public&void&run()&{&//&TODO&Auto-generated&method&stub &super.run();&synDemo.synMethord1();&}&};&Thread&thread2&=&new&Thread()&{&@Override&public&void&run()&{&//&TODO&Auto-generated&method&stub &super.run();&synDemo.synMethord2();&}&};&thread1.start();&thread2.start();&while&(true)&{&try&{&Thread.sleep(1000);&System.out.println("main");&}&catch&(InterruptedException&e)&{&//&TODO&Auto-generated&catch&block &e.printStackTrace(); &} &} &} &}&
我们的SynDemo是这样定义的:
package&blogs./**&*&*&@author&Aaron.Guo&*&*/ &public&class&SynDemo{&public&void&synMethord1()&{&while(true)&{&try&{&Thread.sleep(1000);&System.out.println("synMethord1");&}&catch&(InterruptedException&e)&{&//&TODO&Auto-generated&catch&block &e.printStackTrace(); &} &} &} &public&void&synMethord2()&{&while(true)&{&try&{&Thread.sleep(1000);&System.out.println("synMethord2");&}&catch&(InterruptedException&e)&{&//&TODO&Auto-generated&catch&block &e.printStackTrace(); &} &} &} &}&
以上就是对Java线程同步的详细介绍,希望大家有所帮助&&。
数据库开发
产品库推荐
All Rights Reserved.
珠江路在线版权所有
 |  |  | 1112人阅读
我们都知道传统的线程同步可以通过同步代码块或者同步方法来实现.
那么这里就牵扯到同步监视器的问题.
如果对同步监视器不了解的获取对线程相关知识不了解的, 可以查看博客
要想确保线程之间能够同步,那么监视器对象对于同步线程之间来说是同一个对象,这样才会起到同步互斥效果.
所以很多人使用字节码(class)最为同步对象,因为在内存中只存在一份某个类的字节码. 当然这很好,当这是对于一组同步函数而言的.
如果有多组函数呢?既然是多组,那么就有多个同步监视器对象,如果同步监视器使用同一个类的字节码,那么这样的话就成了一组同步函数.
下面的例子有两组同步函数头两个线程对print()进行同步互斥,后两个线程对printName()进行同步互斥;也就是他们两组是各干各的.互不影响的.
public synchronized static void print() {
//synchronized (ThreadGroup.class) {
String source = &我是中国人&;
char[] cs = source.toCharArray();
System.out.println(Thread.currentThread().getName());
for (int j = 0; j & 30; j++) {
System.out.println(j + & -----------------& &);
for (int i = 0; i & cs. i++) {
System.out.print(cs[i]);
System.out.println();
public synchronized static void printName() {
//synchronized (String.class) {
System.out.println(Thread.currentThread().getName());
String source = &你好吗&;
char[] cs = source.toCharArray();
for (int j = 0; j & 30; j++) {
System.out.println(j+& -----------------& &);
for (int i = 0; i & cs. i++) {
System.out.print(cs[i]);
System.out.println();
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
}).start();
new Thread(new Runnable() {
public void run() {
}).start();
//--------------------------第二组
new Thread(new Runnable() {
public void run() {
printName();
}).start();
new Thread(new Runnable() {
public void run() {
printName();
}).start();
因为print()和printName()都是static的,而且又有synchronized关键字修饰.所以同步监视器对象都是当前类的字节码.所以实际上以上只有一组同步函数.而不是两组
从输出结果可以看出,当一个线程执行了print()或printName()方法,其他3个线程只有等该线程执行完毕才能接着执行.
如果多组的话,一个组的同步互斥不会影响其他组的执行.
由于输出结果较多就不粘贴了.
把print()和printName()两个方法改成一下就是两组同步了.
public static void print() {
synchronized (ThreadGroup.class) {
String source = &我是中国人&;
char[] cs = source.toCharArray();
System.out.println(Thread.currentThread().getName());
for (int j = 0; j & 30; j++) {
System.out.println(j + & -----------------& &);
for (int i = 0; i & cs. i++) {
System.out.print(cs[i]);
System.out.println();
public static void printName() {
synchronized (String.class) {
System.out.println(Thread.currentThread().getName());
String source = &你好吗&;
char[] cs = source.toCharArray();
for (int j = 0; j & 30; j++) {
System.out.println(j+& -----------------& &);
for (int i = 0; i & cs. i++) {
System.out.print(cs[i]);
System.out.println();
从输出结果可以看出,两组同步是互相交替执行的.
转载请注明出处:&
有什么错误不吝指正,谢谢!
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:260953次
积分:3468
积分:3468
排名:第3558名
原创:62篇
转载:56篇
评论:237条
欢迎转载,转载注明出处.}

我要回帖

更多关于 java线程锁 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信