线程通信首先通过共同访问一个字段、对象的字段。这种形式的通信是非常有效的。但是会产生两种错误:线程冲突和内存一致性错误。需要用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;
}
}
Count的increment方法对c做+1操作decrement方法对c做-1操作。然而,这个计数器一旦被多线程调用,线程冲突会阻止预期的结果。
冲突放生在当两个这两个操作运行在不同的线程里,但是访问了同一个数据。这说明两个操作包含很多步骤,步骤顺序重叠。
对Counter实例的操作看起来并不是交错的,因为对c的两个操作是单一的、简单的。但是,一个简单的操作对于jvm来说也是有很多步骤的。我们不想去考虑jvm的特殊步骤,这已经能了解一个c++表达式分为三步:
- 取回当前值c
- 取回的值加1
- 把增长的值写回c
表达式c—能被分解成同样的方式,只是第二步是减一。
假设线程A调用加法,同时线程B调用减法。如果c初始值是0,两个线程的交错动作可能是下面的顺序:
- Thread A: 取回 c.
- Thread B: 取回 c.
- Thread A: 增加取回的值; 结果是1.
- Thread B: 减少取回的值; 结果是 -1.
- Thread A: 结果存回c; c is now 1.
- Thread B: 结果存回c; c is now -1.
线程A的结果丢失了,被线程B覆盖。这个执行顺序只是其中一种可能。在不同的情况下,可能B的结果丢失,或者根本没错误。因为这是不可预料的,线程冲突bug难以发现和解决。
内存不一致错误发生在不同线程对同一个数据的不一致查看。引起内存不一致是复杂的,超出了本教程的范围。辛运的是,程序员不需要详细理解原因。只需要一个侧率避免它。
避免内存不一致的关键是理解happens-before关系。这个关系是一个简单的保证通过一个明确的语句对于另个明确的语句是可见的。看下面的例子,加入一个简单的域被定义和初始化:
int counter = 0;
这个计数域共享给两个线程,A和B。假设线程A增加计数:
counter++
然后,没过多久,线程B打印counter:
System.out.println(counter);
如果两个语句被同一个线程执行,我们可以确定打印的值是1.但是如果两个语句被不同线程执行,打印值可能是0,因为不能担保线程A改变counter对B是可见的 ——除非程序员确保两个语句之间的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;
}
}
如果count是SynchronizedCounter的一个实例,那么是这些方法同步有两个效果:
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.
警告:当构造一个对象共享给多个线程,小心对象的引用不要发生过早泄露。例如,假如你想保持一个叫instances的List包含class的每一个实例。你可能添加下面的语句到构造方法:
instances.add(this);
然而另一个线程可以使用instances访问对象,在构造方法之前。
士大夫同步方法使用一个简单的策略防止线程冲突和内存不一致错误:如果一个对象被多个线程访问,所有对这个对象的变量读写操作都要通过同步方法。(一个重要的例外:final域,对象构造之后不能被修改,能够被不同步方法安全的读)这个策略是有效的,但是会产生活性问题,我们在下面的章会提到。
同步是建立在被叫做“固有锁”或者“监听锁”的内存实体上的。(API规范经常叫做监视器monitor)。固有锁在同步的两个方面起作用:互斥访问一个对象的状态和 简历happens-before关系必须得可见性。
每个对象有一个与之相关的固有锁。按约定,一个线程独占的和一致的去访问一个对象的域,所以不得不在访问对象之前取的它的固有锁,然后处理完之后释放固有锁。在一个线程在取的锁和释放锁之间的时间叫做持有固有锁。只要一个线程持有固有锁,没有其他线程能过获得同一个锁。其他线程将延迟当它试图取的锁。
当一个线程释放一个固有锁,一个happens-before关系被建立在当前动作和后续的获得同一个锁的动作。
当一个线程调用同步方法,它自动获取这个方法的对象的固有锁,当方法返回释放。即使方法由一个未经捕获的异常结束,也要释放锁。
你可能想知道当一个静态的同步方法被调用的时候会发生什么,因为一个静态方法是和class联系在一起的,不是对象。这样,线程获取和这个类有关的Class的对象的固有锁。因此,访问类的静态域的锁的控制和对象锁是不一样的。
另一个创建同步代码的方式是用“同步声明”。和同步方法不同,同步声明必须给固有锁指定对象:
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
在这个例子里,addName方法需要同步改变lastName和nameCount,但是也要避免其他对象的方法。(对同步代码调用其他对象的方法会产生一个问题,在Liveness中描述。)没有同步声明,这里就只有一个单独的、非同步的、目的是调用nameList.add的方法。
同步声明用细粒度的同步来改进并发。假如,类Mslunch有连个域,c1和c2,他们不会共同使用。所有更新他们的操作必须是同步的,但是没有理由阻止更新c1的同时穿插更新c2——而且这样做可以减少并发阻塞。而不是用同步方法或使用this关联的锁,我们创建两个只提供锁的对象。
使用这个方法需要格外小心。你必须完全确定交叉访问受影响的域是安全的。
回想,一个线程不能获取另一个线程拥有的锁。但是一个线程可以获取已经拥有的锁。重入同步使一个线程不止一次获取同一个锁成为可能。想象一个情况,一个同步代码,直接或者间接的调用另一个同步代码。没有“重入同步”同步代码将不得不考虑去避免自己引起锁的情况。
在程序设计中,一个原子操作就是一个有效的同时发生的操作。一个原子操作不能被中断:要么操作完成,要么不操作。在原子操作完成之前不会产生任何效果。
我们看过一个增量表达式,像c++,这不是原子操作。甚至很多简单的表达式可以被分解为很多复杂的动作。但是这些动作你可以指定成原子的:
- 读写操作对于引用变量和基本类型变量(出了long和double)
- 读写操作对于所有定义了volatile的变量是元真子的(包括long和double)
原子操作不能被交错,所以不用考虑线程冲突。但是,并不是完全不需要步了,因为,内存不一致错误仍然存在。使用volatile关键字会减少内存不一致错误的风险,因为,任何对volatile变量的写操作和后面的读操作建立了happens-before关系。这意味着改变volatile变量对于其他线程总是可见的。更重要的是,这也意味着当一个线程读一个volatile变量,它读到的不仅是对变量的最后一次修改,也可能读到修改导致的负面影响。
使用原子变量访问比同步代码更有效,但是需要程序员小心,去避免内存不一致错误。是不是要考虑其他影响,要根据工程的规模和复杂度。
Java.util.concurrent包中的一些类提供了不依赖同步的原子操作的方法。我们将在Level Concurrency Objects一章中讨论。
分享到:
相关推荐
以上知识点覆盖了Java并发编程的主要方面,包括线程管理、同步机制、并发工具、设计模式、并发集合以及并发编程的最佳实践等,是理解和掌握Java并发编程的关键。在实际开发中,理解和熟练运用这些知识可以编写出高效...
书中会首先介绍Java并发编程的基础知识,包括线程的创建和运行,同步机制的基本用法,以及Java内存模型的相关概念。随着章节的深入,作者可能会更深入地讲解Java提供的并发工具,例如锁、原子变量、线程池、以及并发...
锁是Java并发编程中用于同步的关键工具。书中深入剖析了各种锁机制,如内置锁(也称为监视器锁),通过`synchronized`关键字实现。此外,还介绍了高级的锁接口`java.util.concurrent.locks`,如`ReentrantLock`,它...
《Java并发编程艺术》这本书深入探讨了Java平台上的并发编程技术。并发编程是现代多核处理器环境下提升软件性能的关键手段,而Java语言提供了丰富的工具和API来支持这一领域。本书旨在帮助开发者理解和掌握如何在...
总的来说,这份“java并发编程内部分享PPT”涵盖了Java并发编程的多个重要方面,包括线程创建与管理、同步机制、并发容器、线程池、并发问题以及异步计算。通过深入学习和实践这些知识点,开发者可以更好地应对多...
《Java 并发编程实战》是一本专注于Java并发编程的权威指南,对于任何希望深入了解Java多线程和并发控制机制的开发者来说,都是不可或缺的参考资料。这本书深入浅出地介绍了如何在Java环境中有效地管理和控制并发...
### Java并发编程实战知识点概述 #### 一、Java并发特性详解 在《Java并发编程实战》这本书中,作者深入浅出地介绍了Java 5.0和Java 6中新增的并发特性。这些特性旨在帮助开发者更高效、安全地编写多线程程序。书中...
《Java并发编程的艺术》这本书是Java开发者深入理解并发编程的重要参考书籍。这本书全面地介绍了Java平台上的并发和多线程编程技术,旨在帮助开发者解决在实际工作中遇到的并发问题,提高程序的性能和可伸缩性。 ...
#### 一、Java并发概述 自Java诞生之初,其设计者就赋予了该语言强大的并发处理能力。Java语言内置了对线程和锁的支持,这使得开发者能够轻松地编写多线程应用程序。本文旨在帮助Java开发者深入理解并发的核心概念...
《Java并发编程实践》是一本深入探讨Java多线程编程的经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowles和David Holmes等专家共同编写。这本书全面介绍了Java平台上的并发编程技术,是Java开发...
2. **同步机制**:Java并发编程的核心在于同步,以防止数据不一致性和资源竞争。`synchronized`关键字用于实现临界区的互斥访问,确保同一时刻只有一个线程执行特定代码块。此外,还有`wait()`, `notify()`, `...
《JAVA并发编程艺术》是Java开发者深入理解和掌握并发编程的一本重要著作,它涵盖了Java并发领域的核心概念和技术。这本书详细阐述了如何在多线程环境下有效地编写高效、可靠的代码,对于提升Java程序员的技能水平...
"Java并发编程与实践"文档深入剖析了这一主题,旨在帮助开发者理解和掌握如何在Java环境中有效地实现并发。 并发是指在单个执行单元(如CPU)中同时执行两个或更多任务的能力。在Java中,这主要通过线程来实现,...
第四部分深入探讨了Java并发编程的高级主题,包括显式锁(如ReentrantLock)、原子变量(Atomic类)、非阻塞算法以及自定义同步组件的开发。这些高级主题帮助开发者解决复杂并发场景下的问题,实现更高层次的并发...
Java并发编程实践是Java开发中不可或缺的一个领域,它涉及到如何高效、正确地处理多线程环境中的任务。这本书的读书笔记涵盖了多个关键知识点,旨在帮助读者深入理解Java并发编程的核心概念。 1. **线程和进程的...
Java并发编程中的多线程协作机制 在 Java 并发编程中,多线程协作机制是非常重要的一部分。多线程协作机制是指在多线程编程中,多个线程之间如何协作、同步和通信,以达到共同完成某个任务的目的。Java 提供了多种...
《Java并发实战》是深入探讨Java编程中并发处理的一本技术书籍。在现代软件开发中,多线程和并发控制是提升程序性能的关键技术,尤其是在服务器端开发中尤为重要。Java作为一门成熟的编程语言,其在并发控制方面提供...
Java并发API包括了线程、锁、同步、并发容器等丰富的工具,使得开发者可以构建能够充分利用多核处理器性能的应用程序。本书详细介绍了这些主题,并提供了实例代码和实践建议。 首先,书中详细讨论了Java线程的创建...