Thread-基础知识

1,217 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

线程在编程中一直是一个绕不开的话题,它是一把双刃剑,一面是性能一面是复杂。要想挥好这把剑披荆斩棘,就要学会如何使用它。

双刃剑.png

这把剑可以为我们带来什么呢?

  • 可以提高资源利用率
  • 赋予不同程序的对于计算机上的资源的使用权
  • 多任务运行的可以提高程序间交互的效率

进程和线程

进程和线程总是新手学习Thread之前需要先行了解的东西

进程:是操作系统级别的,负责向操作系统申请资源。它是系统进行资源分配和调度的一个独立单位。
可以简单的理解我们在系统上打开一个QQ程序就是创建一个进程的过程。

线程:是在进程中独立运行的子任务,一个进程下面可以有多个线程。例如,QQ运行的同时也启动了很
多子任务,有下载文件线程、传输数据线程、发送表情线程等。

线程状态

在学习Thread之前希望各位小伙伴先了解一下线程在整个生命周期内究竟有哪些状态,如下图所示:

image.png
  • NEW:表示刚刚创建的线程,还没开始执行,需要等到线程的start()方法调用,才开始执行。
  • RUNNABLE:表示线程所需的一切资源都已经准备完毕,处于执行状态
  • BLOCKED:如果线程在执行过程中遇到synchronized同步块,就会进入阻塞状态
  • WAITING:等待状态,用于完成一些特殊事件,通过wait()方法等待的线程可用notify()方法唤醒
  • TIMED WAITING:和WAITING类似带时间的等待
  • TERMINATED:当线程执行完毕后就就进入TERMINATED终止状态

这可不是我乱画的,大家可以看下源码中的状态定义

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

线程的基本操作

新建线程

通过如下代码我们可以快速新建一个线程对象

Thread t1 = new Thread();
t1.start();

Thread类还有一个非常重要的构造方法,它允许我们传入一个Runnable接口的实例,在start()方法调用时,新的线程就会执行Runnable.run()方法。

public Thread(Runnable target) {
    this(null, target, "Thread-" + nextThreadNum(), 0);
}

终止线程(stop)

一般情况线程执行完毕就会结束不需要手工关闭。但人家语言设计者考虑的非常全面,总会有需要的时候,我们可以看到Thread提供了一个stop方法,调用该方法可以立即将一个线程终止。不过这个方法已经被标注为废弃方法了。

@Deprecated(since="1.2")
public final void stop() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        checkAccess();
        if (this != Thread.currentThread()) {
            security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
        }
    }
    // A zero status value corresponds to "NEW", it can't change to
    // not-NEW because we hold the lock.
    if (threadStatus != 0) {
        resume(); // Wake up thread if it was suspended; no-op otherwise
    }

    // The VM can handle all thread states
    stop0(new ThreadDeath());
}

那我们该如何终止一个线程呢?其实方法很简单,我们只要定义好线程合适退出就可以了。

public class StopThread extends Thread{

    volatile boolean stopme = false;

    public void stopMe(){
        stopme = true;
    }

    @Override
    public void run() {
        while (true){
            if (stopme){
                System.out.println("exit");
                break;
            }
            //以下是自己的业务代码
        }
    }
}

中断线程(interrupt)

在Java中,线程中断是一种重要的线程协作机制,线程中断并不会使线程立即退出,而是给线程发送一个通知,告诉它有人希望你退出了。至于目标线程接收到通知后如何处理,则由目标线程自己决定。有三个方法与线程中断有关,看起来有点像,但意义不一样,大家在使用时还需注意:

public void interrupt()                     //中断线程
public boolean isInterrupted()              //判断是否被中断
public static boolean interrupted()         //判断是否被中断,并清除当前中断消息

知道这个中断线程的方法后,上面的代码就可以进行优化了,Thread.currentThread.isInterrupted()函数判断当前线程是否被中断了,如果是,则退出循环体结束线程。

public class InterruptThread extends Thread{

    @Override
    public void run() {
        while (true){
            if (Thread.currentThread.isInterrupted){
                System.out.println("exit");
                break;
            }
            Thread.yield();
        }
    }
}

等待(wait)和通知(notify)

为了支持多线程之间协作,JDK提供了两个非常重要的接口线程:等待wait()方法和通知notify()方法。有意思的是这两个方法不是Thread类中的,而是输出Object类。意味着任何对象都可以调用这两个方法。

public final void wait() throws InterruptedException
public final native void notify()

当一个对象实例调用wait()方法后,当前线程就会在这个对象上等待。直到这个对象调用了notify()方法为止。这时,这个object就成为了多个线程之间有效的通信手段。

ps:wait()和notify()在使用时,都必须提前获得对象锁

image.png

挂起(suspend)和恢复(resume)

线程的挂起suspend和恢复执行resume是一对相反的操作哦,被挂起的线程必须等到resume方法执行后才能继续执行。 不过这两个方法也已经标注为废弃方法,故了解即可,并不推荐使用

等待线程结束(join)和谦让(yeild)

join:有时候一个线程的输入可能依赖于另一个或多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。JDK提供了join()操作来支持此功能。第一个join方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。第二个带参数的方法,表示等待一个传入的时间,如果超过这个时间目标线程还在执行,当前线程就不在等待而继续往下执行。

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

简单示例如下:

public class JoinMain {

    public volatile static int i = 0;

    public static class AddThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100000000; i++) {

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

yield:有时候可能会出现需要让一些线程让出CPU的使用权,从而让其他线程得到更多机会去执行。这个时候就可以调用如下方法:

public static native void yield();

但需要注意的是,让出CPU并不表示当前线程不再执行了,它还是会进行CPU资源争夺,但能否再次被分配到就不一定了。