标签:
(接上文《线程基础:线程(2)——JAVA中的基本线程操作(上)》)
在前面的文章中我们主要讲解的是线程中“对象锁”的工作原理和操作方式。在讲解synchronized关键字的时候,我们还提到了synchronized关键字可以标注的位置。大家经常看到相当部分的网贴,在它们的代码示例中将synchronized关键字加载到代码的方法体上,然后告诉读者:这个操作是线程安全的。代码可能如下:
/**
* 这个类的class对象进行检查。
*/
public static synchronized void doSomething() {
}
/**
* 对这个类的实例化对象进行检查
*/
public synchronized void doOtherthing() {
}
但事实上,一个对象是否是线程安全的除了添加synchronized关键字以外,更重要的还要看如何进行这个对象的操作。如下代码中,我们展示了在两个线程的doOtherthing方法(所谓的线程安全方法),去操作一个对象NOWVALUE:
package test.thread.yield;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.BasicConfigurator;
/**
* 用来在启动后,等待唤醒
* @author yinwenjie
*/
public class SyncThread implements Runnable {
/**
* 日志
*/
private static final Log LOGGER = LogFactory.getLog(SyncThread.class);
private Integer value;
private static Integer NOWVALUE;
static {
BasicConfigurator.configure();
}
public SyncThread(int value) {
this.value = value;
}
/**
* 对这个类的实例化对象进行检查
*/
private synchronized void doOtherthing() {
NOWVALUE = this.value;
LOGGER.info("当前NOWVALUE的值:" + NOWVALUE);
}
@Override
public void run() {
Thread currentThread = Thread.currentThread();
Long id = currentThread.getId();
this.doOtherthing();
}
public static void main(String[] args) throws Exception {
Thread syncThread1 = new Thread(new SyncThread(10));
Thread syncThread2 = new Thread(new SyncThread(100));
syncThread1.start();
syncThread2.start();
}
}
从Debug的情况来看,可能出现静态对象NOWVALUE的值出现了脏读的情况:
0 [Thread-1] INFO test.thread.yield.SyncThread - 当前NOWVALUE的值:100
730 [Thread-0] INFO test.thread.yield.SyncThread - 当前NOWVALUE的值:100
以下是代码出现bug的原因:
syncThread1对象和syncThread2对象是SyncThread类的两个不同实例。“private synchronized void doOtherthing()”方法中的synchronized关键字实际上进行同步检查目标是不一样的。
如果您要进行类的多个实例对象进行同步检查,那么应该对这个类的class对象进行同步检查。写法应该是:“private synchronized static void doOtherthing()”
当然为了对这个类(SyncThread)的class对象进行同步检查,您甚至无需在静态方法上标注synchronized关键字,而单独标注SyncThread的class的对象锁状态检查:
private void doOtherthing() {
synchronized (SyncThread.class) {
NOWVALUE = this.value;
LOGGER.info("当前NOWVALUE的值:" + NOWVALUE);
}
}
所以,一个对象是否是线程安全的除了添加synchronized关键字以外,更重要的还要看如何进行这个对象的操作;标注了synchronized关键字的方法中,针对某个对象的操作不一定是线程安全的!
这是前文中已经给出的线程状态切换图例,可能有的读者还不能完全理解其中的切换条件,没关系从本章节开始我们将详细介绍JAVA中如何进行这些线程状态的操作。
除了上一章节在讲解“对象锁”的时候已经提到的wait、wait(time)操作以外,本章节将讲解notify、notifyAll、interrupt、join和sleep等操作。
在JAVA JDK中,对于notify方法和notifyAll方法的解释分别是:
Wakes up a single thread that is waiting on this object’s monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object’s monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
Wakes up all threads that are waiting on this object’s monitor. A thread waits on an object’s monitor by calling one of the wait methods.
The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.
为了说明notify方法和notifyAll方法的工作现象,下面我会为这两个方法分别给出一段代码,并进行详细解释。
package test.thread.notify;
import org.apache.log4j.BasicConfigurator;
/**
* 这个线程用来发出notify请求
* @author yinwenjie
*/
public class ParentNotifyThread implements Runnable {
/**
* 这个对象的“钥匙”,为每个ChildNotifyThread对象所持有,
* 模拟这个对象为所有ChildNotifyThread对象都要进行独占的现象
*/
public static final Object WAIT_CHILEOBJECT = new Object();
static {
BasicConfigurator.configure();
}
public static void main(String[] args) throws Exception {
new Thread(new ParentNotifyThread()).start();
}
public void run() {
/*
* 3个进行WAIT_CHILEOBJECT对象独立抢占的线程,观察情况
* */
int maxIndex = 3;
for(int index = 0 ; index < maxIndex ; index++) {
ChildNotifyThread childNotify = new ChildNotifyThread();
Thread childNotifyThread = new Thread(childNotify);
childNotifyThread.start();
}
/*
* 请在这里加eclipse断点,
* 以便保证ChildNotifyThread中的wait()方法首先被执行了。
*
* 真实环境下,您可以通过一个布尔型(或者其他方式)进行阻塞判断
* 还可以使用CountDownLatch类
* */
synchronized (ParentNotifyThread.WAIT_CHILEOBJECT) {
ParentNotifyThread.WAIT_CHILEOBJECT.notify();
}
// 没有具体的演示含义;
// 只是为了保证ParentNotifyThread不会退出
synchronized (ParentNotifyThread.class) {
try {
ParentNotifyThread.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package test.thread.notify;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 用来在启动后,等待唤醒
* @author yinwenjie
*/
public class ChildNotifyThread implements Runnable {
/**
* 日志
*/
private static final Log LOGGER = LogFactory.getLog(ChildNotifyThread.class);
@Override
public void run() {
Thread currentThread = Thread.currentThread();
long id = currentThread.getId();
ChildNotifyThread.LOGGER.info("线程" + id + "启动成功,准备进入等待状态");
synchronized (ParentNotifyThread.WAIT_CHILEOBJECT) {
try {
ParentNotifyThread.WAIT_CHILEOBJECT.wait();
} catch (InterruptedException e) {
ChildNotifyThread.LOGGER.error(e.getMessage() , e);
}
}
//执行到这里,说明线程被唤醒了
ChildNotifyThread.LOGGER.info("线程" + id + "被唤醒!");
}
}
以上两段代码中,ParentNotifyThread类负责创建三个ChildNotifyThread类的对象,每一个ChildNotifyThread类的实例对象都持有ParentNotifyThread.WAIT_CHILEOBJECT对象的“钥匙”,并通过wait方法退出ParentNotifyThread.WAIT_CHILEOBJECT对象的独占状态(但是不归还锁),如下图所示:
然后我们通过ParentNotifyThread类中的ParentNotifyThread.WAIT_CHILEOBJECT.notify()方法解除阻塞状态:
synchronized (ParentNotifyThread.WAIT_CHILEOBJECT) {
ParentNotifyThread.WAIT_CHILEOBJECT.notify();
}
以上代码的执行效果如下所示:
0 [Thread-1] INFO test.thread.notify.ChildNotifyThread - 线程14启动成功,准备进入等待状态
1 [Thread-2] INFO test.thread.notify.ChildNotifyThread - 线程15启动成功,准备进入等待状态
1 [Thread-3] INFO test.thread.notify.ChildNotifyThread - 线程16启动成功,准备进入等待状态
87285 [Thread-1] INFO test.thread.notify.ChildNotifyThread - 线程14被唤醒!
实际上,我们只知道有三个ChildNotifyThread类的实例对象处于等待ParentNotifyThread.WAIT_CHILEOBJECT对象的“锁芯”空闲;我们并不知道ParentNotifyThread.WAIT_CHILEOBJECT.notify()方法会将ParentNotifyThread.WAIT_CHILEOBJECT对象的“锁芯”(独占权)交给这三个线程的哪一个线程(这个决定过程是由操作系统完成的)。而且我们还知道,ParentNotifyThread.WAIT_CHILEOBJECT.notify()方法只会唤醒等待ParentNotifyThread.WAIT_CHILEOBJECT对象“锁芯”(独占权)的三个ChildNotifyThread类的实例对象中的一个。
实际上理解了notify()方法的工作情况,就不难理解notifyAll()方法的工作情况了。接下来,同样是以上小节的代码,我们将ParentNotifyThread类中的ParentNotifyThread.WAIT_CHILEOBJECT.notify()方法,替换成ParentNotifyThread.WAIT_CHILEOBJECT.notifyAll()方法。如下代码片段所示:
synchronized (ParentNotifyThread.WAIT_CHILEOBJECT) {
ParentNotifyThread.WAIT_CHILEOBJECT.notifyAll();
}
然后我们观察代码的执行结果:
0 [Thread-2] INFO test.thread.notify.ChildNotifyThread - 线程15启动成功,准备进入等待状态
0 [Thread-1] INFO test.thread.notify.ChildNotifyThread - 线程14启动成功,准备进入等待状态
0 [Thread-3] INFO test.thread.notify.ChildNotifyThread - 线程16启动成功,准备进入等待状态
26834 [Thread-3] INFO test.thread.notify.ChildNotifyThread - 线程16被唤醒!
30108 [Thread-1] INFO test.thread.notify.ChildNotifyThread - 线程14被唤醒!
35368 [Thread-2] INFO test.thread.notify.ChildNotifyThread - 线程15被唤醒!
我们看到这样一个事实:在系统中等待arentNotifyThread.WAIT_CHILEOBJECT对象锁的“锁芯”(独占权)的三个线程被依次唤醒(依次得到独占权)。
(接下文)
标签:
原文地址:http://blog.csdn.net/yinwenjie/article/details/50491222