Java多线程2:线程的使用及其生命周期

一、线程的使用方式

  1、继承Thread类,重写父类的run()方法

  优点:实现简单,只需实例化继承类的实例,即可使用线程
  缺点:扩展性不足,Java是单继承的语言,如果一个类已经继承了其他类,就无法通过这种方式实现自定义线程

public class Thread01 extends Thread{
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread01();
        thread.start();
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

  结果:

......................
......................
main
main
main
main
main
Thread-0
Thread-0
Thread-0
......................
......................

  可以看到两个线程并不是交替运行的,这也是正常的。解释之前先说一下多线程并发与并行的区别。

  1、并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干片,使多个进程快速交替的执行。

  

  如上图所示,并发就是只有一个CPU资源,程序(或线程)之间要竞争得到执行机会。图中的第一个阶段,在A执行的过程中,B、C不会执行,因为这段时间内这个CPU资源被A竞争到了,同理,第二阶段只有B在执行,第三阶段只有C在执行。其实,并发过程中,A、B、C并不是同时进行的(微观角度),但又是同时进行的(宏观角度)。

  2、并行(parallellism):指在同一时刻,有多条指令在多个处理器上同时执行
  

  如图所示,在同一时刻,ABC都是同时执行(微观、宏观)

  通过多线程实现并发,并行:
  ➤ java中的Thread类定义了多线程,通过多线程可以实现并发或并行。
  ➤ 在CPU比较繁忙,资源不足的时候(开启了很多进程),操作系统只为一个含有多线程的进程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。
  ➤ 在CPU资源比较充足的时候,一个进程内的多线程,可以被分配到不同的CPU资源,这就是通过多线程实现并行。
  ➤ 至于多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所以,如果有人问我我所写的多线程是并发还是并行的?我会说,都有可能。
  ➤ 不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源

  所以测试结果中两个线程并没有明显的交替运行就很好解释了。

  所谓的多线程,指的是两个线程的代码可以同时运行,而不必一个线程需要等待另一个线程内的代码执行完才可以运行。对于单核CPU来说,是无法做到真正的多线程的,每个时间点上,CPU都会执行特定的代码,由于CPU执行代码时间很快,所以两个线程的代码交替执行看起来像是同时执行的一样。那具体执行某段代码多少时间,就和分时机制系统有关了。分时系统把CPU时间划分为多个时间片,操作系统以时间片为单位片执行各个线程的代码,越好的CPU分出的时间片越小。所以看不到明显效果也很正常,一个线程打印100句话本来就很快,可能在分出的时间片内就执行完成了(也不一定是并发或者是并行)。所以,最简单的解决办法就是把for循环的值调大一点就可以了(也可以在for循环里加Thread.sleep方法,这个之后再说)。

  2、实现Runnable接口,重写run()方法,通过其实现类使用Thread

  优点:

  • 扩展性好,可以在此基础上继承其他类或实现其他接口,实现其他必需的功能
  • 对于多线程共享资源的场景,具有天然的支持,适用于多线程处理一份资源的场景

  缺点:构造线程实例的过程相对繁琐一点点

public class Thread01 implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Runnable runnable = new Thread01();
        //通过Runnable接口的实现类构建Thread
        Thread thread = new Thread(runnable);
        thread.start();
        for(int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

  结果:

......................
......................
main
main
main
main
main
Thread-0
Thread-0
Thread-0
......................
......................

  如上所述,不再做分析。

  3、实现Callable接口,重写call()方法,通过Runnable实现类使用Thread

public class Thread01 implements Callable<String>{

    @Override
    public String call() throws Exception {
        return "hello world!!!";
    }
}
public class Test {
    public static void main(String[] args) {
        Callable<String> callable = new Thread01();
        //FutureTask是Runnable的实现类
        FutureTask<String> futureTask = new FutureTask<String>(callable);
        //通过Runnable接口的实现类构建Thread
        Thread thread = new Thread(futureTask);
        thread.start();
        String result = null;
        try {
            result = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(result);
    }
}

  结果:

hello world!!!

  优点:

  • 扩展性好
  • 支持多线程处理同一份资源
  • 具备返回值以及可以抛出受检查异常

  缺点:

  • 相较于实现Runnable接口的方式,较为繁琐

  三种实现方式对比的关键就在于extends和implements的对比,当然是后者好。因为第一,继承只能单继承,实现可以多实现;第二,实现的方式对比继承的方式,也有利于减小程序之间的耦合。因此,多线程的实现几乎都是使用的Runnable接口的方式。

参考资料:

  [Java 多线程技术](一)线程和进程以及并行和并发的概念

猜你喜欢

转载自www.cnblogs.com/zfyang2429/p/10512737.html