一、基本概念
-
CPU核心数和线程数的关系:
-
线程数可以模拟出不同的CPU核心数,CPU核心数指的是硬件上存在着几个核心,而线程数可以模拟出多个核心数的功能,线程数越多,越有利于同时运行多个程序,因为线程数等同于在某个瞬间CPU能同时并行处理的任务数。
-
对于一个CPU,线程数总是大于或等于核心数的,一个核心最少对应一个线程,但通过超线程技术,一个信和可以对应多个线程,也就是说它可以同时运行两个线程。
-
通俗的说,CPU核心数就是我们经常说的我们的CPU是几核的CPU,线程则是运行在某个CPU上,但是一个CPU可分时间段来运行多个线程。
-
-
CPU时间片轮转机制:
时间片轮转法(Round-Robin, RR)主要用于分时系统中的进程调度,为了实现轮转调度,系统把所有就绪进程按先入先出的原则排成一个队列,新来的进程加到就绪队列末尾,每当执行进程调度时,进程调度程序总是选出就绪队列的队首进程,让它在CPU上运行一个时间片的时间,时间片是一个小的时间单位,通常为10~100ms数量级,当进程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止改进程的运行,把它放入就绪队列的末尾,然后把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此反复。
-
程序、线程和进程的联系和区别:
- 程序是一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,而进程则不同,它是程序在某个数据集上的执行,进程是一个动态的实体,它有自己的生命周期,它因创建而产生,因调度而运行,因等待资源或时间而被处于等待状态,因完成任务而被撤销,反应了一个程序在一定的数据集上运行的全部动态过程。
- 进程是程序运行进行资源分配的最小单位,进程内部可能有多个线程,多个线程之间共享进程资源,进程与进程之间相互独立。
- 线程是CPU调度的最小单位,线程本身不能独立运行,需要依附进程才能运行。
-
并行和并发:
- 所谓并发处理都是有排队等候,唤醒和执行这三个步骤,所以并发是宏观的概念,在微观上他们都是序列被处理的,只不过资源不会在某一个上被阻塞(一般是通过时间片来轮转),所以在宏观上多个几乎同时到达的请求同时在被处理,如果同一时刻到达的请求也会根据优先级的不同,先后进入队列排队等候执行。
- 并发指的是一个处理器同时处理多个任务,并行是指多个处理或者是多核处理器同时处理多个不同的任务,并行通俗来讲就是4个CPU,每个CPU处理一个任务,并行能力就是4,并发是单个CPU在指定时间段内通过轮转切换处理多个任务的能力。
二、java多线程基本特性:
-
Java语言天生是多线程,仅仅一个main方法,虚拟就就启动了6个线程:
/** * 仅仅运行一个main方法,则后台虚拟机启动6个线程: * [6] [Monitor Ctrl-Break] * [5] [Attach Listener] Attach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且把 结果返回给发送者 * [4] [Signal Dispatcher] Attach Listener线程的职责是接收外部jvm命令, 当命令接收成功后,会交 给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处 理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始 化工作 * [3] [Finalizer] 这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象 的finalize()方法;关于Finalizer线程的几点: 1. 只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象 的finalize()方法都会被执行; 2. 该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有 没有执行完finalize()方法,JVM也会退出; 3. JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实 现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer 对象的引用置为null,由垃圾收集器来回收; 4. JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自 己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对 GC线程来说是一种灾难; * [2] [Reference Handler] VM在创建main线程后就创建Reference Handler线程,其优先级最高, 为10, 它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收 问题 * [1] [main] */ public class OnlyMain { public static void main(String[] args) { // 虚拟机线程管理接口 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); for (ThreadInfo threadInfo : threadInfos) { System.out.println(String.format("[%s] [%s]", threadInfo.getThreadId(), threadInfo.getThreadName())); } } }
-
线程的创建方式:
/** * 创建线程的方式: * 继承Thread类,实现Runnable接口,实现Callable接口 * 区别:java只能单继承,但是可以多实现,使用Callable接口,可以得到每个线程的返回值 */ public class NewThread { // 继承Thread类 private static class UseThread extends Thread { @Override public void run() { System.out.println("This is extends Thread class"); } } // 实现Runnable接口 private static class UseRunnable implements Runnable { @Override public void run() { System.out.println("This is implements Runnable interface"); } } // 实现Callable接口 private static class UseCallable implements Callable<String> { @Override public String call() throws Exception { return "This is Callable Result"; } } public static void main(String[] args) throws ExecutionException, InterruptedException { Thread thread = new Thread(new UseThread(), "UseThread"); thread.start(); Thread runnable = new Thread(new UseRunnable(), "UseRunnable"); runnable.start(); FutureTask<String> futureTask = new FutureTask<String>(new UseCallable()); new Thread(futureTask).start(); System.out.println(futureTask.get()); } }
-
线程的终止:
线程的终止有三种方式:
-
设置推出标志,使线程正常退出,也就是当run()方法完成后线程终止。
/** * 使用标志位来终止线程 */ public class FlagExitThread implements Runnable { // 使用volatile保证flag标志位在多线程运行下可见 private volatile static boolean flag = false; @Override public void run() { String threadName = Thread.currentThread().getName(); while(!flag) { System.out.println(threadName + "is running!!"); } System.out.println(threadName + " is Exit running, flag = " + flag); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new FlagExitThread(), "flagExitThread"); thread.start(); TimeUnit.SECONDS.sleep(1); flag = true; } }
-
使用Thread.stop()方法来终止线程:
程序可以用stop方法来终止线程,但是stop方法是很危险的,就像突然关闭计算机电源一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程就会抛出ThreadDeathError的错误,并且会释放子线程所持有的锁,一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有 的所有锁突然释放(不可控制) ,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用错误,因此不推荐使用stop方法来终止线程。
-
使用interrupt()方法来中断线程:
/** * 使用interrupt方法来终止线程: * 1. interrupt():中断一个线程,相当于打个招呼,将中断标志置为true * 2. isInterrupted(): 判断线程是否处于中断状态 * 3. 静态方法interrupted方法判断线程是否处于中断状态,中断标志置位false * 使用interrupt来中断线程,其实就是线程的协作式,并不是强制关闭线程 */ public class InterruptThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); while(!Thread.currentThread().isInterrupted()) { System.out.println(threadName + "is running!!"); } System.out.println(threadName + " is Exit running, flag = " + Thread.currentThread().isInterrupted()); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptThread(), "Interrupt Thread"); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); } }
-
注意事项:线程抛出InterruptedException,会将isInterrupted进行复位,中断标志位修改为false,需要我们在catch里面再次中断,不然无法中断线程,线程会一直运行下去:
public class InterruptExceptionThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); while(!Thread.currentThread().isInterrupted()) { // 模拟异常场景,如果在线程中断时进行休眠,会抛出InterruptedException try { Thread.sleep(100); } catch (InterruptedException e) { // 如果抛出异常,会将线程isInterrupted进行复位 System.out.println(threadName + " interrupt flag is " + Thread.currentThread().isInterrupted()); // 线程如果不再次中断,线程会一直运行下去 Thread.currentThread().interrupt(); e.printStackTrace(); } System.out.println(threadName + " is running!!"); } System.out.println(threadName + " is Exit running, flag = " + Thread.currentThread().isInterrupted()); } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptExceptionThread(), "Interrupt Exception"); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); } }
-
-
线程的状态:
Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其他线程执行机会的最佳方式。
Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变成就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行,实际上无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中,Thread.yield()不会导致阻塞,该方法与sleep()类似,只是不能由用户定暂停时间。
t.join()/t.join(long millis),当前线程里调用其他线程的join()方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁,线程t执行完毕或者millis时间到,当前线程进入就绪状态。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列,依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的,notifyAll()唤醒在此对象监视器上等待的所有线程。
LockSupport中的方法,park(): 无限期暂停当前线程,parkNanos( long nanos): 暂停当前线程,不过有超时时间的限制,parkUtil(long deadline): 暂停当前线程,直到某个时间,unpark(Thread thread): 恢复指定的线程。park和wait的区别,park不需要获取某个对象的锁,中断的时候park不会抛出InterruptedException异常,所有需要在park之后自行判断中断状态,然后做额外处理。
说明:park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用,park和unpark的使用顺序倒置也不会出现死锁的情况。
-
守护线程:
-
守护线程会随着主线程结束而结束
-
setDaemon必须要在线程启动之前(即在start方法之前)
-
使用后台线程不能保证finally块一定执行
public class DemanThread { private static class DemanThreadDemo implements Runnable { @Override public void run() { try { while(!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + " is running !!"); } System.out.println(Thread.currentThread().getName() + " interrupt flag is " + Thread.currentThread().isInterrupted()); } finally { // 如果线程为后台线程,此finally里面的代码不会被执行 System.out.println("I am finally method"); } } } public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new DemanThreadDemo(), "DemanThread"); thread.setDaemon(true); thread.start(); TimeUnit.SECONDS.sleep(1); } }
-
-
ThreadLocal:线程变量
ThreadLocal在每个线程都会创建一个线程内对应的线程副本,本线程数据可以在本线程内任何地方被使用,线程之间互相不影响,所有是线程安全的。
public class UseThreadLocal { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 1; } }; /** * 启动多个线程,看会不会相互影响彼此的threadLocal结果 */ public void startThreadArray() { Thread[] runs = new Thread[10]; for (int i = 0; i < runs.length; i++) { runs[i] = new Thread(new TestThread(i), "ThreadLocal"); } for (int i = 0; i < runs.length; i++) { runs[i].start(); } } /** * 测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会相互影响 */ public static class TestThread implements Runnable { private int id; public TestThread(int id) { this.id = id; } @Override public void run() { System.out.println(Thread.currentThread().getName() + ":start"); Integer s = threadLocal.get(); s = s + id; threadLocal.set(s); System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); // threadLocal.remove(); 回收threadLocal变量,用的比较多的情况是线程池中 } } public static void main(String[] args) { UseThreadLocal useThreadLocal = new UseThreadLocal(); useThreadLocal.startThreadArray(); } }