第一章 并发编程线程基础(一)

第一章 并发编程线程基础

1.1 什么是线程

在讨论什么是线程之前,我们有必要先说一下什么是进程,因为线程是进程中的一个实体,因为线程是不会独立存在的。那么何为进程?进程(Process)就是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础那么何为线程?线程就是进程的一个执行路径,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程中至少含有一个线程,线程中的多个线程共享进程中的资源。


操作系统在分配 资源的时候是把资源分配给进程,但是CPU的这个资源是比较特殊的,它是被分配给了线程,因为真正要占用CPU资源是线程,也就是说线程是CPU分配的基本单位。eg:在Java中,当我们启动了一个main函数的时候,就是启动了一个JVM的进程,而main函数所在的线程就是这个进程里面的一个线程,也称为主线程。


进程和线程之间的关系可以如图所示:
在这里插入图片描述
如上图所示:我们可以看到一个进程里面可以包含多个线程,多个线程可以共享进程里面的堆和方法区的资源,而且每一个线程都有自己的程序计数器和栈区。
程序计数器是一块内存的区域,用来记录线程要执行的指令地址。另外每一个线程都有自己的栈资源,用来存储该线程的局部变量,这些局部变量为该线程私有的,其他的线程是访问不了的,除此之外,栈区域还可以用来存放线程的调用栈帧。
而进程里面的堆区域是进程中最大的一块内存,堆里面的资源是被所有的线程所共享,是在进程被创建的时候分配的,堆里面主要是存放的是使用new关键字创建的实例对象。 而进程里面的方法区则主要用来存放JVM加载的类、常量以及静态变量等信息,也是被所有线程所共享的。

1.2 线程的创建和运行

在Java中主要有3种方式来创建线程:

  1. 继承Thread类,并且重写run()方法;
  2. 实现Runnable接口,并且重写run()方法;
  3. 使用FutureTask方式,实现Callable接口;

首先,我们先来看一下继承Thread类的这种方式:

public class ThreadTest {
    /** 继承Thread并重写run方法 */
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello Thread!");
        }
    }

    public static void main(String[]args){
        /** 1.创建线程 */
        MyThread myThread = new MyThread();
        /** 2.调用start方法启动线程 */
        myThread.start();
    }
}

从上面的这段代码,我们可以看出:MyThread 继承了Thread 类,并且重写了Thread里面的run方法,在main函数里面创建MyThread 的实例,并且调用了实例的start方法来启动线程,其实在调用start方法之后,线程不会立即处于运行的状态,而是当当前线程分配到CPU资源的时候,才会真正的处于运行的状态,当run()方法执行完毕了,该线程才会处于终止的状态

  • 使用继承的这种创建线程的好处就是:在run()方法里面获取当前的线程可以直接用this关键字来进行获取,而可以不用Thread.currentThread()来进行获取。
  • 不好的地方就是因为Java不支持多继承,如果继承了Thread类,就不能继承其他的类。而且多个线程执行同样的代码的时候,需要new多个MyThread 的实例。

实现Runnable接口的方式来创建线程:

代码如下:

public class ThreadTest {
    /** 继承Thread并重写run方法 */
    public static class RunnableTask implements Runnable{
        @Override
        public void run() {
            System.out.println("Hello Thread!");
            Thread.currentThread();
        }
    }

    public static void main(String[]args){
        /** 1.创建实现Runnable接口这个类的实例 */
        RunnableTask runnableTask = new RunnableTask();
        /** 2.创建Thread的这个实例,并且runnableTask传入,把调用start方法启动线程 */
        new Thread(runnableTask).start();
        new Thread(runnableTask).start();
    }
}
  • 上面的这种创建方式共用了一个runnableTask实例里面的run()方法里面的逻辑,而且RunnableTask的这个类还可以继承其他的类。
  • 以上两种创建方式都有一个共同的特点:那就没有返回值。

使用FutureTask方式,实现Callable接口;

代码如下:

public class ThreadTest {
    /** 继承Thread并重写run方法 */
    public static class CallerTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "hello";
        }
    }

    public static void main(String[]args){
        /** 1.创建异步任务 */
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        /** 2.创建Thread实例,并启动线程 */
        new Thread(futureTask).start();
        /** 3.等待线程执行完毕,并返回执行结果 */
        String result = null;
        try {
	            result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  • 如上代码所示:CallerTask类实现了Callable接口,并且重写了call()方法。在main方法里面创建了一个FutureTask实例,然后使用创建好的FutureTask实例作为任务创建线程并且启动它,最后,我们可以通过futureTask.get()来获取执行完毕的返回结果;

小结:

  • 使用继承的方式的好处就是方便传参,我们可以在子类里面添加成员变量,然后可以通过set方法的方式或者是用构造方法的方式来进行参数的传递,不好的地方就是Java不支持多继承,如果继承了Thread类,那么子类则不能继承其他的类。
  • 而如果使用了实现Runnable接口的方式,则只能使用主线程里面被声明为final的变量,而且还可以是继承其他的类。
  • 使用FutureTask的方式则可以获取到任务执行完成后的返回值。

猜你喜欢

转载自blog.csdn.net/weixin_37778801/article/details/83896982