一些线程基础问题

tim-qtp...大约 3 分钟多线程

1.并行跟并发有什么区别?

  • 并行:多核 CPU 上的多任务处理,多个任务在同一时间真正地同时执行。
  • 并发:单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行。
=
=

就好像去食堂打饭,并行就是每个人对应一个阿姨,同时打饭;而并发就是一个阿姨,轮流给每个人打饭。

2.线程的几种创建方式

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 实现 Callable 接口

第一种,继承 Thread 类,重写 run()方法,调用 start()方法启动线程。

class ThreadTask extends Thread {
    public void run() {
        System.out.println("看完二哥的 Java 进阶之路,上岸了!");
    }

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

这种方法的缺点是,由于 Java 不支持多重继承,所以如果类已经继承了另一个类,就不能使用这种方法了。

第二种,实现 Runnable 接口,重写 run() 方法,然后创建 Thread 对象,将 Runnable 对象作为参数传递给 Thread 对象,调用 start() 方法启动线程。

class RunnableTask implements Runnable {
    public void run() {
        System.out.println("看完二哥的 Java 进阶之路,上岸了!");
    } 

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

这种方法的优点是可以避免 Java 的单继承限制,并且更符合面向对象的编程思想,因为 Runnable 接口将任务代码和线程控制的代码解耦了。

第三种,实现 Callable 接口,重写 call() 方法,然后创建 FutureTask 对象,参数为 Callable 对象;紧接着创建 Thread 对象,参数为 FutureTask 对象,调用 start() 方法启动线程。

class CallableTask implements Callable<String> {
    public String call() {
        return "看完二哥的 Java 进阶之路,上岸了!";
    }

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

这种方法的优点是可以获取线程的执行结果。

3.Java 线程池核心线程数在运行过程中能修改吗?

可以动态修改的。Java 的 ThreadPoolExecutor 提供了动态调整核心线程数和最大线程数的方法。

1)修改核心线程数的方法

  • 使用 ThreadPoolExecutor.setCorePoolSize(int corePoolSize) 方法可以动态修改核心线程数。corePoolSize 参数代表线程池中的核心线程数,当池中线程数量少于核心线程数时,会创建新的线程来处理任务。这个修改可以在线程池运行的过程中进行,立即生效。

2)注意事项

  • 核心线程数的修改不会中断现有任务,新的核心线程数会在新任务到来时生效。
  • setCorePoolSize() 方法可以减少核心线程数,但如果当前线程池中的线程数量超过了新的核心线程数,多余的线程不会立即被销毁,直到这些线程空闲后被回收。

在实际生产环境中,可以通过监控线程池的状态(如当前活跃线程数、队列长度等)来决定是否动态调整线程池大小。

这在负载波动大的应用场景中非常有用,例如在负载高峰时临时增加核心线程数,低负载时通过回收空闲线程释放资源。