基本概念

进程和线程

  • 进程(Process):程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源。
  • 线程(Thread):CPU调度的最小单位,必须依赖进程而存在。

并行和并发

  • 并行(Paralle):同一时刻,可以同时处理事情的能力。
  • 并发(Concurrent):与单位时间相关,在单位时间(如1s)内可以处理事情的能力。

并发的意义与问题

意义:

  • 充分利用cpu的资源,加快用户响应的时间。
  • 程序模块化,异步化。

问题:

  • 线程共享资源,存在冲突,出现线程安全问题。
  • 容易导致线程的死锁。
  • 启用过多的线程,有可能造成计算机宕机。

线程的实现方式

Java中实现多实现线程的方式到底有几种?大部分人会说有 2 种、3 种或是 4 种,很少有人会说有 1 种。我们接下来看看它们具体指什么?

实现Runnable接口

实现Runnable接口的类无法直接启动,必须借助Thread类才能启动。

package com.morris.concurrent.thread.impl;

/**
 * 线程的实现方法之一:实现runnable接口
 */
public class ImplementRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("This thread is implement Runnable interface.");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new ImplementRunnable()); // 无法直接启动,需借助Thread才能启动
        thread.start();
    }
}

继承Thread类

继承Thread类的线程可直接启动。

package com.morris.concurrent.thread.impl;

/**
 * 线程的实现方法之一:继承Thread类
 */
public class ExtendThread extends Thread {
    @Override
    public void run() {
        System.out.println("This thread is extend Thread class.");
    }

    public static void main(String[] args) {
        ExtendThread thread = new ExtendThread();
        thread.start(); // 可直接启动
    }
}

实现Callable接口

继承Thread类和实现Runnable接口这两种方式启动的线程执行完后的结果是无法获取的。

而实现Callable接口可以拿到线程执行完后返回的结果。

package com.morris.concurrent.thread.impl;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 线程的实现方法之一:实现Callable接口
 */
public class ImplementCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("This thread is implement Runnable interface.");
        return "morris";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new ImplementCallable());
        Thread thread = new Thread(futureTask);
        thread.start();
        String result = futureTask.get(); // 阻塞等待结果
        System.out.println(result);
    }
}

线程池创建线程

可以通过线程池快速的创建多个线程:

package com.morris.concurrent.thread.impl;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用线程池创建线程
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(()-> System.out.println("This thread is create by thread pool."));
        executorService.shutdown();
    }
}

接下来,我们深入解析线程池中的源码,来看看线程池是怎么创建线程的?

static class DefaultThreadFactory implements ThreadFactory {
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
    }
 

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);

        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

对于线程池而言,本质上是通过线程工厂创建线程的,默认采用DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过new Thread()创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程的本质上还是通过new Thread()实现的。

总结

  • Thread类底层实现了Runnable接口。
  • 实现Runnable接口的线程必须借助Thread类才能启动。
  • 需要接受返回值可以使用Callable。
  • 实现Runnable接口可以避免由于Java的单继承特性而带来的局限,增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
  • Thread负责线程的启动执行,Runnable接口负责具体的业务逻辑,符合单一职责原则,使用了策略模式。
  • 在实际应用场景中,一般不会单独直接创建线程,而是使用线程池来创建线程。

事实上创建线程只有一种方式,就是构造一个Thread类,这是创建线程的唯一方式。

查看jvm启动的线程

当运行main方法后,jvm底层到底启动了多少个线程?

可以使用java api来查看线程的情况。

package com.morris.concurrent.thread.impl;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.TimeUnit;

/**
 * 查看程序运行时,jvm底层到底产生了多少个线程
 */
public class ThreadViewDemo {

    public static void main(String[] args) throws InterruptedException {

        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);

        for (ThreadInfo threadInfo : threadInfos) {
            System.out.printf("ThreadName:%s, status:%s \n", threadInfo.getThreadName(), threadInfo.getThreadState());
        }

        TimeUnit.SECONDS.sleep(30);
    }
}

运行结果如下:

ThreadName:Monitor Ctrl-Break, status:RUNNABLE 
ThreadName:Attach Listener, status:RUNNABLE 
ThreadName:Signal Dispatcher, status:RUNNABLE 
ThreadName:Finalizer, status:WAITING 
ThreadName:Reference Handler, status:WAITING 
ThreadName:main, status:RUNNABLE

从运行结果可以发现,jvm不仅启动一个main线程来运行我们的程序,还启动了一些守护线程,如垃圾回收线程、RMI线程等。

也可借助jdk自带的工具jconsole来查看:

线程的实现方式_多进程