线程基础:线程(3)——JAVA中的基本线程操作(中)

(接上文《线程基础:线程(2)——JAVA中的基本线程操作(上)》)

1-4、注意synchronized关键字的使用

在前面的文章中我们主要讲解的是线程中“对象锁”的工作原理和操作方式。在讲解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关键字的方法中,针对某个对象的操作不一定是线程安全的!

2、JAVA中的基本线程操作

这是前文中已经给出的线程状态切换图例,可能有的读者还不能完全理解其中的切换条件,没关系从本章节开始我们将详细介绍JAVA中如何进行这些线程状态的操作。

除了上一章节在讲解“对象锁”的时候已经提到的wait、wait(time)操作以外,本章节将讲解notify、notifyAll、interrupt、join和sleep等操作。

2-1、notify和notifyAll操作

在JAVA JDK中,对于notify方法和notifyAll方法的解释分别是:

  • notify:

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.

  • notifyAll:

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方法的工作现象,下面我会为这两个方法分别给出一段代码,并进行详细解释。

2-1-1、notify方法的工作情况

  • ParentNotifyThread类:
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();
            }
        }
    }
}
  • ChildNotifyThread类:
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类的实例对象中的一个

2-1-2、notifyAll方法的工作情况

实际上理解了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对象锁的“锁芯”(独占权)的三个线程被依次唤醒(依次得到独占权)

(接下文)

时间: 12-05

线程基础:线程(3)——JAVA中的基本线程操作(中)的相关文章

线程基础:JDK1.5+(9)——线程新特性(中)

(接上文<线程基础:JDK1.5+(8)--线程新特性(上)>) 3.工作在多线程环境下的"计数器": 从这个小节開始,我们将以一个"赛跑"的样例.解说JDK1.5环境下一些线程控制工具(包含Semaphore.CountDownLatch和java.util.concurrent.atomic子包),而且复习这个专题讲到的知识点:同步快.锁.线程池.BlockingQueue.Callable等. 3-1. 赛跑比赛的需求 如今您不仅能够通过我们已经介

线程基础:JDK1.5+(10)——线程新特性(下)

(接上文<线程基础:JDK1.5+(9)--线程新特性(中)>) 3-4.CountDownLatch:同步器 上文中我们主要讲解了JDK1.5+中提供的一个重要工具:Semaphore信号量,并且用这个工具第一次实现了"100米赛跑"的需求.在第一次的实现中,我们还运用了"线程专栏"中已介绍的多个知识点,包括锁.线程池.队列.Callable接口等. 但实际上第一次实现的"100米赛跑"的需求,离我们真正的需求还有一定的距离:在我们

线程基础:JDK1.5+(8)——线程新特性(上)

1.概要 如果您阅读JAVA的源代码,出现最多的代码作者包括:Doug Lea.Mark Reinhold.Josh Bloch.Arthur van Hoff.Neal Gafter.Pavani Diwanji等等.其中java.util.concurrent包中出现的基本都是Doug Lea的名字.Doug Lea,是对Java影响力最大的个人,直接贡献的设计包括java的Collections和util.concurrent. JDK1.5中一个重要特性就是util.concurrent

7种创建线程方式,你知道几种?线程系列Thread(一)

前言 最近特别忙,博客就此荒芜,博主秉着哪里不熟悉就开始学习哪里的精神一直在分享着,有着扎实的基础才能写出茁壮的代码,有可能实现的逻辑有多种,但是心中必须有要有底哪个更适合,用着更好,否则则说明我们对这方面还比较薄弱,这个时候就得好好补补了,这样才能加快提升自身能力的步伐,接下来的时间会着重讲解线程方面的知识.强势分割线. 话题乱入,一到跳槽季节想必我们很多人就开始刷面试题,这种情况下大部分都能解决问题,但是这样的结果则是导致有可能企业招到并非合适的人,当然作为面试官的那些人们也懒得再去自己出一

线程基础:线程池(6)——基本使用(中)

(接上文:<线程基础:线程池(5)--基本使用(上)>) 3-4.JAVA主要线程池的继承结构 我们先来总结一下上文中讨论过的内容,首先就是JAVA中ThreadPoolExecutor类的继承结构.如下图所示: ThreadPoolExecutor:这个线程池就是我们这两篇文章中介绍的重点线程池实现.程序员可以通过这个线程池中的submit()方法或者execute()方法,执行所有实现了Runnable接口或者Callable接口的任务:ThreadPoolExecutor对于这些任务的执

并发编程总结——java线程基础1

最近没事,顺便看看java并发编程的东西,然后总结纪录下来,大家如果能看到帮忙指正指正哈哈,另外一方面也为以后自己回顾的时候可以看看. 关于并发编程,准备从几个点切入: 1.java线程几本知识 2.juc原子类 3.锁 4.juc集合 5.线程池 ------------------------------------------------------------------- 分割线开始.... 说到多线程,java中超父类Object中有wait()和notify()方法,包括Runna

java学习笔记14--多线程编程基础1

本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为一个进程,例如:用字处理软件编辑文稿时,同时打开mp3播放程序听音乐,这两个独立的程序在同时运行,称为两个进程 进程要占用相当一部分处理器时间和内存资源 进程具有独立的内存空间 通信很不方便,编程模型比较复杂 多线程 一个程序中多段代码同时并发执行,称为多线程,线程比进程开销小,协作和数据交换容易

Java中事件分发线程(EDT)与SwingUtilities.invokeLater相关总结

前言:这篇文章严格来说不算原创,算是我对这方面知识的一点小结,素材来至其他网友.当然我在我写的C段查询工具也用到了这方面的东西,不过由于代码太多不方便用作事例,因此用了他人的素材总结一下,望理解O(∩_∩)O~ 一 Swing线程基础 一个Swing程序中一般有下面三种类型的线程:    * 初始化线程(Initial Thread)    * UI事件调度线程(EDT)    * 任务线程(Worker Thread)每个程序必须有一个main方法,这是程序的入口.该方法运行在初始化或启动线程

Java中的守护线程和非守护线程(转载)

<什么是守护线程,什么是非守护线程> Java有两种Thread:“守护线程Daemon”(守护线程)与“用户线程User”(非守护线程). 用户线程:非守护线程包括常规的用户线程或诸如用于处理GUI事件的事件调度线程,Java虚拟机在它所有非守护线程已经离开后自动离开. 守护线程:守护线程则是用来服务用户线程的,比如说GC线程.如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去.(操作系统里面是没有所谓的守护线程的概念,只有守护进程一说,但是Java语言机制是构建在JVM