线程的基本使用

线程的引入

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

线程又被称为轻型进程,是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{
    @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 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");
    }
}

输出结果:


线程的基本使用_第1张图片
没有加 join() 方法输出结果.png

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


线程的基本使用_第2张图片
加 join() 方法输出结果.png

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

参考资料

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

你可能感兴趣的:(线程的基本使用)