Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2566457
  • 博文数量: 245
  • 博客积分: 4125
  • 博客等级: 上校
  • 技术积分: 3113
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-25 23:56
文章分类

全部博文(245)

文章存档

2015年(2)

2014年(26)

2013年(41)

2012年(40)

2011年(134)

2010年(2)

分类: Java

2014-07-01 10:08:33

线程同步Synchronization

线程就像图书借阅者,线程从资源池中借资源。线程通过共享内存、文件处理、sockets和其他资源让程序更加高效。只要两个线程不想同时使用相同的资源,多线程比多进程更高效,因为每个进程需要keep its own copy of every resource。
多线程的缺点是:如果两个线程同时想要得到相同的资源时,其中之一不得不等待另一个线程完成。如果其中之一不等待,共享的资源可能会被损坏(corrupted)。

@Override
public void run() {
	try {
		FileInputStream in = new FileInputStream(filename);
		MessageDigest sha = MessageDigest.getInstance("SHA-256");
		DigestInputStream din = new DigestInputStream(in, sha);
		while (din.read() != -1) ; // read entire file
		din.close();
		byte[] digest = sha.digest();
		System.out.print(input + ": ");
		System.out.print(DatatypeConverter.printHexBinary(digest));
		System.out.println();
	} catch (IOException ex) {
		System.err.println(ex);
	} catch (NoSuchAlgorithmException ex) {
		System.err.println(ex);
	}
}

当我们启动多个上面的线程时,在控制台输出的结果可能是这样的:
Triangle.java: B4C7AF1BAE952655A96517476BF9DAC97C4AF02411E40DD386FECB58D94CC769
InterfaceLister.java: Squares.java: UlpPrinter.java:
C8009AB1578BF7E730BD2C3EADA54B772576E265011DF22D171D60A1881AFF51
267D0EFE73896CD550DC202935D20E87CA71536CB176AF78F915935A6E81B034
DA2E27EA139785535122A2420D3DB472A807841D05F6C268A43695B9FDFE1B11

Synchronized Blocks

You need a way to assign exclusive access to a shared resource to one thread for a specific series of statements.
我们需要将共享资源以排他访问的方式分配给某个线程的特定的代码片段。
例如,为了让下面的五行代码同时执行,我们需要将它们放在synchronized同步块下,同步System.out对象:

synchronized (System.out) {
    System.out.print(input + ": ");
    System.out.print(DatatypeConverter.printHexBinary(digest));
    System.out.println();
}

Synchronization forces all code that synchronizes on the same object to run in series, never in parallel.
当线程A开始输出时,其他线程必须停下来等待线程A完成才可以开始输出。同步强制被同步的代码块按顺序执行,而不能并行。

假设,你的web服务器保存一个 logfile.这个logfile用下面的类LogFile表示。这个类本身没有使用多线程。但是,如果web服务器使用多线程处理连接,那么每个连接,都需要访问相同的logfile,也就是同一个LogFile 对象。

imporngt java.io.*;
import java.util.*;
public class LogFile {
	private Writer out;
	public LogFile(File f) throws IOException {
		FileWriter fw = new FileWriter(f);
		this.out = new BufferedWriter(fw);
	}
	public void writeEntry(String message) throws IOException {
		Date d = new Date();
		out.write(d.toString());
		out.write('\t');
		out.write(message);
		out.write("\r\n");
	}
	public void close() throws IOException {
		out.flush();
		out.close();
	}
}

当有多个线程都持有同一个LogFile对象的引用,调用writeEntry()方法输出log时,输出的log将可能会混乱。
这里有两种选择:同步Writer out对象,或者同步LogFile 对象。

同步out对象:

public void writeEntry(String message) throws IOException {
	synchronized (out) {
		Date d = new Date();
		out.write(d.toString());
		out.write('\t');
		out.write(message);
		out.write("\r\n");
	}
}

This works because all the threads that use this LogFile object also use the same out
object that’s part of that LogFile. It doesn’t matter that out is private. Although it is used by the other threads and objects, it’s referenced only within the LogFile class.

同步LogFile对象:

public void writeEntry(String message) throws IOException {
	synchronized (this) {
		Date d = new Date();
		out.write(d.toString());
		out.write('\t');
		out.write(message);
		out.write("\r\n");
	}
}

Synchronized Methods

Because synchronizing the entire method body on the object itself is such a common thing to do, Java provides a shortcut. You can synchronize an entire method on the current object (the this reference) by adding the synchronized modifier to the method declaration.

public synchronized void writeEntry(String message) throws IOException {
	Date d = new Date();
	out.write(d.toString());
	out.write('\t');
	out.write(message);
	out.write("\r\n");
}

简单的给所有的方法添加synchronized 关键词,不能解决所有的同步问题。这样做会带来以下问题:性能损耗;增加死锁的机会;通常需要同步的并不是对象本身。

上面的例子中,我们确切的需求是阻止两个线程同时使用out对象。如果其他的类有一个指向out的引用,这个引用和LogFile无关,那么我们同步LogFile对象本身就无法满足需求。然而,在上面的例子中同步LogFile是有效的,因为out是LogFile的一个私有实例变量,没有其他的引用可以指向这个out对象,也就不能调用out的方法。因此,上面的例子中,同步LogFile对象本身和同步out效果是一样的。
For instance, in this example, what you’re really trying to prevent is two threads simultaneously writing onto out. If some other class had a reference to out completely unrelated to the LogFile, this attempt would fail. However, in this example, synchronizing on the LogFile object is sufficient because out is a private instance variable. Because you never expose a reference to this object, there’s no way for any other object to invoke its methods except through the LogFile class. Therefore, synchronizing on the Log File object has the same effect as synchronizing on out.

Synchronization的其他方法

同步不总是解决由线程调度引起的不一致行为的最佳解决方案。有些技术可以完全避免使用同步的必要,例如:使用本地变量代替成员变量。
The first is to use local variables instead of fields wherever possible.本地变量没有同步的问题。每次进入一个方法时,虚拟机都会为方法创建新的本地变量。本地变量对于方法外部是不可见的,当方法执行完时,本地变量就被销毁了。总之,一个本地变量不可能被两个不同的线程共享。每个线程都有自己的单独的局部变量。

Method arguments of primitive types are also safe from modification in separate threads because Java passes arguments by value rather than by reference. A corollary of this is that pure functions such as Math.sqrt() that take zero or more primitive data type arguments, perform some calculation, and return a value without ever interacting with the fields of any class are inherently thread safe. These methods often either are or should be declared static.

构造器不会有线程安全的问题。因为在构造器return之前,没有线程会拥有这个对象的引用,也就不会有两个线程引用同一个对象。
immutable的对象不会有线程安全的问题。
To make an object immutable, simply declare all its fields private and final and don’t write any methods that can change them.
A lot of classes in the core Java library are immutable (e.g., java.lang.String, java.lang.Integer, java.lang.Double, and many more).

A third technique is to use a thread-unsafe class but only as a private field of a class that is thread safe. As long as the containing class accesses the unsafe class only in a threadsafe fashion and as long as it never lets a reference to the private field leak out into
another object, the class is safe. An example of this technique might be a web server that uses an unsynchronized LogFile class but gives each separate thread its own separate log so no resources are shared between the individual threads.

In some cases, you can use a designedly thread-safe but mutable class from the java.util.concurrent.atomic package. In particular, rather than using an int, you can use an AtomicInteger. Rather than using a long, you can use an AtomicLong. Rather than using a boolean, you can use an AtomicBoolean. Rather than using an int[], you can use an AtomicIntegerArray. Rather than a reference variable, you can store an object inside an AtomicReference, though note well that this doesn’t make the object itself thread safe, just the getting and setting of the reference variable. These classes may be faster than synchronized access to their respective primitive types if they can take advantage of fast machine-level thread-safe instructions on modern CPUs.

For collections such as maps and lists, you can wrap them in a thread-safe version using the methods of java.util.Collections.
For instance, if you have a set foo, you can get a thread-safe view of this set with Collections.synchronizedSet(foo).
If you have a list foo, you’d use Collections.synchronizedList(foo) instead.
For a map, call Collections.synchronizedMap(foo), and so forth.
In order for this to work, you must henceforth use only the view returned by Collections.synchronizedSet/List/Map.
If at any point you access the original, underlying data structure, neither the original nor the synchronized view will be thread safe.

In all cases, realize that it’s just a single method invocation that is atomic. If you need to perform two operations on the atomic value in succession without possible interruption, you’ll still need to synchronize. Thus, for instance, even if a list is synchronized via
Collections.synchronizedList(), you’ll still need to synchronize on it if you want to iterate through the list because that involves many consecutive atomic operations. Although each method call is safely atomic, the sequence of operations is not without explicit synchronization.

Deadlock

同步可能会导致死锁(deadlock)。更糟糕的是,死锁是不定时发生的,很难探测的bug。
当两个线程都需要排他的访问一系列共享的资源,并且每个线程都占用部分资源的锁,每个线程都不想放弃它占有的资源,这两个线程都将无限期的挂起。阻止死锁发生的最佳方法就是避免不必要的同步。如果有除了同步以为的其他方法可以保证线程安全,例如让对象immutable,使用局部变量,那么就不用使用同步。如果不得不使用同步,那就让同步块尽可能小,并且尽量不要在同一时刻同步多个对象。如果多个对象需要操作相同的一系列资源,请确保它们按相同的顺序请求这些资源。
Forinstance, if class A and class B need exclusive access to object X and object Y, make sure that both classes request X first and Y second. If neither requests Y unless it already possesses X, deadlock is not a problem.

阅读(5115) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~