线程的基本使用

线程的引入

在线程未提出之前,操作系统中都是以进程作为能拥有资源和独立运行的基本单位的。但是为了提高系统内程序并发执行的程度,减少程序在并发执行时所付出的时空开销,从而可进一步提高系统的吞吐量,人们引入了线程的概念。为什么?这是因为线程只拥有一些必要的基本资源,做到“轻装上阵”。

线程又被称为轻型进程,是CPU调度和分派的最小单位。一个进程中至少还有线程,比如在 Android 操作系统中,一个应用就是一个进程,而一个应用至少含有一个线程,即我们平常所说的主线程、UI 线程。而且线程可以共享进程资源,比如内存、打开的文件。

线程的状态

和进程一样,在各线程之间也存在着共享资源和相互合作的制约关系,致使线程在运行是也具有下述三种基本状态。

  • 执行状态,表示线程正获得CPU而执行。
  • 就绪状态,指线程已具备了各种执行条件,一旦获得CPU便可执行的状态。
  • 阻塞状态,指线程在执行中因某事件而受阻,处于暂停执行时的状态。

Java中线程的创建(下文关于线程的阐述都是基于Java)

一、 继承 Thread 类创建线程类

1.创建一个 Thread 的子类,重写该类的 run() 方法,run() 方法就是线程要完成的任务。
2.创建 Thread 子类的对象
3.调用线程对象的 start() 方法来启动该线程。
class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println(getClass().getSimpleName() + " 正在执行");
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
二、 实现 Runnable 接口

1.定义 runnable 接口的实现类,并重写该接口的 run() 方法,把要执行逻辑写在 run() 里面
2.创建 runnable 实现类的对象,并把它提交给一个 Thread 的构造器创建一个 Thread 对象。
3.调用 Thread 对象的 start 方法来启动该线程。

class MyRunnable implements Runnable {    
    @Override
    public void run() {
        System.out.println(getClass().getSimpleName() + "正在执行");
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);
        myThread.start();
    }
}
三、 通过Callable 和 Future 创建线程
  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FuturaTask 类来包装 Callback 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  3. 使用 FutureTask 对象(本质上是 runnable)作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值,调用 get() 方法会阻塞线程。
class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(getClass().getSimpleName() + "正在执行");
        return "this is " + getClass().getSimpleName();
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        new Thread(futureTask).start();
        try {
            System.out.println("返回值:" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

线程的常用方法

线程让步 Thread.yield()

Thread.yield() 的调用时对线程调度器(Java线程机制的一部分,可以将 CPU 从一个线程转移到另一个线程中)的一种建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”。这完全是选择性的,但是这里使用它在这些示例中产生更加有趣的输出:你更有可能会看到任务换进换出的证据。因为线程调度机制是非确定性的。

线程休眠 Thread.sleep()
class SleepTask implements Runnable{

    private int count = 5;

    @Override
    public void run() {
        try {
            while (count-- > 0){
                System.out.println(getClass().getSimpleName() +":count = "+ count);
                //Thread.sleep(1000);
                TimeUnit.MILLISECONDS.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class SleepingTest {
    public static void main(String[] args) {
        new Thread(new SleepTask()).start();
    }
}

可以看出,线程休眠只需调用 sleep() 方法,值得注意的是对 sleep() 的调用可以抛出 InterruptException 异常,它在 run 中被捕获,因为异常不能跨线程传播回 main(),所以你必须在本地处理所有在任务内部产生的异常。

线程加入 join(),实例方法,非静态方法

一个线程可以在其他线程之上调用 join() 方法,其效果是阻塞起来直到第二个线程结束才继续执行。如果某个线程在另一个线程 t 上调用 t.join(),此线程将被挂起,直到目标线程 t 结束才恢复(即 t.isAlive 返回为假)。
也可以在调用 join()时带上一个超时参数,这样如果目标线程在这段时间到期还没有结束的话,join()方法总能返回。
我们首先看下没有加 join()方法的例子:

class JoinRunnable implements Runnable{
    @Override
    public void run() {
        try {
            for (int i = 1; i <= 5; i++) {
                Thread.sleep(1000);
                System.out.println(getClass().getSimpleName() + " i="+ i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class JoinTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new JoinRunnable());
        t1.start();
       /* try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        System.out.println("This is main thread");
    }
}

输出结果:


没有加 join() 方法输出结果.png

不出所料,我们创建了一个子线程执行操作,并不会影响主线程的继续执行。但是当我们把上面代码中的注释去掉,输出结果就大不一样,如下图所示:


加 join() 方法输出结果.png

是的,主线程阻塞在那里直到子线程执行完才继续执行。至于加一个超时参数也就很好理解了,比如超时参数的时长是 1s ,那就是主线程会阻塞在那里等 1s 后就继续执行了。同理,把主线程替换成其他子线程也是同样的效果。

参考资料

[1] (美)艾克尔(Eckel,B.).Java编程思想[M]. 北京 : 机械工业出版社 , 2007.6.
[2]Java 创建线程的3种方式

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 7,853评论 3 53
  • 线程的创建 java创建线程有两种方法​​ 创建Thread的子类 实现Runnable接口 Thread和Run...
    lialzm阅读 3,085评论 0 0
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 4,987评论 0 6
  • 大雁被北风吹向南方 却招来了一群乌鸦 你醉红着脸表白 对我的一腔热情 却死盯着那些 盘旋在头顶的黑鸟…… 落叶在飞...
    远方的记忆阅读 3,118评论 5 3
  • 今晚和老公坐下来平心静气地谈了谈,两个人都放空自己的情绪,把最真实感受说出来。他说自己最近心情不好,在工作上遇到了...
    卜芳阅读 1,046评论 2 6