线程的基础

简介: 安全是多线程编程的核心主题,但并不是只要使用多线程就一定会引发安全问题。要了解哪些操作是安全的,哪些是不安全的,就必须先掌握如何使用多线程。不过在操作多线程之前,我们先了解一下多线程的几种状态。线程的状态在Thread的实现中,包含一个名为State的enum类,用来标识线程运行中的各种状态,其中定义了以下几个类型:public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state fo

安全是多线程编程的核心主题,但并不是只要使用多线程就一定会引发安全问题。要了解哪些操作是安全的,哪些是不安全的,就必须先掌握如何使用多线程。不过在操作多线程之前,我们先了解一下多线程的几种状态。

线程的状态
在Thread的实现中,包含一个名为State的enum类,用来标识线程运行中的各种状态,其中定义了以下几个类型:

public enum State {

/**
 * Thread state for a thread which has not yet started.
 */
NEW,

/**
 * Thread state for a runnable thread.  A thread in the runnable
 * state is executing in the Java virtual machine but it may
 * be waiting for other resources from the operating system
 * such as processor.
 */
RUNNABLE,

/**
 * Thread state for a thread blocked waiting for a monitor lock.
 * A thread in the blocked state is waiting for a monitor lock
 * to enter a synchronized block/method or
 * reenter a synchronized block/method after calling
 * {@link Object#wait() Object.wait}.
 */
BLOCKED,

/**
 * Thread state for a waiting thread.
 * A thread is in the waiting state due to calling one of the
 * following methods:
 * <ul>
 *   <li>{@link Object#wait() Object.wait} with no timeout</li>
 *   <li>{@link #join() Thread.join} with no timeout</li>
 *   <li>{@link LockSupport#park() LockSupport.park}</li>
 * </ul>
 *
 * <p>A thread in the waiting state is waiting for another thread to
 * perform a particular action.
 *
 * ...
 */
WAITING,

/**
 * Thread state for a waiting thread with a specified waiting time.
 * A thread is in the timed waiting state due to calling one of
 * the following methods with a specified positive waiting time:
 * <ul>
 *   <li>{@link #sleep Thread.sleep}</li>
 *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
 *   <li>{@link #join(long) Thread.join} with timeout</li>
 *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
 *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
 * </ul>
 */
TIMED_WAITING,

/**
 * Thread state for a terminated thread.
 * The thread has completed execution.
 */
TERMINATED;

}
首先,NEW 和 TERMINATED 是两种特殊的状态,前者表示线程还未开始运行,后者表示线程已经运行完毕。在这两种状态下,对线程进行一些操作是没有意义的,因为线程根本没有运行,也就不会去响应中断、睡眠等请求了。

当执行了start方法之后或者线程正在运行时,线程会进入RUNNABLE状态,这时线程或者正在运行,或者正在等待CPU调度。

BLOCKED表示线程正在等待锁,也就是说此时线程要执行的代码是同步的,有其他线程正在运行此代码,所以线程需要等待获取锁。

WAITING和TIMED_WAITING都表示线程要等待一段时间之后再运行,只是后者会有一个超时处理。

线程的执行就是在以上这些状态中不断切换,当然 NEW 和 TERMINATED 这两种表示QQ号买卖线程起止的状态,在线程的生命周期中只会执行一次。

创建线程
在Java中创建一个线程有两种方式:继承Thread和实现Runnable。其实这两种方式并没有很大的差别,只是Java仅支持单继承,实现Runnable的方式更灵活一些,但是一个Runnable对象本身是无法执行的,需要用一个Thread对象来帮助它启动,就像这样:

new Thread(new MyRunnable()).start();
启动线程
线程的启动要使用start方法,而不是调用Runnable的run方法,不过如果多次调用start方法会抛出异常:

public synchronized void start() {

/**
 * This method is not invoked for the main method thread or "system"
 * group threads created/set up by the VM. Any new functionality added
 * to this method in the future may have to also be added to the VM.
 *
 * A zero status value corresponds to state "NEW".
 */
if (threadStatus != )
    throw new IllegalThreadStateException();

/* Notify the group that this thread is about to be started
 * so that it can be added to the group's list of threads
 * and the group's unstarted count can be decremented. */
group.add(this);

boolean started = false;
try {
    start0();
    started = true;
} finally {
    // ...
}

}
通过判断threadStatus的值来确保线程仅被启动一次,threadStatus对应一个枚举的线程状态,前面已经分析过它。

调用start方法之后并不是说线程马上就开始运行了,因为CPU可能处于忙碌中,没有多余的时间片,因此start方法只是把Thread的状态变为RUNNABLE,等待CPU调度。

线程休眠
如果要让线程停止一段时间再继续运行,可以使用sleep(long millis)方法,sleep会让当前线程进入TIMED_WAITING状态,经过millis时间之后线程会自动苏醒,并重新进入RUNNABLE状态等待系统调度。

yield放弃时间片
当一个线程获得CPU时间片之后,可以通过调用yield方法放弃所获得的时间片,并重新进入RUNNABLE状态等待系统调度。和sleep不同之处在于,我们无法知道yield方法调用后线程会等待多久,因为它和其他所有的线程一样处于RUNNABLE状态,那么CPU就可能在任何时候调度它,也可能永远不会调度它。

中断停止线程
通过start方式可以启动线程,我们很容易就会想到使用stop方法来停止,然而stop方法已经过时了,如下:

@Deprecated
public final void stop() {

// ...

}
在说明如何正确的停止线程之前,我们先说明一下为什么stop方法会过时。stop会释放持有的锁,以使数据可以被其他线程访问,我们知道计算机解决任何问题都不是一蹴而就的,完成一个任务需要很多步骤,每一步都会对结果产生一定的影响,而如果让线程在某一步直接停止,就很可能得到一个不完整的数据。例如给一个用户依次设置姓名和昵称,如果stop正好发生在设置姓名和设置昵称之间,我们得到的用户信息就不再完整。

正确的停止线程方法是使用中断。中断不是说会立即打断线程的执行,而是给线程发送一个中断的信号,由线程来决定何时响应,这样我们就有了足够的时间对数据进行清理。

既然有发送信号,那就一定有办法判断是否接收到了中断信号,Thread中有两种方式来判断是否中断:

public static boolean interrupted() {

return currentThread().isInterrupted(true);

}

public boolean isInterrupted() {

return isInterrupted(false);

}
这两种方式最终都是调用了一个native方法实现的:

/**

  • Tests if some Thread has been interrupted. The interrupted state
  • is reset or not based on the value of ClearInterrupted that is
  • passed.

*/
private native boolean isInterrupted(boolean ClearInterrupted);
可以看到,两种方式的区别在于,interrupted是判断当前的线程是否中断,并且会清除中断标记;而isInterrupted是判断调用此方法的Thread对象是否中断,并且不会清除中断标记。我们要区别线程对象和当前线程的区别,前者表示的就是某个线程,而当前线程表示的是执行的某段代码所处的线程,例如,在main线程中,执行以下两处代码时,当前线程currentThread值是不同的:

public class MyThread extends Thread {

public MyThread() {
    System.out.println("MyThread constructor :" + Thread.currentThread().getName());
}

@Override
public void run() {
    super.run();
    System.out.println("MyThread run :" + Thread.currentThread().getName());
}

}
在主线程中,调用该线程的start方法,可以得到以下的输出结果:

MyThread constructor :main
MyThread run :Thread-
也就是说,调用的代码是在哪个线程中执行的,Thread.currentThread的值就是哪个线程。明白了这个区别,我们就知道了 interrupted 和 isInterrupted 两个方法在何时有区别了。

前面说过,中断只是给线程发送了一个信号,至于如何响应还是由线程决定,可以不理会中断的信号,也可以根据中断的信号做一些数据的处理之后再结束掉当前的线程。

不同状态下的线程对中断的响应方式也有区别,NEW 和 TERMINATED 肯定是不会响应的,RUNNABLE 和 BLOCKED 则是会接收到中断信号,而 WAITING 和 TIMED_WAITING 则是会抛出InterruptedException,并且会清除中断标记,这从 sleep 和 wait 函数的定义中就可以看出:

// Thread.java
/**

  • ...
  • @throws InterruptedException if any thread interrupted the
  • current thread before or while the current thread
  • was waiting for a notification. The interrupted
  • status of the current thread is cleared when
  • this exception is thrown.
  • ...

*/
public static native void sleep(long millis) throws InterruptedException;
// Object.java
public final native void wait(long timeout) throws InterruptedException;
了解了中断的概念,中断一个线程就简单多了。例如在 RUNNABLE 状态下,只需要判断是否接收到了中断信号,就可以在合适的时间中断线程。

public class Worker extends Thread {

public void run() {
    System.out.println("Worker started.");
    while (!isInterrupted()) {
        System.out.println("doing something.");
    }
    System.out.println("Worker stopped.");
}

}
然后,给线程发送一个中断信号:

Worker thread = new Worker();
thread.start();
// 让线程运行起来
Thread.sleep(10);
thread.interrupt();
就可以得到以下的输出:

Worker started.
doing something.
doing something.
doing something.
...
Worker stopped.
如果线程有睡眠等行为时,就可以利用中断异常来停止线程,修改Worker线程的run方法如下:

public class Worker extends Thread {

public void run() {
    System.out.println("Worker started.");
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
        System.out.println("Worker Interrupted.");
    }
    System.out.println("Worker stopped.");
}

}
可以看到如下输出:

Worker started.
java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)
at chapter01.section1.Interrupt$Worker.run(Interrupt.java:42)

Worker Interrupted.
Worker stopped.
线程从睡眠中被打断,并立即结束。

总结
了解线程的基本概念,是学习线程的第一步。但是至此我们只是学会了如何使用一个线程,接下来我们继续研究多个线程交互时,如何处理数据安全的问题。

目录
相关文章
|
6月前
|
消息中间件 Java 调度
Java多线程基础-3:进程与线程间的区别的联系
进程是操作系统中运行的应用程序,具有独立内存空间,包含代码、数据和堆栈,是资源分配的最小单位,而线程是CPU调度的最小单位,是进程内的执行任务,多个线程可共享进程资源。
48 0
|
6月前
|
Java 调度
多线程的基本概念和实现方式,线程的调度,守护线程、礼让线程、插入线程
多线程的基本概念和实现方式,线程的调度,守护线程、礼让线程、插入线程
55 0
|
存储
【线程概念和线程控制】(二)
【线程概念和线程控制】(二)
64 0
|
Linux 编译器 调度
【线程概念和线程控制】(一)
【线程概念和线程控制】(一)
120 0
|
存储 Linux 调度
多线程——线程概念和线程控制
什么是线程,POSIX线程库,线程控制:pthread_create线程创建,pthread_exit线程终止,pthread_join线程回收,pthread_cancel线程取消,pthread_detach线程分离。线程id和地址空间分局,C++语言级别的多线程,二次封装线程库
119 0
多线程——线程概念和线程控制
|
Java Linux API
【多线程】初识线程,基础了解
认识线程、了解Thread类及常见方法、线程状态
|
监控 并行计算 Java
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(四)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(四)
|
Java 调度
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(三)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(三)
|
存储 Java 应用服务中间件
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(一)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(一)
|
监控 Java Linux
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(二)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(二)