乐趣区

夯实Java基础系列17一文搞懂Java多线程使用方式实现原理以及常见面试题

本系列文章将整理到我在 GitHub 上的《Java 面试指南》仓库,更多精彩内容请到我的仓库里查看

https://github.com/h2pl/Java-…

喜欢的话麻烦点下 Star 哈

文章首发于我的个人博客:

www.how2playlife.com

<!– more –>

Java 中的线程

Java 之父对线程的定义是:

线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进程所创建的对象资源(内存资源)。java.lang.Thread 对象负责统计和控制这种行为。

每个程序都至少拥有一个线程 - 即作为 Java 虚拟机 (JVM) 启动参数运行在主类 main 方法的线程。在 Java 虚拟机初始化过程中也可能启动其他的后台线程。这种线程的数目和种类因 JVM 的实现而异。然而所有用户级线程都是显式被构造并在主线程或者是其他用户线程中被启动。

  本文主要讲了 java 中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区别:进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含 1 -- n 个线程。(进程是资源分配的最小单位)线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是 cpu 调度的最小单位)线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。多进程是指操作系统能同时运行多个任务(程序)。多线程是指在同一程序中有多个顺序流在执行。在 java 中要想实现多线程,有两种手段,一种是继续 Thread 类,另外一种是实现 Runable 接口.(其实准确来讲,应该有三种,还有一种是实现 Callable 接口,并与 Future、线程池结合使用

Java 线程状态机

Java 给多线程编程提供了内置的支持。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

这里定义和线程相关的另一个术语 – 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。


一个线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了 start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

Java 多线程实战

多线程的实现

public class 多线程实例 {

    // 继承 thread
    @Test
    public void test1() {
        class A extends Thread {
            @Override
            public void run() {System.out.println("A run");
            }
        }
        A a = new A();
        a.start();}

    // 实现 Runnable
    @Test
    public void test2() {
        class B implements Runnable {

            @Override
            public void run() {System.out.println("B run");
            }
        }
        B b = new B();
        //Runable 实现类需要由 Thread 类包装后才能执行
        new Thread(b).start();}

    // 有返回值的线程
    @Test
    public void test3() {Callable callable = new Callable() {
            int sum = 0;
            @Override
            public Object call() throws Exception {for (int i = 0;i < 5;i ++) {sum += i;}
                return sum;
            }
        };
        // 这里要用 FutureTask,否则不能加入 Thread 构造方法
        FutureTask futureTask = new FutureTask(callable);
        new Thread(futureTask).start();
        try {System.out.println(futureTask.get());
        } catch (InterruptedException e) {e.printStackTrace();
        } catch (ExecutionException e) {e.printStackTrace();
        }
    }

    // 线程池实现
    @Test
    public void test4() {ExecutorService executorService = Executors.newFixedThreadPool(5);
        //execute 直接执行线程
        executorService.execute(new Thread());
        executorService.execute(new Runnable() {
            @Override
            public void run() {System.out.println("runnable");
            }
        });
        //submit 提交有返回结果的任务,运行完后返回结果。Future future = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {return "a";}
        });
        try {System.out.println(future.get());
        } catch (InterruptedException e) {e.printStackTrace();
        } catch (ExecutionException e) {e.printStackTrace();
        }

        ArrayList<String> list = new ArrayList<>();
        // 有返回值的线程组将返回值存进集合
        for (int i = 0;i < 5;i ++) {
            int finalI = i;
            Future future1 = executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {return "res" + finalI;}
            });
            try {list.add((String) future1.get());
            } catch (InterruptedException e) {e.printStackTrace();
            } catch (ExecutionException e) {e.printStackTrace();
            }
        }
        for (String s : list) {System.out.println(s);
        }
    }
}

线程状态转换

public class 线程的状态转换 {
// 一开始线程是 init 状态,结束时是 terminated 状态
class t implements Runnable {
    private String name;
    public t(String name) {this.name = name;}
    @Override
    public void run() {System.out.println(name + "run");
    }
}

// 测试 join,父线程在子线程运行时进入 waiting 状态
@Test
public void test1() throws InterruptedException {Thread dad = new Thread(new Runnable() {Thread son = new Thread(new t("son"));
        @Override
        public void run() {System.out.println("dad init");
            son.start();
            try {
                // 保证子线程运行完再运行父线程
                son.join();
                System.out.println("dad run");
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    });
    // 调用 start,线程进入 runnable 状态,等待系统调度
    dad.start();
    // 在父线程中对子线程实例使用 join,保证子线程在父线程之前执行完

}

// 测试 sleep
@Test
public void test2(){Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {System.out.println("t1 run");
            try {Thread.sleep(3000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }
    });

    // 主线程休眠。进入 time waiting 状态
    try {Thread.sleep(3000);
    } catch (InterruptedException e) {e.printStackTrace();
    }
    t1.start();}

// 线程 2 进入 blocked 状态。public static void main(String[] args) {test4();
    Thread.yield();// 进入 runnable 状态}

// 测试 blocked 状态
public static void test4() {
    class A {
        // 线程 1 获得实例锁以后线程 2 无法获得实例锁,所以进入 blocked 状态
        synchronized void run() {while (true) {System.out.println("run");
            }
        }
    }
    A a = new A();
    new Thread(new Runnable() {
        @Override
        public void run() {System.out.println("t1 get lock");
            a.run();}
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {System.out.println("t2 get lock");
            a.run();}
    }).start();}

//volatile 保证线程可见性
volatile static int flag = 1;
//object 作为锁对象,用于线程使用 wait 和 notify 方法
volatile static Object o = new Object();
// 测试 wait 和 notify
//wait 后进入 waiting 状态,被 notify 进入 blocked(阻塞等待锁释放)或者 runnable 状态(获取到锁)public void test5() {new Thread(new Runnable() {
        @Override
        public void run() {
            //wait 和 notify 只能在同步代码块内使用
            synchronized (o) {while (true) {if (flag == 0) {
                        try {Thread.sleep(2000);
                            System.out.println("thread1 wait");
                            // 释放锁,线程挂起进入 object 的等待队列,后续代码运行
                            o.wait();} catch (InterruptedException e) {e.printStackTrace();
                        }
                    }
                    System.out.println("thread1 run");
                    System.out.println("notify t2");
                    flag = 0;
                    // 通知等待队列的一个线程获取锁
                    o.notify();}
            }
        }
    }).start();
    // 解释同上
    new Thread(new Runnable() {
        @Override
        public void run() {while (true) {synchronized (o) {if (flag == 1) {
                        try {Thread.sleep(2000);
                            System.out.println("thread2 wait");
                            o.wait();} catch (InterruptedException e) {e.printStackTrace();
                        }
                    }
                    System.out.println("thread2 run");
                    System.out.println("notify t1");
                    flag = 1;
                    o.notify();}
            }
        }
    }).start();}

// 输出结果是
//    thread1 run
//    notify t2
//    thread1 wait
//    thread2 run
//    notify t1
//    thread2 wait
//    thread1 run
//    notify t2
// 不断循环

}

Java Thread 常用方法

Thread#yield():

执行此方法会向系统线程调度器(Schelduler)发出一个暗示,告诉其当前 JAVA 线程打算放弃对 CPU 的使用,但该暗示,有可能被调度器忽略。使用该方法,可以防止线程对 CPU 的过度使用,提高系统性能。

Thread#sleep(time)或 Thread.sleep(time, nanos):

使当前线程进入休眠阶段,状态变为:TIME_WAITING

Thread.interrupt():

中断当前线程的执行,允许当前线程对自身进行中断,否则将会校验调用方线程是否有对该线程的权限。

如果当前线程因被调用 Object#wait(),Object#wait(long, int), 或者线程本身的 join(), join(long),sleep()处于阻塞状态中,此时调用 interrupt 方法会使抛出 InterruptedException,而且线程的阻塞状态将会被清除。

Thread#interrupted(),返回 true 或者 false:

查看当前线程是否处于中断状态,这个方法比较特殊之处在于,如果调用成功,会将当前线程的 interrupt status 清除。所以如果连续 2 次调用该方法,第二次将返回 false。

Thread.isInterrupted(),返回 true 或者 false:

与上面方法相同的地方在于,该方法返回当前线程的中断状态。不同的地方在于,它不会清除当前线程的 interrupt status 状态。

Thread#join(),Thread#join(time):

A 线程调用 B 线程的 join()方法,将会使 A 等待 B 执行,直到 B 线程终止。如果传入 time 参数,将会使 A 等待 B 执行 time 的时间,如果 time 时间到达,将会切换进 A 线程,继续执行 A 线程。

构造方法和守护线程

构造方法
Thread 类中不同的构造方法接受如下参数的不同组合:一个 Runnable 对象,这种情况下,Thread.start 方法将会调用对应 Runnable 对象的 run 方法。如果没有提供 Runnable 对象,那么就会立即得到一个 Thread.run 的默认实现。一个作为线程标识名的 String 字符串,该标识在跟踪和调试过程中会非常有用,除此别无它用。线程组(ThreadGroup),用来放置新创建的线程,如果提供的 ThreadGroup 不允许被访问,那么就会抛出一个 SecurityException。Thread 对象拥有一个守护 (daemon) 标识属性,这个属性无法在构造方法中被赋值,但是可以在线程启动之前设置该属性(通过 setDaemon 方法)。当程序中所有的非守护线程都已经终止,调用 setDaemon 方法可能会导致虚拟机粗暴的终止线程并退出。isDaemon 方法能够返回该属性的值。守护状态的作用非常有限,即使是后台线程在程序退出的时候也经常需要做一些清理工作。(daemon 的发音为”day-mon”, 这是系统编程传统的遗留,系统守护进程是一个持续运行的进程,比如打印机队列管理,它总是在系统中运行。)

启动线程的方式和 isAlive 方法

启动线程
调用 start 方法会触发 Thread 实例以一个新的线程启动其 run 方法。新线程不会持有调用线程的任何同步锁。

当一个线程正常地运行结束或者抛出某种未检测的异常(比如,运行时异常(RuntimeException),错误(ERROR) 或者其子类)线程就会终止。

当线程终止之后,是不能被重新启动的。在同一个 Thread 上调用多次 start 方法会抛出 InvalidThreadStateException 异常。

如果线程已经启动但是还没有终止,那么调用 isAlive 方法就会返回 true. 即使线程由于某些原因处于阻塞 (Blocked) 状态该方法依然返回 true。

如果线程已经被取消(cancelled), 那么调用其 isAlive 在什么时候返回 false 就因各 Java 虚拟机的实现而异了。没有方法可以得知一个处于非活动状态的线程是否已经被启动过了。

Java 多线程优先级

Java 的线程实现基本上都是内核级线程的实现,所以 Java 线程的具体执行还取决于操作系统的特性。

Java 虚拟机为了实现跨平台 (不同的硬件平台和各种操作系统) 的特性,Java 语言在线程调度与调度公平性上未作出任何的承诺,甚至都不会严格保证线程会被执行。但是 Java 线程却支持优先级的方法,这些方法会影响线程的调度:

每个线程都有一个优先级,分布在 Thread.MIN_PRIORITY 和 Thread.MAX_PRIORITY 之间(分别为 1 和 10)
默认情况下,新创建的线程都拥有和创建它的线程相同的优先级。main 方法所关联的初始化线程拥有一个默认的优先级,这个优先级是 Thread.NORM_PRIORITY (5).

线程的当前优先级可以通过 getPriority 方法获得。
线程的优先级可以通过 setPriority 方法来动态的修改,一个线程的最高优先级由其所在的线程组限定。

Java 多线程面试题

这篇文章主要是对多线程的问题进行总结的,因此罗列了 40 个多线程的问题。

这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,但是本文写作的重心就是所有的问题都会按照自己的理解回答一遍,不会去看网上的答案,因此可能有些问题讲的不对,能指正的希望大家不吝指教。

1、多线程有什么用?

一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓 ” 知其然知其所以然 ”,” 会用 ” 只是 ” 知其然 ”,” 为什么用 ” 才是 ” 知其所以然 ”,只有达到 ” 知其然知其所以然 ” 的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:

1)发挥多核 CPU 的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4 核、8 核甚至 16 核的也都不少见,如果是单线程的程序,那么在双核 CPU 上就浪费了 50%,在 4 核 CPU 上就浪费了 75%。单核 CPU 上所谓的 ” 多线程 ” 那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程 ” 同时 ” 运行罢了。多核 CPU 上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核 CPU 的优势来,达到充分利用 CPU 的目的。

2)防止阻塞

从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因为在单核 CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

3)便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成几个小任务,任务 B、任务 C、任务 D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

2、创建线程的方式

比较常见的一个问题了,一般就是两种:

1)继承 Thread 类

2)实现 Runnable 接口

至于哪个好,不用说肯定是后者好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程 也是设计模式 6 大原则的核心。

3、start()方法和 run()方法的区别

只有调用了 start()方法,才会表现出多线程的特性,不同线程的 run()方法里面的代码交替执行。如果只是调用 run()方法,那么代码还是同步执行的,必须等待一个线程的 run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其 run()方法里面的代码。

4、Runnable 接口和 Callable 接口的区别

有点深的问题了,也看出一个 Java 程序员学习知识的广度。

Runnable 接口中的 run()方法的返回值是 void,它做的事情只是纯粹地去执行 run()方法中的代码而已;Callable 接口中的 call()方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获取异步执行的结果。

这其实是很有用的一个特性,因为 多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而 Callable+Future/FutureTask 却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。

5、CyclicBarrier 和 CountDownLatch 的区别

两个看上去有点像的类,都在 java.util.concurrent 下,都可以用来表示代码运行到某个点上,二者的区别在于:

1)CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值 - 1 而已,该线程继续运行。

2)CyclicBarrier 只能唤起一个任务,CountDownLatch 可以唤起多个任务。

3) CyclicBarrier 可重用,CountDownLatch 不可重用,计数值为 0 该 CountDownLatch 就不可再用了。

6、volatile 关键字的作用

一个非常重要的问题,是每个学习、应用多线程的 Java 程序员都必须掌握的。理解 volatile 关键字的作用的前提是要理解 Java 内存模型,这里就不讲 Java 内存模型了,可以参见第 31 点,volatile 关键字的作用主要有两个:

1)多线程主要围绕可见性和原子性两个特性而展开,使用 volatile 关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到 volatile 变量,一定是最新的数据。

2)代码底层执行不像我们看到的高级语言 —-Java 程序这么简单,它的执行是Java 代码 –> 字节码 –> 根据字节码执行对应的 C /C++ 代码 –>C/C++ 代码被编译成汇编语言 –> 和硬件电路交互,现实中,为了获取更好的性能 JVM 可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用 volatile 则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率。

从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger,更多详情请点击这里进行学习。

7、什么是线程安全

又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的

这个问题有值得一提的地方,就是线程安全也是有几个级别的:

1)不可变

像 String、Integer、Long 这些,都是 final 类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用

2)绝对线程安全

不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java 中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java 中也有,比方说 CopyOnWriteArrayList、CopyOnWriteArraySet

3)相对线程安全

相对线程安全也就是我们通常意义上所说的线程安全,像 Vector 这种,add、remove 方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个 Vector、有个线程同时在 add 这个 Vector,99% 的情况下都会出现 ConcurrentModificationException,也就是fail-fast 机制

4)线程非安全

这个就没什么好说的了,ArrayList、LinkedList、HashMap 等都是线程非安全的类,点击这里了解为什么不安全。

8、Java 中如何获取到线程 dump 文件

死循环、死锁、阻塞、页面打开慢等问题,打线程 dump 是最好的解决问题的途径。所谓线程 dump 也就是线程堆栈,获取到线程堆栈有两步:

1)获取到线程的 pid,可以通过使用 jps 命令,在 Linux 环境下还可以使用 ps -ef | grep java

2)打印线程堆栈,可以通过使用 jstack pid 命令,在 Linux 环境下还可以使用 kill -3 pid

另外提一点,Thread 类提供了一个 getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈。

9、一个线程如果出现了运行时异常会怎么样

如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

10、如何在两个线程之间共享数据

通过在线程之间共享对象就可以了,然后通过 wait/notify/notifyAll、await/signal/signalAll 进行唤起和等待,比方说阻塞队列 BlockingQueue 就是为线程之间共享数据而设计的

11、sleep 方法和 wait 方法有什么区别

这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,wait 方法会放弃这个对象的监视器

12、生产者消费者模型的作用是什么

这个问题很理论,但是很重要:

1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用

2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

13、ThreadLocal 有什么用

简单说 ThreadLocal 就是一种以 空间换时间 的做法,在每个 Thread 里面维护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了

14、为什么 wait()方法和 notify()/notifyAll()方法要在同步块中被调用

这是 JDK 强制的,wait()方法和 notify()/notifyAll()方法在调用前都必须先获得对象的锁

15、wait()方法和 notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和 notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器

16、为什么要使用线程池

避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。点击这里学习线程池详解。

17、怎么唤醒一个阻塞的线程

如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。

18、不可变对象对多线程有什么帮助

前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。

19、什么是多线程的上下文切换

多线程的上下文切换是指 CPU 控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取 CPU 执行权的线程的过程。

20、线程类的构造方法、静态块是被哪个线程调用的

这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new 这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。

如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,那么:

1)Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是 Thread2 自己调用的

2)Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法是 Thread1 自己调用的

21、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,希望每个人都能看到并且思考一下,因为这个问题非常好、非常实际、非常专业。关于这个问题,个人看法是:

1)高并发、任务执行时间短的业务,线程池线程数可以设置为 CPU 核数 +1,减少线程上下文的切换

2)并发不高、任务执行时间长的业务要区分开看:

a)假如是业务时间长集中在 IO 操作上,也就是 IO 密集型的任务,因为 IO 操作并不占用 CPU,所以不要让所有的 CPU 闲下来,可以加大线程池中的线程数目,让 CPU 处理更多的业务

b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换

c)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考其他有关线程池的文章。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

参考文章

https://blog.csdn.net/zl1zl2z…

https://www.runoob.com/java/j…

https://blog.csdn.net/qq_3803…

https://blog.csdn.net/tongxue…

https://www.cnblogs.com/snow-…

微信公众号

Java 技术江湖

如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号【Java 技术江湖】一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点 Docker、ELK,同时也分享技术干货和学习经验,致力于 Java 全栈开发!

Java 工程师必备学习资源: 一些 Java 工程师常用学习资源,关注公众号后,后台回复关键字 “Java” 即可免费无套路获取。

个人公众号:黄小斜

作者是 985 硕士,蚂蚁金服 JAVA 工程师,专注于 JAVA 后端技术栈:SpringBoot、MySQL、分布式、中间件、微服务,同时也懂点投资理财,偶尔讲点算法和计算机理论基础,坚持学习和写作,相信终身学习的力量!

程序员 3T 技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 即可免费无套路获取。

退出移动版