`
leichenlei
  • 浏览: 124496 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发(三、同步)

    博客分类:
  • java
 
阅读更多

同步

线程通信首先通过共同访问一个字段、对象的字段。这种形式的通信是非常有效的。但是会产生两种错误:线程冲突和内存一致性错误。需要用Synchronization工具避免这些错误。

l  线程冲突:描述了当多个线程访问共享数据时错误是怎么发生的。

l  内存一致错误:描述了共享内存的不一致展现的错误结果。

l  同步方法:描述了一个简单的习惯,能够避免线程冲突和内存不一致错误。

l  隐式锁和同步:描述了一个更普遍的同步习惯,还描述了怎么基于隐式锁进行同步。

l  原子访问:谈到了一个避免和其他线程冲突的操作习惯。

(一)           线程冲突

看下面的Counter类:

class Counter {
    private int c = 0;
    public void increment() {
        c++;
    }
    public void decrement() {
        c--;
    }
    public int value() {
        return c;
    }
}

Countincrement方法对c+1操作decrement方法对c-1操作。然而,这个计数器一旦被多线程调用,线程冲突会阻止预期的结果。

冲突放生在当两个这两个操作运行在不同的线程里,但是访问了同一个数据。这说明两个操作包含很多步骤,步骤顺序重叠。

Counter实例的操作看起来并不是交错的,因为对c的两个操作是单一的、简单的。但是,一个简单的操作对于jvm来说也是有很多步骤的。我们不想去考虑jvm的特殊步骤,这已经能了解一个c++表达式分为三步:

  1. 取回当前值c
  2. 取回的值加1
  3. 把增长的值写回c

表达式c—能被分解成同样的方式,只是第二步是减一。

假设线程A调用加法,同时线程B调用减法。如果c初始值是0,两个线程的交错动作可能是下面的顺序:

  1. Thread A: 取回 c.
  2. Thread B: 取回 c.
  3. Thread A: 增加取回的值; 结果是1.
  4. Thread B: 减少取回的值; 结果是 -1.
  5. Thread A: 结果存回c; c is now 1.
  6. Thread B: 结果存回c; c is now -1.

线程A的结果丢失了,被线程B覆盖。这个执行顺序只是其中一种可能。在不同的情况下,可能B的结果丢失,或者根本没错误。因为这是不可预料的,线程冲突bug难以发现和解决。

 

 

(二)           内存不一致错误

内存不一致错误发生在不同线程对同一个数据的不一致查看。引起内存不一致是复杂的,超出了本教程的范围。辛运的是,程序员不需要详细理解原因。只需要一个侧率避免它。

    避免内存不一致的关键是理解happens-before关系。这个关系是一个简单的保证通过一个明确的语句对于另个明确的语句是可见的。看下面的例子,加入一个简单的域被定义和初始化:

int counter = 0;

这个计数域共享给两个线程,AB。假设线程A增加计数:

    counter++

然后,没过多久,线程B打印counter

    System.out.println(counter);

    如果两个语句被同一个线程执行,我们可以确定打印的值是1.但是如果两个语句被不同线程执行,打印值可能是0,因为不能担保线程A改变counterB是可见的 ——除非程序员确保两个语句之间的happens-before关系。

有一些方法创建happens-before关系。其中一个是同步,我们将在下面的章节看到。

我们看两种方法创建happens-before关系:

l  当一个语句调用Thread.start,每个语句和这个语句有happens-before关系,同时新线程的执行语句之间也是happens-before关系。

l  当一个线程终止同时引起一个Thread.join另一个线程返回,那么,所有被终止线程执行的语句和接下来成功join的语句是happens-before关系。线程的内代码对于执行join的线程是可见的。

创建Happens-before关系的方法列表,参考Summary page of the java.util.concurrent package.

(三)           同步方法

Java编程语言提供了两个基本的同步语句:synchronized methods synchronized statements。更复杂的synchronized statement下一章描述。这章是关于同步方法。

    使一个方法同步,只要简单的加synchronized关键字到它的声明中:

public class SynchronizedCounter {
    private int c = 0;
    public synchronized void increment() {
        c++;
    }
    public synchronized void decrement() {
        c--;
    }
    public synchronized int value() {
        return c;
    }
}
如果countSynchronizedCounter的一个实例,那么是这些方法同步有两个效果:
l  两个同步方法交错的访问同一个对象是不可能的。当一个线程执行一个同步方法访问对象,所有的其他线程调用同步方法访问同一个对象是阻塞的(推迟执行)直到执行完成。
l  当一个同步方法退出,它自动的和任何后面的同步方法建立一个happens-before关系,这保证了对象状态的改变对所有线程是可见的。
注意:构造方法不能同步——对构造方法使用同步是语法错误。同步构造方法没有意义,因为只有创建对象的线程在它创建之后能够访问它。

Note that constructors cannot be synchronized — using the synchronized keyword with a constructor is a syntax error. Synchronizing constructors doesn't make sense, because only the thread that creates an object should have access to it while it is being constructed.


警告:当构造一个对象共享给多个线程,小心对象的引用不要发生过早泄露。例如,假如你想保持一个叫instancesList包含class的每一个实例。你可能添加下面的语句到构造方法:

instances.add(this);
然而另一个线程可以使用instances访问对象,在构造方法之前。

士大夫同步方法使用一个简单的策略防止线程冲突和内存不一致错误:如果一个对象被多个线程访问,所有对这个对象的变量读写操作都要通过同步方法。(一个重要的例外:final域,对象构造之后不能被修改,能够被不同步方法安全的读)这个策略是有效的,但是会产生活性问题,我们在下面的章会提到。

(四)           固有锁和同步

同步是建立在被叫做“固有锁”或者“监听锁”的内存实体上的。(API规范经常叫做监视器monitor)。固有锁在同步的两个方面起作用:互斥访问一个对象的状态和 简历happens-before关系必须得可见性。

每个对象有一个与之相关的固有锁。按约定,一个线程独占的和一致的去访问一个对象的域,所以不得不在访问对象之前取的它的固有锁,然后处理完之后释放固有锁。在一个线程在取的锁和释放锁之间的时间叫做持有固有锁。只要一个线程持有固有锁,没有其他线程能过获得同一个锁。其他线程将延迟当它试图取的锁。

当一个线程释放一个固有锁,一个happens-before关系被建立在当前动作和后续的获得同一个锁的动作。

1.         同步方法中的锁

当一个线程调用同步方法,它自动获取这个方法的对象的固有锁,当方法返回释放。即使方法由一个未经捕获的异常结束,也要释放锁。

    你可能想知道当一个静态的同步方法被调用的时候会发生什么,因为一个静态方法是和class联系在一起的,不是对象。这样,线程获取和这个类有关的Class的对象的固有锁。因此,访问类的静态域的锁的控制和对象锁是不一样的。

2.         同步声明

另一个创建同步代码的方式是用“同步声明”。和同步方法不同,同步声明必须给固有锁指定对象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

 

在这个例子里,addName方法需要同步改变lastNamenameCount,但是也要避免其他对象的方法。(对同步代码调用其他对象的方法会产生一个问题,在Liveness中描述。)没有同步声明,这里就只有一个单独的、非同步的、目的是调用nameList.add的方法。

同步声明用细粒度的同步来改进并发。假如,类Mslunch有连个域,c1c2,他们不会共同使用。所有更新他们的操作必须是同步的,但是没有理由阻止更新c1的同时穿插更新c2——而且这样做可以减少并发阻塞。而不是用同步方法或使用this关联的锁,我们创建两个只提供锁的对象。

使用这个方法需要格外小心。你必须完全确定交叉访问受影响的域是安全的。

3.     重入同步

回想,一个线程不能获取另一个线程拥有的锁。但是一个线程可以获取已经拥有的锁。重入同步使一个线程不止一次获取同一个锁成为可能。想象一个情况,一个同步代码,直接或者间接的调用另一个同步代码。没有“重入同步”同步代码将不得不考虑去避免自己引起锁的情况。

(五)           原子访问

在程序设计中,一个原子操作就是一个有效的同时发生的操作。一个原子操作不能被中断:要么操作完成,要么不操作。在原子操作完成之前不会产生任何效果。

    我们看过一个增量表达式,像c++,这不是原子操作。甚至很多简单的表达式可以被分解为很多复杂的动作。但是这些动作你可以指定成原子的:

  • 读写操作对于引用变量和基本类型变量(出了longdouble
  • 读写操作对于所有定义了volatile的变量是元真子的(包括longdouble

原子操作不能被交错,所以不用考虑线程冲突。但是,并不是完全不需要步了,因为,内存不一致错误仍然存在。使用volatile关键字会减少内存不一致错误的风险,因为,任何对volatile变量的写操作和后面的读操作建立了happens-before关系。这意味着改变volatile变量对于其他线程总是可见的。更重要的是,这也意味着当一个线程读一个volatile变量,它读到的不仅是对变量的最后一次修改,也可能读到修改导致的负面影响。

使用原子变量访问比同步代码更有效,但是需要程序员小心,去避免内存不一致错误。是不是要考虑其他影响,要根据工程的规模和复杂度。

Java.util.concurrent包中的一些类提供了不依赖同步的原子操作的方法。我们将在Level Concurrency Objects一章中讨论。

分享到:
评论
1 楼 ynztpwl 2012-10-19  
看完啦,翻的很清楚,中断哪里没看明白,印象里面就只是在调用某些方法时需要强制抛出或捕获中断异常时才用到过...

相关推荐

    Java并发编程实战

    第三部分 活跃性、性能与测试 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章...

    JAVA并发编程实践 .pdf

    《Java并发编程实战》深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证...

    Java并发大神Doug Lee同步队列论文

    Java并发大神Doug Lee同步队列论文

    java 并发学习总结

    java并发学习总结 例子:包含(并发容器、同步容器、同步工具、死锁、异常、中断、线程池、返回结果、同步方法等代码例子)

    Java并发编程实战.rar

    《Java并发编程实战》是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程...

    并发Java程序同步操作的有效删除

    并发Java程序同步操作的有效删除

    Java并发编程实战2019.zip

    Java并发编程实战,第1章 简介,第2章 线程安全性 第3章 对象的共享 第4章 对象的组合 第5章 基础构建模块 第6章 任务执行 第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第10章 避免...

    JAVA并发编程实践 带书签

    本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则...

    Java并发编程实战(华章专业开发者书库).mobi

    本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则...

    java并发编程实战相关书籍

    本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则...

    Java并发编程实践 PDF 高清版

    Java 5以及6在开发并发程序取得了显著的进步,提高了Java虚拟机的性能,提高了并发类的可伸缩性,并加入了丰富的新并发构建块。在本书中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,同时,还阐释...

    java并发工具包 java.util.concurrent中文版用户指南pdf

    1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    Java 并发编程实战

    第三部分 活跃性、性能与测试 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章...

    java并发工具包详解

    1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...

    Java 并发编程实战(中文+高清版).zip

    本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则...

    Java并发编程实战.pdf

    本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则...

    龙果java并发编程完整视频

    第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器CopyOnWriteArrayList原理与使用00:15:52分钟 | 第48节并发容器ConcurrentLinkedQueue原理与使用00:31:03分钟 | 第49节Java中的阻塞队列原理与使用00:26:...

    Java并发编程实战-高清完整版-带书签

    本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则...

    Java并发编程实践

    《Java并发编程实战》深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及...

    java并发编程

    此外,本书还提供了有关并发编程的全方位的详细内容,例如限制和同步、死锁和冲突、依赖于状态的操作控制、异步消息传递和控制流、协作交互,以及如何创建基于web的服务和计算型服务。 本书的读者对象是那些希望掌握...

Global site tag (gtag.js) - Google Analytics