锁与synchronized

我们知道,Java能够new一个Thread类或者一个有继承Thread类的子类,来创建一个线程。关于多线程编程,其中一个必须考虑的问题就是同步问题,比如,如果我有两个线程要操作同一个资源或者对象,如何能够保证这两个线程的操作是正确有效的、可以保持数据的一致性呢?

十分有用的一个解决方案是:使用synchronized关键字。


synchronized,字面意思是“同步的”。在探讨它的功能之前,我们最好先来认识一下“锁”这个东西。

想象一下,Java中每个对象,都有且只有一个叫做“锁”的东西。这个“锁”对于对象来说意义非凡,该对象的某些关联着这个“锁”的方法或者代码,只有获得对象这个“锁”的线程,才能访问执行。

举个例子来说,我是一个对象,我叫object1,我有一把“锁”,这把“锁”关联着我的其中一个方法objectMethod1();另外,有两个线程A和B,这一刻,线程A获得了我object1的“锁”,所以线程A有权访问我的方法objectMethod1(),与此同时线程B也想访问objectMethod1(),但是它没有“锁”,所以只能等待线程A访问完这个方法,释放了这个锁,B再去获得这个“锁”,才有资格访问objectMethod1()。

另一方面来说,没有关联着“锁”的方法和代码,不同的线程要访问它们,那都是随时随地的事情,没有什么制约也不用分先来后到。


从上面的描述来看,“锁”似乎是一个实实在在的东西,比较每个对象都有一个嘛。事实上,在代码当中,“锁”这个东西并不需要我们实际去声明(我们需要幻想它确实存在),我们需要做的仅仅是,声明那些与“锁”相关联的方法或者代码。这个时候,就要用到synchronized关键字了。

public class Run {
	public static void main(String[] args) {
		ObjectWithLock owl = new ObjectWithLock();
		A a = new A(owl);
		B b = new B(owl);
		a.start();
		b.start();
	}
}

class ObjectWithLock {

	private String sentence;

	public void say(String sentence) {
		this.sentence = sentence;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.sentence);
	}

}

class A extends Thread {
	private ObjectWithLock owl = null;
	
	public A(ObjectWithLock owl) {
		this.owl = owl;
	}
	
	public void run() {
		owl.say("我是A");
	}
}

class B extends Thread {
	private ObjectWithLock owl = null;

	public B(ObjectWithLock owl) {
		this.owl = owl;
	}
	
	public void run() {
		owl.say("我是B");
	}
}


创建一个类,这个类记忆力很差,只能记住一句话,只能做一件事,修改自己的记忆,睡一秒,然后把记忆里的那句话说出来。

好了,问题来了,如果有两个线程A和B,有一个ObjectWithLock实例owl,两个线程分别调用owl.say("我是A")和owl.say(“我是B”),会发生什么呢?

按照寻常思路来说,线程A先把sentence改成了“我是A”,然后滚去睡了,切到线程B,它把sentence改成了“我是B”,也滚去睡了。接着A和B相继醒来,打印出记忆中的内容,那应该都是“我是B”。然而结果并非如此,A线程依然打印“我是A”,B线程依然打印“我是B”。

为什么呢?因为say()方法用了synchronized修饰。

关于synchronized的作用,可以总结为一句话:synchronized标示一个方法或者一段代码只能被拥有某个“锁”的线程访问,首先访问synchronized标识方法或代码的线程将得到“锁”。

回到例子,对象owl拥有一个锁,这个锁叫“对象锁”(因为它为一个特定对象所拥有)。它的say()方法与这把“对象锁”关联着,一旦A线程访问这个方法,A线程就得到了这把“对象锁”,即使去睡觉,依然抱着这把锁不放。这时,B线程也想访问owl的say()方法,但是这是做不到的,因为owl的“对象锁”被A线程持有,它只能等A线程执行完say(),放开这把锁,自己再去获得这把锁,它才有了访问say()方法的能力。


所谓“对象锁”,就是被一个对象持有的“锁”。声明一个方法或者一段代码需要获得“对象锁”才能执行,有以下3种写法:

1. 写在方法头部

class ObjectWithLock {

	private String sentence;

	// 方法头部加上synchronized
	public synchronized void say(String sentence) {
		this.sentence = sentence;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(this.sentence);
	}

}


2. synchronized(this)用花括号包含特定代码段

class ObjectWithLock {

	private String sentence;

	public void say(String sentence) {
		
		// synchronized(this)包含特定代码段
		synchronized(this) {
			this.sentence = sentence;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.sentence);
		}
	
	}

}


3.synchronized(variable)用花括号包含特定代码段

class ObjectWithLock {
	
	private static Object lock;
	private String sentence;

	public void say(String sentence) {
		
		// synchronized(variable)包含特定代码段
		synchronized(lock) {
			this.sentence = sentence;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.sentence);
		}
	
	}

}

上面3个片段,都是标识“对象锁”与代码关联的例子。需要强调的是,片段1和2都表明,方法或代码段与包含这个方法或者代码的当前对象的锁关联,而片段3则表明,那个代码片段与lock这个对象的锁关联。所以更进一步地说,下面例子中,如果两个线程分别调用say()和think(),两线程不必互相同步,它们因为分别得到不同的“对象锁”而顺利执行。

class ObjectWithLock {
	
	private static Object lock;
	private String sentence;

	public void say(String sentence) {
		
		// 关联lock对象的锁
		synchronized(lock) {
			this.sentence = sentence;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(this.sentence);
		}
	
	}
	// 关联本对象的锁
	public synchronized void think() {
		System.out.println("我在思考");
	}

}

有“对象锁”,也就有“类锁”。“类锁”是每个类有且只有一个的“锁”,用来标定一个静态方法或者一段代码必须持有这个锁才能被执行。

1. synchronized(class)包含特定代码段

class ObjectWithLock {
	
	public void say(String sentence) {
		
		// synchronized(class)包含特定代码段
		synchronized(ObjectWithLock.class) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(sentence);
		}
	
	}
	
}

2. synchronized标识静态方法

class ObjectWithLock {
	
	// synchronized标识静态方法
	public static synchronized void say(String sentence) {
		
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(sentence);
		}
	
}

“类锁”比“对象锁”虽然有差异,但是理解上仍遵循同一思路:“锁”是一个东西,某线程只有得到这个“锁”才能访问被这个“锁”关联的方法或者代码,无论“锁”是对象所有的还是类所有的。

另外,“对象锁”和“类锁”各自为政,互相没有干扰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值