线程与进程理论知识入门
-
什么是进程,什么是线程
-
概述:进/线程是伴随操作系统出现的
-
应用程序(死的)安装在硬盘里面,操作系统打开此应用后,应用程序变为进程(活的)
-
进程:独立,分配资源单位,不执行具体任务的
-
进程之间是相互独立的(可以扯到zygot fork子进程为应用程序分配独立JVM)
-
操作系统进行资源分配的最小单位
-
资源:将应用程序变成进程所需要的资源
- CPU
- 内存
- 磁盘I/O
-
-
-
线程:无所不在,执行具体任务,资源拿给线程用
- CPU调度的最小单位,不能独立于进程存在
- 启动一个进程后,至少有一个线程
- 同一个进程中的线程之间可以共享进程的资源(内存,磁盘I/O)
-
-
CPU的核心数与线程的关系
-
什么是多核?
- 早期计算机中一个芯片只能放一个逻辑核心/物理核心,随着摩尔定律失效,CPU的制程不断缩小;出现量子限制(量子遂川,导致提升晶体管的密度不可行)--->将多个物理核心集成到一个芯片上面(一块物理核心上,有多个处理器--->多核)
-
CPU的核心数与线程的关系
-
概述:
- 一般情况下内核数与线程数(真正执行任务的)对应
- 但是Inter引入了超线程技术:物理核心数 : 逻辑核心数 = 1 :2
-
示意图:意味着逻辑核心数就是当前可以跑的线程数
-
但是为什么在开发过程中启动的线程数不受逻辑处理器数的限制?
- 操作系统提供了CPU时间片轮转机制(RR调度)
-
-
-
CPU时间片轮转机制:使得线程数不在受限
-
概述:看起来是一瞬间,但是CPU切的太快了
- 人眼的反应时间:0.1S,CPU(1.6G)执行一条指令:0.6ns(一秒对应十亿纳秒);
- 将CPU时间切片,只要切得快,用户就感觉不到
-
时间片轮转机制的代价:
- 操作系统一般是需要在几个进程之间进行切换,进程本身就要占用资源(内存,CPU里面的寄存器),当一个进程的时间片到了,那么就需要让出CPU,将下一个需要运行的进程载入(也叫上下文切换,非常消耗CPU时间)
- 一次上下文切换,需要占用两万个CPU的周期(在编写代码的时候,需尽量避免造成上下文切换)
-
-
并行与并发:
-
并行:可以同时运行的进程数(两个咖啡机,两队同时使用)
- 同时执行不同任务,实际上确实是同时执行
- CPU逻辑核心数为8,那么并行度就是8,CPU可以同时执行8个进程
-
并发:不能脱离时间单位,只讨论单位时间的并发量,(例如,一分钟能提供几杯咖啡,两队人交替使用咖啡机,假如一分钟出了四倍咖啡,那么在这一分钟中,并发量为4)
-
应用可以交替执行不同任务,看起来是同时执行(操作系统中部的技术,切得很快)
-
实际上不是同时执行的,只是切得很快,让用户感觉不到
-
时间片轮转机制就是并发的一种手段
-
-
-
高并发编程的意义
-
多核背景,导致多线程高并发应用广泛
-
多线程的好处:充分利用CPU资源,加速用户响应时间,代码模块化,异步化,简单化
- 假如有8个CPU,单线程程序只能用一个,其他的就空闲了
- 多线程速度快
- 比如电商系统中多个不相关的子任务,拆给多个线程同时用,实现模块化,异步化,速度快,简单化
-
高并发的问题:
-
安全性:线程会共享进程资源,造成资源争夺
-
引入锁:
- 造成死锁
- 锁的竞争会导致效率下降,甚至不如单线程
-
操作系统内核限制线程数:对线程分配资源(线程独立的栈空间,java默认是1MB,还有文件描述符(句柄(指向资源的位置,像指针),引用))
-
Linux中一个进程最多开1000个线程,一个系统最多同时存在1024个文件描述符
-
Windows中一个进程最多开2000个线程
-
线程开多了,导致服务器崩溃
-
使用数据库(MySQL):连接数一般在150-200,但是应用往往使用不止这个
- 引入数据库连接池
-
-
-
-
-
java天生就是多线程的
-
证明:
-
代码示意:
public class OnlyMain { public static void main(String[] args) { //Java 虚拟机线程系统的管理接口 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); // 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息 ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); // 遍历线程信息,仅打印线程ID和线程名称信息 for (ThreadInfo threadInfo : threadInfos) { System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName()); } } } -
运行截图:打印结果与虚拟机版本,操作系统实时调度有关,可能不止6个
-
部分线程说明:这个是对java程序进行监控采用
-
Main:程序入口,这个里面会初始化虚拟机
-
Finalizer:不要将对象资源回收放在这里(有可能不执行)
- 优先级很低,属于守护线程(随主线程一起退出,可能导致当前对象都没有掉这个线程,来不及进行资源的回收)
-
Attach Listener:获取内存的dump,将虚拟机中的线程信息打出来
-
Monitor Ctrl-Break:检测中断信号的
-
-
-
-
新线程的启动方式
-
java中新线程的启动大体是两种方式
-
JDK官方文档:在Thread源码注释信息中明确说明了只有两种
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:
-
通过Thread类:继承自Thread类,重写run方法
//继承自Thread类,重写run方法 private static class UseThread extends Thread{ @Override public void run() { super.run(); // do my work; System.out.println("I am extendec Thread"); } } //调用: public static void main(String[] args) throws InterruptedException, ExecutionException { UseThread useThread = new UseThread(); useThread.start(); useThread.start(); } -
通过Runnable接口,重写run方法
/*实现Runnable接口*/ private static class UseRunnable implements Runnable{ @Override public void run() { // do my work; System.out.println("I am implements Runnable"); } } //调用: public static void main(String[] args) throws InterruptedException, ExecutionException { UseRunnable useRunnable = new UseRunnable(); new Thread(useRunnable).start(); }-
细节:为什么要写这个new Thread(useRunnable).start();
-
因为在Thread的构造方法中:Runnable为参数
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
-
-
-
-
-
Thread与Runnable的区别
-
概述:
- 对象角度:单继承,多实现
- Thread才是对线程的抽象(真正干活的),Runnable是对任务(业务逻辑)的抽象
-
-
线程stop方法的不安全性:不建议使用,大部分情况下不合适
-
JDK不推荐使用:这些方法带有很高的强制性
-
终止线程时,直接将当前线程干掉(根本不管当前线程有没有正常释放资源)
-
假如,当前线程在写入文件达到一半的时候,调用stop
- 导致文件缺失,后面的线程无法正常读取
- 正常写入文件,结束后会在结尾处打上标记
-
-
代码示意:
@Deprecated//不推荐使用 public final void stop() { SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); } -
suspend:将线程挂起(发生一次上下文切换,从可运行切换到挂起)
-
细节:线程会持有此刻的资源(比如锁),进行休眠--->可能导致死锁
-
代码
@Deprecated public final void suspend() { checkAccess(); suspend0(); }
-
-
-
让java中的线程安全停止工作:interrupt
-
概述:让线程进行中断
-
Thread类提供了三个interrupt方法(interrupted还是static修饰的)
-
-
isInterrupted():boolean
-
作用:判断当前线程是否被中断
-
代码示意:
//不会改变标志位,主线程发送中断信号后,标志位就不会改了 public boolean isInterrupted() { return isInterrupted(false); }
-
-
interrupted():boolean
-
作用:判断当前线程是否被中断
-
代码:
public static boolean interrupted() { return currentThread().isInterrupted(true); } //isInterrupted private native boolean isInterrupted(boolean ClearInterrupted); -
具体使用:调用后会将标志位从true(主线程发送了中断信号) 改为false
-
代码:
package cn.enjoyedu.ch1.base.safeend; /** *类说明:如何安全中断线程 */ public class EndThread { private static class UseThread extends Thread { public UseThread(String name) { super(name); } @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " interrrupt flag =" + isInterrupted()); // while (!isInterrupted()) { while(!Thread.interrupted()){ // while (true) { System.out.println(threadName + " is running"); System.out.println(threadName + "inner interrrupt flag =" + isInterrupted()); } System.out.println(threadName+" interrrupt flag ="+isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread endThread = new UseThread("endThread"); endThread.start(); Thread.sleep(20); endThread.interrupt();//中断线程,其实设置线程的标识位true } } -
运行截图:
-
-
-
interrupt():void
-
作用:向当前线程发起中断(不代表线程立即停止工作),并不是终止线程(修改线程的标志位,默认是false),而且线程甚至可以不理会这种中断操作(在JDK中线程是协作式的(只是通知这个线程,至于怎么做,就是线程自己的事情了),不是抢占式(调用stop))
-
源码:
public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
-
具体使用:仅对线程发送中断信号
-
代码:
package cn.enjoyedu.ch1.base.safeend; /** *类说明:如何安全中断线程 */ public class EndThread { private static class UseThread extends Thread { public UseThread(String name) { super(name); } @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " interrrupt flag =" + isInterrupted()); // while (!isInterrupted()) { // while(!Thread.interrupted()){ while (true) { System.out.println(threadName + " is running"); System.out.println(threadName + "inner interrrupt flag =" + isInterrupted()); } // System.out.println(threadName+" interrrupt flag ="+isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread endThread = new UseThread("endThread"); endThread.start(); Thread.sleep(20); endThread.interrupt();//中断线程,其实设置线程的标识位true } } -
运行结果:线程并没有理会此中断信号(在run方法中没有对线程中断标示位检测)

-
-
具体使用:向线程发送中断信号,并在线程run方法中对中断信号做检测
-
代码:
package cn.enjoyedu.ch1.base.safeend; /** *类说明:如何安全中断线程 */ public class EndThread { private static class UseThread extends Thread { public UseThread(String name) { super(name); } @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " interrrupt flag =" + isInterrupted()); while (!isInterrupted()) { // while(!Thread.interrupted()){ // while (true) { System.out.println(threadName + " is running"); System.out.println(threadName + "inner interrrupt flag =" + isInterrupted()); } System.out.println(threadName+" interrrupt flag ="+isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread endThread = new UseThread("endThread"); endThread.start(); Thread.sleep(20); endThread.interrupt();//中断线程,其实设置线程的标识位true } } -
运行截图:
-
-
-
-
尽量使用中断结束线程:
-
概述:
-
调用interrupt方法:修改线程的中断标志位,但不会立即停止线程
-
在线程run方法中,调用isInterrupted(这个用的比较大)与interrupted(充值标志位为空)方法可以检测标志位:
- 线程可以不去看这个标志位,干自己的事情就行了
-
-
自定义标志位(增加一个类属性):不建议
-
在调用阻塞方法(wait,sleep,take)后,根本不会对自定义标志位进行判断
-
在sleep源码中会抛出一个InterruptedException(即使线程挂起,也会对中断进行检测)
public static native void sleep(long millis) throws InterruptedException;
-
-
使用Runnable接口时怎么实现中断:找当前线程的标志位
-
代码展示:
package cn.enjoyedu.ch1.base.safeend; /** *类说明:实现接口Runnable的线程如何中断 */ public class EndRunnable { private static class UseRunnable implements Runnable{ @Override public void run() { while(!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + " I am implements Runnable."); } System.out.println(Thread.currentThread().getName() +" interrupt flag is "+Thread.currentThread().isInterrupted()); } } public static void main(String[] args) throws InterruptedException { UseRunnable useRunnable = new UseRunnable(); Thread endThread = new Thread(useRunnable,"endThread"); endThread.start(); Thread.sleep(20); endThread.interrupt(); } }
-
-
调用阻塞方法(sleep)后,如何侦测中断
-
sleep方法以抛出异常的方式检测中断信号
public static native void sleep(long millis) throws InterruptedException; -
使用try-catch捕获相应的异常信息
package cn.enjoyedu.ch1.base.safeend; /** *类说明:阻塞方法中抛出InterruptedException异常后,如果需要继续中断,需要手动再中断一次 */ public class HasInterrputException { private static class UseThread extends Thread{ public UseThread(String name) { super(name); } @Override public void run() { while(!isInterrupted()) { try { Thread.sleep(100); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() +" in InterruptedException interrupt flag is " +isInterrupted()); //资源释放 //interrupt(); e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " I am extends Thread."); } System.out.println(Thread.currentThread().getName() +" interrupt flag is "+isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread endThread = new UseThread("HasInterrputEx"); endThread.start(); Thread.sleep(500); endThread.interrupt(); } } -
运行截图:抓到异常,但是没有终止当前的线程
-
细节:抓到异常以后,它会修改线程的标志位(重新置false);导致当前线程不结束
-
解决办法:在try-catch中再进行一次中断,修改标志位为true
-
代码:
try { Thread.sleep(100); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() +" in InterruptedException interrupt flag is " +isInterrupted()); //资源释放 interrupt();//再次中断,修改标志位,结束当前线程 e.printStackTrace(); } -
运行截图:
-
为什么要这样设计?
-
当sleep时拿到了资源,如果不这样搞,就跟之前的stop方法一样,资源都没有被释放,就中断(终止了这个线程)
死锁是不会理会中断信号的
-
-
-
-
-
深入理解run与start方法的区别
-
概述:
- Thread类是对线程的抽象
- 实例化Thread时并没有跟操作系统扯上关系,在调用start方法之后,才有关系
-
start方法:查看源码
-
调用了start0方法:private native void start0();
-
-
假如说:对一个线程进行了两次start会发生什么情况?
-
抛出异常:Thread.java
//调用start方法后,首先就会对当前线程的状态进行判断,掉了就不能掉了 if (threadStatus != 0) throw new IllegalThreadStateException();
-
-
run方法:
-
概述:就像是一个类的成员方法,可以脱离操作系统意义上的线程,可以随便调用
-
代码:
public class StartAndRun { public static class ThreadRun extends Thread{ @Override public void run() { int i = 90; while(i>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { //e.printStackTrace(); } System.out.println("I am "+Thread.currentThread().getName() +" and now the i="+i--); } } } public static void main(String[] args) { ThreadRun threadRun = new ThreadRun(); threadRun.setName("threadRun"); threadRun.run(); } } -
运行截图:执行run方法的是main线程,不是程序猿写的那个线程
-
运行截图:将 threadRun.run();改为threadRun.start();(执行run方法的就是程序猿定义的那个线程了)
-
-
-
join方法:
-
概述:
- 应用场景:存在当前正在执行的线程A,此时调用线程B的join方法--->线程A被挂起,需要等到线程B执行完后,线程A才继续执行;谁调用join方法,谁就插队
- 怎么保证两个线程顺序执行?调用join方法即可
-
使用:
static class Goddess implements Runnable { private Thread thread; public Goddess(Thread thread) { this.thread = thread; } public Goddess() { } public void run() { System.out.println("Goddess开始排队打饭....."); try { if(thread!=null) thread.join(); } catch (InterruptedException e) { } SleepTools.second(2);//休眠2秒 System.out.println(Thread.currentThread().getName() + " Goddess打饭完成."); } } static class GoddessBoyfriend implements Runnable { public void run() { SleepTools.second(2);//休眠2秒 System.out.println("GoddessBoyfriend开始排队打饭....."); System.out.println(Thread.currentThread().getName() + " GoddessBoyfriend打饭完成."); } }-
不进行插队:
-
代码:
public static void main(String[] args) throws Exception { Thread lison = Thread.currentThread(); Goddess goddess = new Goddess(); Thread g = new Thread(goddess); g.start(); System.out.println("lison开始排队打饭....."); System.out.println(Thread.currentThread().getName() + " lison打饭完成."); } -
运行截图:分开都有饭吃
-
-
让g插队:实现两个线程顺序执行
-
代码:
public static void main(String[] args) throws Exception { Thread lison = Thread.currentThread(); Goddess goddess = new Goddess(); Thread g = new Thread(goddess); g.start(); System.out.println("lison开始排队打饭....."); g.join(); SleepTools.second(2);//让主线程休眠2秒 System.out.println(Thread.currentThread().getName() + " lison打饭完成."); } -
运行截图:保证子线程一定在主线程之前完成执行
-
-
在g线程之前再查一个线程(g线程内部会检测类的成员变量,不为空,就将其插入到g线程前面执行)
-
代码:
public static void main(String[] args) throws Exception { Thread lison = Thread.currentThread(); GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend(); Thread gbf = new Thread(goddessBoyfriend); Goddess goddess = new Goddess(gbf);//在g线程前面在插一个线程 Thread g = new Thread(goddess); g.start(); gbf.start(); System.out.println("lison开始排队打饭....."); g.join(); SleepTools.second(2);//让主线程休眠2秒 System.out.println(Thread.currentThread().getName() + " lison打饭完成."); } -
运行截图:
-
-
-
-
线程的优先级和守护线程
-
线程优先级:优先级不能确定线程的具体执行顺序(由操作系统决定)
-
在启动线程的时候,可以为其设定优先级(1-10),默认为5
-
优先级高的线程所分配的时间片可能较多(具体由操作系统决定)
- 有些操作系统最高只有3
-
需要休眠和I/O操作(设置优先级高点),计算的线程优先级设置低一点
- 确保处理器不会被计算线程占据
-
-
守护线程:支持性的线程(在程序后台中做调度,内存的回收等)
-
在启动一个进程后,进程中会有很多线程
-
通过new Thread启动的线程(用户线程),没有特殊处理时,均为非守护线程
-
通过JDK内部启动与参数启动的就是守护线程
- interrupt
- 在用户线程(main)等非守护线程结束后,进程就结束了,守护线程随进程结束而结束
-
自定义守护线程
-
代码:
Thread useThread = new Thread(); useThread.setDaemon(true);//默认是false -
在框架中应用较多:管理自己分配的内存Netty
- 应用程序退出后,操作系统会释放内存
-
守护线程中的tr-catch-finally中的finally不一定执行
-
一般来说finalize操作是放在finally里面的,但是如果是在守护线程中,那么就不一定会执行(主要看有没有被分配时间片)
- 不要在这个里面做耗时操作
-
在用户线程中finally就一定会执行
- 里面就放内存释放,finalize
-
-
-
-
-
synchronized
-
概述:锁住的是对象(不同的锁之间是可以并行执行),对象头上面就有标志位(同步代码拿到这个对象就修改其标志位,表示这个对象已经被某一把锁拿到了)
- 内置锁(没有显示的开锁,绑锁操作),实际上是绑在对象上的(对象锁)
- 在执行同步代码的时候,需要拿到对象的实例才行
- 不同线程拿到不同锁里面的对象--->并发执行
- 不同线程拿到同一把锁里面的对象--->不能并发执行
-
使用:同步块,方法
-
对方法加锁:对当前的对象加锁
public synchronized void incCount(){ …… } -
同步块
private Object obj = new Object();//作为一个锁 public void incCount(){ synchronized (obj){//可以将obj替换为this count++; } }- this关键字:当前成员方法所在的对象实例
-
-
代码展示:使用Runnable,传入实例
-
对两个对象分别加锁,各自启动一个线程,实现并行执行:运行时,基本上是同时输出的
package cn.enjoyedu.ch1.syn; import cn.enjoyedu.tools.SleepTools; /** *类说明:锁的实例不一样,也是可以并行的 */ public class DiffInstance { private static class InstanceSyn implements Runnable{ private DiffInstance diffInstance; public InstanceSyn(DiffInstance diffInstance) { this.diffInstance = diffInstance; } @Override public void run() { System.out.println("TestInstance is running..."+ diffInstance); diffInstance.instance(); } } private static class Instance2Syn implements Runnable{ private DiffInstance diffInstance; public Instance2Syn(DiffInstance diffInstance) { this.diffInstance = diffInstance; } @Override public void run() { System.out.println("TestInstance2 is running..."+ diffInstance); diffInstance.instance2(); } } private synchronized void instance(){ SleepTools.second(3); System.out.println("synInstance is going..."+this.toString()); SleepTools.second(3); System.out.println("synInstance ended "+this.toString()); } private synchronized void instance2(){ SleepTools.second(3); System.out.println("synInstance2 is going..."+this.toString()); SleepTools.second(3); System.out.println("synInstance2 ended "+this.toString()); } public static void main(String[] args) { DiffInstance instance1 = new DiffInstance(); Thread t3 = new Thread(new Instance2Syn(instance1)); DiffInstance instance2 = new DiffInstance(); Thread t4 = new Thread(new InstanceSyn(instance2)); t3.start(); t4.start(); SleepTools.second(1); } }
-
对一个对象加锁,两条线程执行同一个对象的实例
//修改main方法 public static void main(String[] args) { DiffInstance instance1 = new DiffInstance(); //传入相同对象实例 Thread t3 = new Thread(new Instance2Syn(instance1)); Thread t4 = new Thread(new InstanceSyn(instance1)); t3.start(); t4.start(); SleepTools.second(1); }
-
-
类锁:在静态方法上面加锁;跟对象锁互相对立
- 实质上还是对对象加锁;因为java虚拟机在执行类加载的时候,每一个类都有唯一的Class对象;只是说,现在synchronized锁住的是类唯一的Class对象
-
判断能否并行:可以,锁的是不同的对象
//加载静态方法上,锁的是类的Class对象 private static synchronized void synClass(){ System.out.println(Thread.currentThread().getName() +"synClass going..."); SleepTools.second(1); System.out.println(Thread.currentThread().getName() +"synClass end"); } //加载静态对象上,锁的是静态变量而已 private static Object obj = new Object(); private static void synStatic(){ synchronized (obj){ System.out.println(Thread.currentThread().getName() +"synStatic going..."); SleepTools.second(1); System.out.println(Thread.currentThread().getName() +"synStatic end"); } }
-