1、多线程基础

61 阅读4分钟

进程与线程的基本概念

进程产生的背景

批量操作系统指令运行方式仍然为串行的,内存中只能同时存在一个程序。 进程的提出:应用程序在内存中分配空间,正在运行的进程之间互不打扰(操作系统并发成为可能)

备注:程序为能够完成某项任务的代码集合。

线程与进程的区别

线程让进程内部并发成为可能。

  • 进程之间的数据是隔离的,各个进程之间互不打扰,数据共享复杂,同步简单,而线程相反。
  • 一个进程崩溃,不影响其他进程 ,一个线程崩溃可能导致程序崩溃
  • 进程的创建与销毁需要保存寄存器与栈信息还需要资源分配与页调度。线程只需要保存寄存器与栈信息。

总结:

  • 本质区别是是否占用内存资源与其他资源(例如io)
  • 进程是操作系统进行资源分配的基本单位,线程是操作系统任务调度的基本单位(cup时间片)

创建线程的几种方法

继承Thread

Thread thread = new Thread("线程一");
thread.start();

注意:程序中调用start() 方法,虚拟机会为我们创建一个线程,当线程获取到时间片的时候会调用重写的run() 方法。且start方法只能调用一次,否则会报错。

实现Runable接口

public class Demo {
    
    public static class MyThread implements Runnable {

        @Override
        public void run() {
            System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start();
    }
}

结果:

image.png

备注:实现接口的方式灵活性更高

Callable接口

  • 可以获取线程内任务返回值,有返回值,支持泛型
  • 一般配合ExcutorService使用,可以使用submit方法来让一个Callable接口执行。它会返回一个Future,通过这个Future的get方法得到结果
public class Demo {

    public static class MyCallable implements Callable<String> {


        @Override
        public String call() throws Exception {
            return "我是callable";
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(new MyCallable());
        System.out.println(future.get());
    }
}

结果:

image.png

Future接口

为了让任务有能够取消的功能,就使用Callable来代替Runnable。如果为了可取消性而使用 Future但又不提供可用的结果,则可以声明 Future<?>形式类型、并返回 null作为底层任务的结果。

FutureTask实现类

public class Demo {

    public static class MyCallable implements Callable {

        @Override
        public Object call() throws Exception {
            return "测试FutureTask";
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        FutureTask<String> futureTask = new FutureTask<String>(new MyCallable());
        Future<?> submit = executorService.submit(futureTask);
        System.out.println(submit.get());
        System.out.println(futureTask.get());
    }
}

结果:

image.png

  • future 接口实现类,也是RunableFuture实现类(继承了Runable与Future接口)
  • 调用submit方法是没有返回值的。这里实际上是调用的submit(Runnable task)方法,
  • 这里是使用FutureTask直接取get取值

总结: 在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次

线程组与线程优先级

线程组

使用线程组对线程进行批量管理,每个thread必然存在于threadgroup中,没有显示指定的话就设置自己为线程组。

线程及线程组优先级

  • 优先级是是给操作系统的建议,真正调用还是操作系统调度算法。
  • 非守护线程结束了,守护线程也跟着结束。
  • 某个线程的优先级大于他线程组的优先级,那么该线程优先级会失效,取而代之的是线程组的最大优先级。

线程组常用方法

//获取线程组的名称
Thread.currentThread().getThreadGroup().getName();
//复制线程组
// 获取当前的线程组
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
// 复制一个线程组到一个线程数组(获取Thread信息)
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);

线程组统一异常处理

ThreadGroup threadGroup1 = new ThreadGroup("group1") {
            // 继承ThreadGroup并重新定义以下方法
            // 在线程成员抛出unchecked exception
            // 会执行此方法
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + ": " + e.getMessage());
            }
        };

        // 这个线程是threadGroup1的一员
        Thread thread1 = new Thread(threadGroup1, new Runnable() {
            public void run() {
                // 抛出unchecked异常
                throw new RuntimeException("测试异常");
            }
        });

        thread1.start();

线程组数据结构

线程组还可以包含线程组,可以看ThreadGroup的源码,了解一下。