多线程3:Java内置锁与synchronized关键字

Java提供了一种内置的锁机制来支持原子性:同步代码块(synchronized block),同步代码块包含2个部分:1,作为锁的对象引用;2,该锁所保护的代码块

synchronized(lock){
    ......
}
public class Status {
	private int count = 0;
	
	public int getNum(){
		synchronized(this){
			++count;
		}
		return count;
	}
}

方法的执行过程为:

 0:   aload_0
 1:   dup
 2:   astore_1
 3:   monitorenter    //获得对象锁
 4:   aload_0
 5:   dup
 6:   getfield        #12; //Field count:I
 9:   iconst_1
 10:  iadd
 11:  putfield        #12; //Field count:I
 14:  aload_1
 15:  monitorexit     //释放对象锁
 16:  goto    22
 19:  aload_1
 20:  monitorexit
 21:  athrow
 22:  aload_0
 23:  getfield        #12; //Field count:I
 26:  ireturn

可以看到比起正常的方法,增加了加锁和解锁的字节码指令(为什么会有个goto语句?)

每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B必须等待或阻塞直到线程A退出同步代码块,线程B才能获取到同一个锁,由于同一时刻只能有一个线程进入同步代码块,故同步代码块中的操作可以保证原子性 

public class Status {
	private int num = 0;
	
	public final int getNum(){
		return num;
	}
	
	public final void setNum(int num){
		this.num = num;
	}
}
public class Task implements Runnable {
	private Status status = new Status();
	
	public void run() {
		synchronized (status) {
			int num = status.getNum();
			status.setNum(num + 1);
			System.out.println(Thread.currentThread().getName() 
					+ "|" + status.getNum() 
					+ "|" + status.hashCode());
		}
	}
	
	public static void main(String[] args) {
		Task task = new Task();
		Thread t1 = new Thread(task);
		Thread t2 = new Thread(task);
		Thread t3 = new Thread(task);
		Thread t4 = new Thread(task);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

运行结果为:

Thread-0|1|22971385
Thread-2|2|22971385
Thread-3|3|22971385
Thread-1|4|22971385

修改一下上面的代码,每个线程运行时new一个新的Status对象,而不是像上面的代码,4个线程共用同一个Status对象:

public class Task implements Runnable {
	private Status status;
	
	public void run() {
		status = new Status();
		synchronized (status) {
			int num = status.getNum();
			status.setNum(num + 1);
			System.out.println(Thread.currentThread().getName() 
					+ "|" + status.getNum() 
					+ "|" + status.hashCode());
		}
	}
......

由于充当锁的对象不一定是同一个对象(hashcode不同),同步失效:

Thread-0|1|27744459
Thread-3|1|28737396
Thread-1|2|28737396
Thread-2|1|6927154

因此同步代码块中充当锁的对象必须为同一个对象

public class Task implements Runnable {
	private Status status;
	
	public Task(Status status){
		this.status = status;
	}
	
	public void run() {
		synchronized (status) {
			System.out.println("Thread lock");
			System.out.println("Thread:" + status.getNum());
			System.out.println("Thread over");

		}
	}
	
	public static void main(String[] args) {
		Status status = new Status();
		Task task = new Task(status);
		Thread t = new Thread(task);
		t.start();
		//synchronized(status){
			System.out.println("Main");
			status.setNum(1);
			System.out.println("Main:" + status.getNum());
		//}
	}
}

运行结果为:

Main
Thread lock
Main:1
Thread:1
Thread over

从运行结果可以看出,在Thread线程锁定status对象的时候,Main线程在Thread线程释放锁对象前依然能够修改status对象的num域,说明锁没有生效

Main线程中没有对status对象进行同步,故在Thread线程锁定status对象的时候不需要等待或阻塞,可以直接操作status对象,因此所有使用同步对象的地方都必须进行同步

修改方式为:Task类的main方法中,在操作status对象时进行同步(去掉代码中的注释部分)

用synchronized关键字修饰的方法是一个横跨整个方法体的同步代码块,锁为方法所在的对象,如果该方法为静态方法,则锁为Class类,当然这里的锁也必须为同一个对象

特别需要注意的是所有访问状态变量的方法都必须进行同步

public class Task implements Runnable {
	private int count = 0;
	
	public void run() {
		for(int i = 0 ; i < 5 ; i++){
			this.reset();
			this.add();
		}
	}
	
	public synchronized void reset(){
		if(count == 5){
			count = 0;
			System.out.println(Thread.currentThread().getName()
					+ "[count reset]");
		}
	}
	
	public synchronized void add(){
		count++;
		System.out.println(Thread.currentThread().getName()
				+ "[count:" + count + "]");
	}
	
	public static void main(String[] args){
		Task t = new Task();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		t2.start();
	}
} 

运行结果:

Thread-0[count:1]
Thread-0[count:2]
Thread-0[count:3]
Thread-0[count:4]
Thread-1[count:5]
Thread-1[count reset]
Thread-1[count:1]
Thread-1[count:2]
Thread-1[count:3]
Thread-1[count:4]

 

posted @ 2012-11-30 13:30  心意合一  阅读(220)  评论(0编辑  收藏  举报