像我们之前写过的所有代码都是只用了一个核心,不论我们如何优化代码,最多也只能用到一个核心,即便是跑满这个核心,其他核心也是空着的; 通过写特殊的代码,把多个CPU核心,都能利用起来,这样的代码就称为“并发编程" 多进程编程,就是一种典型的并发编程. 多进程编程的缺陷:当需要频繁的创建和销毁编程时,消耗的时间和空间就会明显的增加,再者有些任务场景需要"等待IO",为了让等待IO的时间能够去做⼀些其他的⼯作,也需要用到并发编程. 因此为了解决进程开发较大的问题,创建了线程,线程可以理解成,更轻量的进程. 也能解决并发编程的问题,但是创建/销毁的开销,要比进程更低. 线程概念:一个线程就是一个"执行流".每个线程之间都可以按照顺序执行自己的代码.多个线程之间"同时"执行着多份代码. 因此,多线程的编程,就成了当下主流的并发编程方式. 多线程编程相比多进程线程的优势?
所谓的进程,在系统中,是通过PCB这样的结构体来描述,通过链表的形式来组织的 对于系统中,线程,同样也是通过PCB来描述的(Linux) 进程和线程的关系: 一个进程,其实是一组PCB 一个线程,是一个PCB 一个进程包含了多个线程,此时每个线程,都可以独立的到CPU上调度执行. 线程是系统,"调度执行"的基本单位. 进程是系统"资源分配"的基本单位. 可执行程序在操作系统中的基本运行过程? 一个可执行程序,运行的时候(双击)操作系统就会创建进程,给这个程序分配各种系统资源(CPU,内存,硬盘,网络带宽..)同时也会在这个进程中,创建出一个或者多个线程.这些线程再去CPU上调度执行.
我们上一篇文章所说的进程调度实际上就是线程调度,只不过时相对于一个线程的进程来说的,如果有多个线程在一个进程中,每个线程,都会有自己的状态,优先级,上下文,记账信息,每个都会各自独立的在CPU上调度执行. 进程和线程的区别
线程数目越多越好吗?怎样进一步提高效率?
线程是操作系统的概念,操作系统提供了一些API可以操作线程,不同的系统API也不同,Java对系统API进行了封装,使用Thread类就可以创建出一个线程.
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("hello run");
try {
Thread.sleep(1000);//1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();//回调
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}为什么Thread这个类不需要进行导包? 在java.lang包下的所有类不需要进行导包操作,Thread,String...都不需要导包 代码说明:

此时我们可以借助第三方工具jconsole来查看线程的情况,该工具在jdk目录下的bin文件夹下

注意:要在代码运行的情况下打开,不能停止 若没有信息可尝试使用管理员身份打开

一个Java中不止有两个线程 main函数就是主线程,Thread-0就是t.start创建的新线程(代码中自己创建的线程命名的规律就是Thread-数字)


作用: 这些线程都是起到了一些辅助作用
package thread;
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}代码说明:
好处:使用Runnable这种方法的是更有利于解耦合
package thread;
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}代码说明:
好处:内聚性更好一些
通过匿名内部类来实现,本质就是方法二
package thread;
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t = new Thread(runnable);
t.start();
while(true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
//更为简便的方法
package thread;
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
while(true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}package thread;
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

package thread;
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("线程ID:" + t.getId());
System.out.println("线程Name:" + t.getName());
System.out.println("线程状态:" + t.getState());
System.out.println("线程优先级:" + t.getPriority());
}
}
注意:可能大家的线程状态会出现RUNNABLE的状态, 是因为Thread-0和main是两个线程,这两个线程是并发执行的,当先执行main线程的打印就是RUNNABLE状态,先执行Thread-0的sleep就是TIMED_WATTING状态
常见属性详解:
辨析前台线程和后台线程 后台线程/守护线程:在执行过程中不能阻止进程结束(虽然线程在运行,但当进程结束后线程也会随之带走) 我们在main线程中t.start执行创建的线程Thread默认是前台线程
如何将线程设置为后台(守护)线程?
package thread;
public class Demo8 {
public static void main(String[] args) throws InterruptedException {
//Thread后台线程
Thread t = new Thread(() ->{
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setDaemon(true);//设置为后台线程,默认是前台线程
t.start();//启动Thread线程
//main线程中的前台线程,当前台线程结束后后台线程也会被带走
for (int i = 0; i < 3; i++) {
System.out.println("hello main");
Thread.sleep(2000);
}
}
}使用的注意事项:
setDaemon方法默认是前台线程,只有当需要设置为后台线程时才需要使用该方法
setDaemon方法必须在线程启动(即调用start()方法)之前调用。否则,会抛出IllegalThreadStateException异常。
一旦线程开始运行,就无法更改其守护线程的状态。
在守护线程中执行的操作应该尽快完成,因为当所有非守护线程结束时,守护线程会被立即终止,而不会执行完后台线程方法中的剩余任务。 当main前台线程执行完后,Thread线程也会被随之带走,两个线程是并行执行的,不能确定谁先执行谁后执行
前台线程:在执行过程中能阻止进程结束 前台线程(例如main主线程,Thread线程)能阻止进程结束,后台线程不能阻止进程结束
isAlive是否存活:用来判断内核线程是否存在,true表示内核的线程存在,false表示内核的线程不存在 代码中,创建的new Thread对象,生命周期,和内核中实际的线程是不一定一样的. 可能会出现,Thread对象仍然存在,但是内核中的线程不存在了这样的情况.(但是不会出现 Thread对象不存在,线程还存在这种) 1)调用start之前,内核中,还没创建线程 2)线程的run 执行完毕了,内核的线程就无了.但是Thread 对象,仍然存在. 此时我们就可以通过isAlive来区分 线程的执行顺序是未知的,当两个线程的sleep时间相同时,执行完sleep后不知道接下来两个线程谁先执行谁后执行
package thread;
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
for (int i = 0; i < 3; i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(t.isAlive());//false
t.start();
//此处Thread线程启动后不知道先执行main线程中的isAlive语句还是Thread中的打印语句
//当Thread线程先执行则main线程中的isAlive语句就是true
//当Thread线程后执行则main线程中的isAlive语句就是false
System.out.println(t.isAlive());
Thread.sleep(3000);
System.out.println(t.isAlive());
}
}代码解释:
此处Thread线程启动后(t.start)不知道先执行main线程中的isAlive语句还是Thread中的打印语句
当Thread线程先执行则main线程中的isAlive语句就是true
当Thread线程后执行则main线程中的isAlive语句就是false那为什么执行起来一直是true呢? 上述所说的不一定,不是指,双方概率均等. 实际上这里的两种情况的概率,会随着你系统的不同,随着你代码运行环境的不同,都可能存在差异
调用start方法,才真的在操作系统的底层创建出一个线程. 在Thread类下只有当t.start执行后才算是创建了线程,然后才会去执行run main方法作为程序的入口不需要用
start()方法启动线程,它会自动运行在一个由JVM创建的主线程中,而你自己创建的线程(Thread)则需要通过调用start()方法来启动。
经典面试题之start和run方法的区别?
start的执行速度非常快,当start执行完毕后新线程就会开始执行,调用start的线程main线程也会继续执行,即两个线程并发执行
调用start,不一定非得是main线程调用.任何的线程都可以创建其他线程.
但一个Thread对象一次只能调用一次start即一个对象只能对应系统中一个线程,此时是因为第一次调用start时Thread的状态是NEW,调用后状态就会发生改变,只有状态是NEW时才能成功调用
package thread;
public class Demo10 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("t");
Thread t2 = new Thread(() -> {
System.out.println("t2");
});
t2.start();
});
t.start();
}
}B正在运行,A想让B结束 其实核心就是A要想办法让B的run方法执行完毕,此时B就自然结束了.而不是B的run 执行一半,A直接把B强制结束了.这样做是为了确保B执行完,避免是一个半成品,这里是让B更加快速的执行完
方法一:
package thread;
public class Demo11 {
private static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!isQuit) {
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(2000);
System.out.println("main线程尝试中断t线程");
isQuit = true;
}
}为什么将isQuit放到main中就会出现报错呢?
变量捕获问题,isQuit和lambda定义在一个作用域中,此时lambda 内部,是可以访问到lambda外部(和lambda同一个作用域)中的变量的
观察报错信息:

final意味着它的值在初始化之后就不能再被修改。final,只要它在初始化之后没有被重新赋值,Java编译器也会将其视为effectively final。这意味着你可以在不显式使用final关键字的情况下在lambda表达式中使用它。因此我们要写成成员变量的方式,此时进行的就是内部类访问外部类(内部类本身就能访问外部类成员)
lambda表达式,本质上是一个"函数式接口"产生的“匿名内部类"
方法二:
package thread;
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
Thread currentThread = Thread.currentThread();
while (!currentThread.isInterrupted()){
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Thread.sleep(2000);
t.interrupt();
}
}代码解释:
我们发现当我们运行的时候会抛出RuntimeException异常信息

原因:
由于isInterrupt()判断语句和打印语句执行速度很快,因此整个循环基本上都在执行sleep(1000),当main线程调用interrupt时,大概率t线程正处于休眠状态
此处Interrupt不仅仅能设置标志位,还能把刚才这个sleep 操作,给唤醒 比如, sleep 此时刚睡了100ms,还剩900ms,此时Interrupt被调用了 此时sleep就会直接被唤醒,并且抛出InterruptedException异常
由于catch 中默认代码再次抛出异常,再次抛出的异常 没人catch,最终就到了JVM这一层,进程就直接异常终止了.
当我们将catch语句改成简单的sout打印语句时发现虽然不抛出异常了但并没有进行中断操作,标志位好像没设置一样,一直在执行,明明此时的interrupt把sleep唤醒了,异常也被catch住了,那原因是什么呢?
首先标志位肯定是设置了的只是sleep等阻塞的函数被唤醒之后,就会先清空刚才设置的interrupted标志位,因此想要结束线程就需要在catch中return/break
这里我们通过一个简单的例子来理解这种中断操作
Java中,终止线程,是一个"温柔"的过程,不是强行就终止了
我和女朋友正在看电视,突然女朋友跟我说她渴了让我去帮他买瓶水,此时我可以做出三种选择:
Java中,终止线程,是一个"温柔"的过程,不是强行就终止了
A希望B线程终止,B收到这样的请求之后,B需要自行决定,是否要终止/立即还是稍后(B线程内部的代码来决定的,其他线程无权干涉
线程等待:在操作系统中针对多个线程的执行,是一个随即调度,抢占式执行的过程
线程等待,就是在确定两个线程的"结束顺序" 无法确定两个线程调度执行的顺序,但是可以控制,谁先结束,谁后结束. 让后结束的线程,等待先结束的线程即可. 此时后结束的线程就会进入阻塞,一直到先结束的线程,真的结束了,阻塞才解除 t等main:
public class Demo14 {
public static void main(String[] args) {
Thread t = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("t开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t结束");
});
t.start();
System.out.println("main开始");
try {
t.join(); //main等待t t执行完再执行main
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main结束");
}
}

main等t: 在执行主线程的内容时,加上等待时间,确保t线程执行完毕 此时join并没有发生阻塞,t线程已经结束了 join就是确保,被等待的线程,能够先结束. 如果已经结束了, join就不必再等了
public class Demo15 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("t开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t结束");
});
t.start();
Thread.sleep(4000);
//此处时间必须大于for循环整体的时间,这样才能保证t线程执行完再执行main线程
System.out.println("main开始");
try {
t.join(); //main等待t t执行完再执行main
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main结束");
}
}
t2等t1: 必须在t2执行前来设置t.join() a线程中调用b.join就是a等待b结束
public class Demo17 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 2; i++) {
System.out.println("t1开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1结束");
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("t2开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
});
System.out.println("main开始");
t1.start();
t2.start();
}
}以上操作中使用到的join操作都是无参的版本,无参数,意思是"死等" 被等待的线程,只要不执行完,这里的等待,就会持续阻塞


实现t线程等待main线程
//t线程等待主线程
public class Demo18 {
public static void main(String[] args) {
Thread mainthread = Thread.currentThread();//获取main线程
Thread t = new Thread(() -> {
try {
mainthread.join();//t等待main
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("t开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t结束");
});
System.out.println("main开始");
t.start();
System.out.println("main结束");
}
}Thread.sleep让调用的线程阻塞等待是有一定时间的,当线程执行sleep时就会使这个线程不参与cpu的调度,从而把cpu的资源让出来给别人使用,也叫“放权”操作 某个线程cpu占用率过高时就可以通过sleep来进行改善
public class Demo19 {
public static void main(String[] args) throws InterruptedException {
//WAITING
Thread mainThread = Thread.currentThread();
Thread t = new Thread(() -> {
while (true){
try {
System.out.println(mainThread.getState());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
t.join();
//System.out.println(t.getState());
}
public static void main4(String[] args) throws InterruptedException {
//TIMED_WAITING
Thread t = new Thread(() -> {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(500);
System.out.println(t.getState());
}
public static void main3(String[] args) throws InterruptedException {
//RUNNABLE
Thread t = new Thread(() -> {
while (true){
}
});
t.start();
System.out.println(t.getState());
}
public static void main2(String[] args) throws InterruptedException {
//TERMINATED
Thread t = new Thread(() -> {
});
t.start();
Thread.sleep(1000);
System.out.println(t.getState());
}
public static void main1(String[] args) {
//New
Thread t = new Thread(() -> {
for (int i = 0; i < 2; i++) {
System.out.println("t开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t结束");
});
System.out.println(t.getState());
t.start();
}
}