什么是线程
- 线程是程序执行的一条路径, 一个进程中可以包含多条线程
- 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
CPU核心数和线程数的关系
以前:
六个核心数, 1:1, 一个核心 就是一个线程
单核 一个核心 一个线程
现在:
超线程技术, 1:2 , 六个核心数 = 12个线程
四核 六核 ...
安卓处理器跟PC还不太一样
ARM32,ARM64,x86,x64
CPU时间片轮转机制
随机切换机制
进程:操作系统所管理的最少单元
线程:CPU调度的最少单元
CPU时间片轮转机制采用了RR调度算法
进程和线程
进程 > 线程
一个进程至少一个线程 或 多个线程
如果一个进程,还有一个线程没有杀掉,还存活,那么进程还存活 (线程依附进程)
多线程并行和并发的区别
- 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
- 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
- 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
- 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。
并行:四个跑道
并发:10秒钟,服务器的吞吐量。比如十秒钟,多少车流量,多少车跑过去
Java程序运行原理
- Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
JVM的启动是多线程的吗
// Java服务器
/*虚拟机线程管理的接口*/
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
/*取得线程信息*/
ThreadInfo[] threadInfos =
threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos) {
System.out.println("["+threadInfo.getThreadId()+"]"+" "
+threadInfo.getThreadName());
}
上述代码打印结果:
[6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的
[5] Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给JVM信号的线程
[3] Finalizer // 调用对象finalize方法的线程
[2] Reference Handler//清除Reference的线程
[1] main //main线程,用户程序入口
- Finalizer Object finalize() 需要资源回收 就复写该方法 把代码写这里面去
- GC 并不是马上就会有GC,资源需要回收,JVM才会检测到,启用GC
多线程程序实现的方式1
public class Demo2_Thread {
/**
* @param args
*/
public static void main(String[] args) {
MyThread mt = new MyThread(); //4,创建Thread类的子类对象
mt.start(); //5,开启线程,调用run方法
for(int i = 0; i < 1000; i++) {
System.out.println("bb");
}
}
}
class MyThread extends Thread { //1,继承Thread
public void run() { //2,重写run方法
for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaa");
}
}
}
多线程程序实现的方式2
public class Demo3_Thread {
/**
* @param args
*/
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //4,创建Runnable的子类对象
Thread t = new Thread(mr); //5,将其当作参数传递给Thread的构造函数
t.start(); //6,开启线程
for(int i = 0; i < 1000; i++) {
System.out.println("bb");
}
}
}
class MyRunnable implements Runnable { //1,定义一个类实现Runnable
@Override
public void run() { //2,重写run方法
for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaa");
}
}
}
有返回值的多线程
public class MyClass {
private static class WorkerThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("do work WorkerThread");
Thread.sleep(10000);
return "run success";
}
}
public static void main(String[] args) throws Exception {
// --- 有返回值 任务不能运行,需要寄托 Thread
WorkerThread workerThread = new WorkerThread();
FutureTask<String> futureTask = new FutureTask<>(workerThread);
new Thread(futureTask).start();
System.out.println("get==="+futureTask.get()); // 阻塞的
}
}
thread的好处就是runnable的弊端
- 继承Thread
- 好处是:可以直接使用Thread类中的方法,代码简单
- 弊端是:如果已经有了父类,就不能用这种方法(java是单继承,只能一个父类)
- 实现Runnable接口
- 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
- 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
匿名内部类实现线程的两种方式
案例:
public class Demo4_Thread {
/**
* @param args
*/
public static void main(String[] args) {
//继承Thread类
new Thread() { //1,继承Thread类
public void run() { //2,重写run方法
for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaaaa");
}
}
}.start(); //4,开启线程
//实现Runnable接口
new Thread(new Runnable() { //1,将Runnable的子类对象传递给Thread的构造方法
public void run() { //2,重写run方法
for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中
System.out.println("bb");
}
}
}).start(); //4,开启线程
}
}
多线程获取名字和设置名字
- 1.获取名字
- 通过
Thread.getName()
方法获取线程对象的名字
- 通过
- 2.设置名字
- 通过
Thread.setName("李四")
设置名字,不推荐在run
函数中设置,线程一般执行东西较多。在start
前设置
- 通过
获取当前线程的对象
-
Thread.currentThread()
, 主线程也可以获取
多线程休眠
Thread.sleep(毫秒,纳秒)
守护线程
setDaemon()
, 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出守护线程在结束时也要有时间缓冲才结束,所以并不一定非守护线程执行完后守护线程就立马结束
案例:
public class Demo4_Daemon {
/**
* @param args
* 守护线程
*/
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 2; i++) {
System.out.println(getName() + "...aa.."+i);
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "...bb.."+i);
}
}
};
t2.setDaemon(true); //设置为守护线程
t1.start();
t2.start();
}
}
打印结果:
Thread-0...aa..0
Thread-0...aa..1
Thread-1...bb..0
Thread-1...bb..1
Thread-1...bb..2
Thread-1...bb..3
Thread-1...bb..4
Thread-1...bb..5
Thread-1...bb..6
Thread-1...bb..7
Thread-1...bb..8
Thread-1...bb..9
- 主线程结束,不管守护线程有没有结束,守护线程都必须结束
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "---" + i);
}
}
};
t.setDaemon(true); // 设置了守护线程
t.start(); // 谁调用的 main, main管了我 我就守护main
// 主线程,是为了 等 Thread t 10秒钟
Thread.sleep(10000);
// 非守护线程 主线程一直在等 Thread t 到底执行完了没有
// 走到这里,代表主线程结束,主线程结束,不管t线程有没有结束,t线程都必须结束,因为t线程是守护线程,守护了main
}
}
加入线程
- join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
- 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
- join(int), 可以等待指定的毫秒之后继续
public class Demo5_Join {
/**
* @param args
* join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
*/
public static void main(String[] args) {
final Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(getName() + "...aaaaaaaaaaaaa");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
if(i == 2) {
try {
//t1.join();
t1.join(1); //插队指定的时间,过了指定时间后,两条线程交替执行 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + "...bb");
}
}
};
t1.start();
t2.start();
}
}
礼让线程
- yield让出cpu
- 使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源,所有执行
yield()
的线程有可能在进入到可执行状态后马上又被执行。
public class Demo6_Yield {
/**
* yield让出cpu礼让线程
*/
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread {
public void run() {
for(int i = 1; i <= 1000; i++) {
if(i % 10 == 0) {
Thread.yield(); //让出CPU
}
System.out.println(getName() + "..." + i);
}
}
}
设置线程的优先级
- setPriority()设置线程的优先级, 只是大部分优先一点(不靠谱)
public class Demo7_Priority {
/**
* @param args
*/
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + "...aaaaaaaaa" );
}
}
};
Thread t2 = new Thread(){
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + "...bb" );
}
}
};
//t1.setPriority(10); 设置最大优先级 默认是5 最大10 最小1
//t2.setPriority(1);
t1.setPriority(Thread.MIN_PRIORITY); //设置最小的线程优先级
t2.setPriority(Thread.MAX_PRIORITY); //设置最大的线程优先级
t1.start();
t2.start();
}
}
多线程同步方法
- 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
- 锁可以是任意对象,但不能是匿名对象。因为匿名对象不是同一个对象,也就不是同一把锁
案例(非静态的同步方法):
public class Demo2_Synchronized {
/**
* @param args
* 同步代码块
*/
public static void main(String[] args) {
final Printer2 p = new Printer2();
new Thread() {
public void run() {
while(true) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
p.print2();
}
}
}.start();
}
}
class Printer2 {
Demo d = new Demo();
//非静态的同步方法的锁对象是神马?
//答:非静态的同步方法的锁对象是this 谁来调用我this就代表谁的对象
//同步方法只需要在方法上加synchronized关键字即可
public synchronized void print1() {
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.print("\r\n");
}
public void print2(this) {
//synchronized(new Demo()) {
//锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(Printer2.class) {
System.out.print("a");
System.out.print("b");
System.out.print("c");
System.out.print("d");
System.out.print("\r\n");
}
}
}
案例(静态的同步方法):
public class Demo2_Synchronized {
/**
* @param args
* 同步代码块
*/
public static void main(String[] args) {
final Printer2 p = new Printer2();
new Thread() {
public void run() {
while(true) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
p.print2();
}
}
}.start();
}
}
class Printer2 {
Demo d = new Demo();
//静态的同步方法的锁对象是什么?
//是该类的字节码对象 .class
public static synchronized void print1() {
//同步方法只需要在方法上加synchronized关键字即可
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.print("\r\n");
}
public static void print2() {
//synchronized(new Demo()) {
//锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(Printer2.class) {
System.out.print("a");
System.out.print("b");
System.out.print("c");
System.out.print("d");
System.out.print("\r\n");
}
}
}
死锁
- 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
- 尽量不要嵌套使用
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread() {
public void run() {
while(true) {
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) {
System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
synchronized(s1) {
System.out.println(getName() + "...拿到" + s1 + "开吃");
}
}
}
}
}.start();
}
停止线程
- 自然终止
要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
- 手动中止
暂停、恢复和停止操作对应在线程Thread的API就是suspend()
、resume()
和stop()
。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()
方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()
方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()
、resume()
和stop()
方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
安全的中止则是其他线程通过调用某个线程A的interrupt()
方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为true来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()
来进行判断当前线程是否被中断,不过Thread.interrupted()
会同时将中断标识位改写为false
。
如果一个线程处于了阻塞状态(如线程调用了thread.sleep
、thread.join
、thread.wait
、),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException
异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false
。
不建议自定义一个取消标志位来中止线程的运行。因为run
方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,一、一般的阻塞方法,如sleep
等本身就支持中断的检查,二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*类 说明:抛出InterruptedException异常的时候,要注意中断标志位
*/
public class HasInterrputException {
private static SimpleDateFormat formater
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while(!isInterrupted()) {
try {
System.out.println("UseThread:"+formater.format(new Date()));
Thread.sleep(3000);
} catch (InterruptedException e) { // sleep 会把中断信号清除
System.out.println(threadName+" catch interrput flag is "
+isInterrupted()+ " at "
+(formater.format(new Date())));
// TODO interrupt 需要在此内部 调用才能中断了,才能把被清楚的标记修改成true
interrupt();
e.printStackTrace();
}
System.out.println(threadName);
}
System.out.println(threadName+" interrput flag is " +isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread useThread = new UseThread("HasInterrputEx");
useThread.start();
System.out.println("Main:"+formater.format(new Date()));
Thread.sleep(800);
System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
useThread.interrupt(); // 被 InterruptedException e 人家清除
}
}
深入理解run()和start()
Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()
其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()
方法后,才实现了真正意义上的启动线程。
start()
方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()
方法,start()
方法不能重复调用。
而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,可以被单独调用。
总结
run 和start的区别 ?
答:run是函数调用 和线程没有任何关系, .start会走底层 会走系统层 最终调度到 run函数,这才是线程。
如何控制线程的执行顺序 ?
答:join来控制 让t2获取执行权力,能够做到顺序执行
多线程中的并行和并发是什么?
答:四个车道,四辆车并行的走,就是并行, 四个车道中,五秒钟多少的车流量,多少的吞吐量一样
在Java中能不能指定CPU去执行某个线程?
答:不能,Java是做不到的,唯一能够去干预的就是C语言调用内核的API去指定才行,这个你回答的话,面试官会觉得你研究点东西
在项目开发过程中,你会考虑Java线程优先级吗?
答:不会考虑优先级,为什么呢? 因为线程的优先级很依赖与系统的平台,所以这个优先级无法对号入座,无法做到你想象中的优先级,属于不稳定,有风险
因为某些开源框架,也不可能依靠线程优先级来,设置自己想要的优先级顺序,这个是不可靠的
例如:Java线程优先级又十级,而此时操作系统优先级只有2~3级,那么就对应不上
sleep和wait又什么区别?
答:sleep是休眠,等休眠时间一过,才有执行权的资格,注意:只是又有资格了,并不代表马上就会被执行,什么时候又执行起来,取决于操作系统调度
wait是等待,需要人家来唤醒,唤醒后,才有执行权的资格,注意:只是又有资格了,并不代表马上就会被执行,什么时候又执行起来,取决于操作系统调度
含义的不同:sleep无条件可以休眠, wait是某些原因与条件需要等待一下(资源不满足)
在Java中能不能强制中断线程的执行?
答:虽然提供了 stop 等函数,但是此函数不推荐使用,为什么因为这种暴力的方式,很危险,例如:下载图片5kb,只下载了4kb 等
我们可以使用interrupt来处理线程的停止,但是注意interrupt只是协作式的方式,并不能绝对保证中断,并不是抢占式的
如何让出当前线程的执行权?
答:yield方法,一般只在JDK某些实现才能看到,是让出执行权
sleep,wait,到底那个函数才会 清除中断标记?
答:sleep在抛出异常的时候,捕获异常之前,就已经清除