锁与synchronized

来源:互联网 发布:房屋平面设计软件手机 编辑:程序博客网 时间:2024/05/21 09:11

我们知道,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 blocke.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;// 方法头部加上synchronizedpublic synchronized void say(String sentence) {this.sentence = sentence;try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 blocke.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 blocke.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 blocke.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 blocke.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 blocke.printStackTrace();}System.out.println(sentence);}}

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

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

原创粉丝点击