Skip to content

进程和线程

进程和线程的区别

进程是操作系统进行资源分配的最小单位。
操作系统通常使用进程来表示独立的应用程序实例。比如,你的计算机上可能同时运行着浏览器、文本编辑器、音乐播放器等多个进程。
线程必须依赖于进程而存在,是CPU调度的最小单位,它们共享相同的进程内存空间和系统资源。
多线程的使用方式有助于提高程序的性能。例如,一个文字处理软件的进程可能包括一个主线程,用于处理用户界面内容显示,同时还有一个后台线程,负责自动保存文件。

Java中创建线程的四种方式

继承Thread类

继承Thread类
java
public class ThreadExtend extends Thread {

    public static void main(String[] args) {
        ThreadExtend threadExtend = new ThreadExtend();
        threadExtend.start();
    }

    @Override
    public void run() {
        System.out.println("继承的Thread类run方法重写");
    }

}

总结:重写的是run()方法,而不是start()方法,但是占用了继承的名额,Java中的类是单继承的。

TIP

Java中的接口是可以多继承的。

实现Runnable接口

实现Runnable接口
java
public class ThreadImplement implements Runnable {

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

    @Override
    public void run() {
        System.out.println("重写Runnable接口的run方法");
    }

}

总结:实现Runnable接口,实现run()方法,使用依然要用到Thread,这种方式更常用。

有时候,会直接使用匿名内部类的方式或Lambda表达式的方式:

java
public class AnonymousClass {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                System.out.println("匿名内部类实现run方法");
            }
        });

        thread.start();
    }
}
java
public class AnonymousClass {

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Lambda表达式实现run方法");
        });

        thread.start();
    }
}

实现Callable接口

实现Callable接口
java
public class CallableImplement implements Callable<String> {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new CallableImplement());
        Thread thread = new Thread(futureTask);
        thread.start();
        String result = futureTask.get();
        System.out.println(result);
    }

    @Override
    public String call() throws Exception {
        return "实现call方法";
    }

}

总结:实现Callable接口,实现call()方法,需要使用Thread+FutureTask配合,这种方式支持拿到异步执行任务的结果。

利用线程池来创建线程

线程池创建线程
java
public class ThreadPoolImplement implements Runnable {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new ThreadPoolImplement());
    }

    @Override
    public void run() {
        System.out.println("重写Runnable接口的run方法");
    }
}

总结:实现Callable接口或Runnable接口都可以,由ExecutorService创建线程。

WARNING

注意,工作中不建议使用Executors来创建线程池

以上几种方式,底层都是基于Runnable。

不建议使用Executors来创建线程池

FixedThreadPool

当使用Executors创建FixedThreadPool时,对应的构造方法为:

FixedThreadPool构造方法
java
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

发现创建的队列为LinkedBlockingQueue,是一个无界阻塞队列,如果使用该线程池执行任务,如果任务过多就会不断的添加到队列中,任务越多占用的内存就越多,最终可能耗尽内存,导致OOM。

SingleThreadExecutor

当使用Executors创建SingleThreadExecutor时,对应的构造方法为:

SingleThreadExecutor构造方法
java
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

发现也是LinkedBlockingQueue,所以同样会耗尽内存。

总结

除开有可能造成OOM之外,我们使用Executors来创建线程池也不能自定义线程的名字,不利于排查问题,所以建议直接使用ThreadPoolExecutor来定义线程池,这样可以灵活控制。