线程同步


和多线程紧密相关的一个概念就是:同步。同步是控制多线程获取共享资源的一种方法,一般情况下,只有一个线程可以占用资源。

在非同步的多线程应用中,多个进程可以同时需该一个对象的值。同步应用则不允许这样的情况发生,因为这会导致脏数据和错误的产生。

一般来说,关键部分的代码都是用synchronized关键字来修饰的。

线程同步的一个经典例子就是“生产者/消费者模型”

为了控制多进程对共享资源的获取,我们使用锁。一个锁可以和一个共享资源相关联。

线程通过获取锁来获取共享资源的使用权。任何时候只能有一个线程占用锁,也就是占用相关的资源。锁机制就是这样实现独占的。

锁机制实现了同步的以下规则:

1,一个线程如果想要占用独占资源,必须先获得锁。如果锁被其他资源占用,线程就要等待其他线程释放锁。

2,当线程放弃对共享资源的占用时,系统保证资源锁也会放弃占用。其他线程从而可以获得对锁的占用。

存在一种指定类的锁,它和对象锁类似,是和某个具体的类相关联的,用法和对象锁类似。

java中同步的两种实现方法:

1, 同步方法;

2,同步代码块。

两种方法是类似的。同步代码块只有当线程获取声明的锁之后才能执行。

同步方法

同步方法是用来控制对某个对象的获取。只有在获得对象或类的锁之后,线程才能执行同步方法。如果锁被其他线程占用,后来的线程需要等待。占用锁的线程执行完同步方法返回,随即放弃对锁的占用,队列中等待锁的线程开始执行。同步方法应用于不能并发执行的操作。这种情况被称为竞争条件。当多个进程同时修改同一个对象时,数据会出现不完整或者错误的状态。但是当进程是在一个同步的方法当中,其他线程需要得到锁才成执行这个方法,就不会出现数据被多个进程同时读写的错误。同步的方法中可以调用其他需要同步的方法(前提是所需要的对象锁是一致的)。

下面的例子表明同步方法是怎么协调多个进程对资源的占用的。如果把synchronized关键字删掉,信息出现的顺序就是随机的了。

public class SyncMethodsExample extends Thread {

	static String[] msg = { "Beginner", "java", "tutorial,", ".,", "com",
			"is", "the", "best" };
	public SyncMethodsExample(String id) {
		super(id);
	}
	public static void main(String[] args) {
		SyncMethodsExample thread1 = new SyncMethodsExample("thread1: ");
		SyncMethodsExample thread2 = new SyncMethodsExample("thread2: ");
		thread1.start();
		thread2.start();
		boolean t1IsAlive = true;
		boolean t2IsAlive = true;
		do {
			if (t1IsAlive && !thread1.isAlive()) {
				t1IsAlive = false;
				System.out.println("t1 is dead.");
			}
			if (t2IsAlive && !thread2.isAlive()) {
				t2IsAlive = false;
				System.out.println("t2 is dead.");
			}
		} while (t1IsAlive || t2IsAlive);
	}
	void randomWait() {
		try {
			Thread.currentThread().sleep((long) (3000 * Math.random()));
		} catch (InterruptedException e) {
			System.out.println("Interrupted!");
		}
	}
	public synchronized void run() {
		SynchronizedOutput.displayList(getName(), msg);
	}
}

class SynchronizedOutput {

	// if the 'synchronized' keyword is removed, the message
	// is displayed in random fashion
	public static synchronized void displayList(String name, String list[]) {
		for (int i = 0; i < list.length; i++) {
			SyncMethodsExample t = (SyncMethodsExample) Thread
					.currentThread();
			t.randomWait();
			System.out.println(name + list[i]);
		}
	}
}
Output

thread1: Beginner
thread1: java
thread1: tutorial,
thread1: .,
thread1: com
thread1: is
thread1: the
thread1: best
t1 is dead.
thread2: Beginner
thread2: java
thread2: tutorial,
thread2: .,
thread2: com
thread2: is
thread2: the
thread2: best
t2 is dead.



同步代码块

静态方法基于特定类的锁同步。线程对锁的占用决定了是不是能执行某个同步方法。

静态方法的同步和同一个类中非静态方法的不同。子类决定继承自父类的同步方法是否还应该为同步的。

通常,同步代码块如下:

synchronized (<object reference expression>) {
<code block>
}

如果synchronized表达式的结果不是基本类型的话,会发生编译错误。如果代码块正常执行完毕,锁就被释放。如果代码块的执行突然中断,锁也被释放。

一个进程可以同时占有多个锁。同步声明可以嵌套使用。同一个同步声明也可以嵌套。只是表达式的值必须为非空,不然的话将出现空指针异常。

代码块通常和同步的资源相关联,下面这个同步方法的例子,方法中的逻辑已经被锁同步了。

public Object method() {
synchronized (this) { // Synchronized block on current object
// method block
}
}



上面的例子中,如果已经有个进程在执行代码块,那么其他进程就无法执行这个代码块,也无法执行任何需要同样对象锁的代码块。

同步声明中必须要指定锁的对象。可以将方法中需要同步的部分放在synchronized代码块中。synchronized后面的大括号不能省略,就是同步的只有一条语句。

class SmartClient {
BankAccount account;
// …
public void updateTransaction() {
synchronized (account) { // (1) synchronized block
account.update(); // (2)
}
}
}



上述代码中,(2)处对账户的修改是需要同步的语句。(1)处声明了同步的对象。如果多个线程同时要执行synchronized部分的代码,那么只能每次有一个进程来执行。