目录
2.3.继承Thread,重写run,使用匿名内部类( 的内部类)
一、线程的相关内容
1.1线程的概念
一个线程就是一个 "执行流". 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时" 执行着多份代码. 线程是系统调度的最小单位(PCB)
进程是操作系统对一个正在运行的程序(可看做程序的一次运行过程);
- 轻量级进程:创建/销毁/调度线程比进程更高效。
- 进程需要资源的申请和释放,更为繁琐,成本较大
为什么引入线程???------>
- 频繁创建和销毁进程成本较大
- "并发编程" 成为 "刚需":
- 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
- 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.
1.2进程和线程的区别
- 1.进程包含线程: 每个进程至少有一个线程(主线程)存在,同时也可包含多个线程,线程和进程共用一样的资源。
- 2.进程和线程都是为了处理并发编程的场景,但是进程效率更低,线程更为高效(少了申请释放资源的过程)
- 3.OS创建进程,需要给其分配资源,进程是系统分配资源的最小单位;OS创建线程,要在CPU上执行调度,线程是系统调度的最小单位。
- 4.进程具有独立性,(进程之间不共享内存空间),一个进程出问题,不会影响其他;同一个进程的线程之间共享同一个内存空间(虚拟地址),一个线程出现问题,会影响其他线程,进而导致整个进程崩溃。
1.3Java 的线程 和 操作系统线程的关系
- 操作系统中的线程描述:使用Thread类的对象来表示
- java代码中描述线程:Thread类
- PCB是在操作系统OS内核中描述线程
2.Java多线程编程(线程的构造方法)
Java标准库中,使用Thread类(API)操作线程。
操作系统提供一组关于线程的API(C语言风格),Java对API进行封装,就成了Thread类
创建线程的方法:
1.创建子类,继承Thread类,重写run
2.实现Runnable接口,重run ---->此方法常用,能让线程之间更好的解耦(符合代码要求:高内聚低耦合)
3.继承Thread,重写run,使用匿名内部类(一个没有类名的内部类)
4.实现Runnnable匿名内部类
5.lambda表达式
-
2.1.创建子类,继承Thread类,重写run
class myThread extends Thread {
@Override
public void run() {
System.out.println("hello");
}
}
(run方法内部为线程要执行的代码,新创建的线程要执行的逻辑,非主线程逻辑),run方法创建后并没有创建线程,定义子类实例后,调用start才创建了线程,再执行run方法的逻辑
Thread t = new myThread(); //此处创建的线程是在同一个进程里创建的
t.start();
Java进程中,至少会有一个调用main方法的线程(主线程),自己创建的 t 线程和自动创建的main线程属于并发执行的关系
class myThread2 extends Thread {
@Override
public void run() {
System.out.println("hello2");
try {
Thread.sleep(1000); //1s内休眠1000ms
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class Thread2 {
public static void main(String[] args) throws InterruptedException {
Thread t2 = new myThread2();
t2.start();
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
输出结果:随机输出两个线程的执行逻辑(因为系统对线程的调度(抢占式执行)随时都在发生变化,并发和并行都可能会执行)
-
2.2.实现Runnable接口,重run
class myRunnable implements Runnable{
@Override
public void run() {
System.out.println("hello");
}
}
public class Thread3 {
public static void main(String[] args) {
Thread t = new Thread(new myRunnable());
t.start();
}
}
-
2.3.继承Thread,重写run,使用匿名内部类( 的内部类)
public class thread4 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("hello thread");
}
};
t.start();
}
}
-
2.4.使用lambda表达式
public class thread4 {
public static void main(String[] args) {
Thread t = new Thread(() ->{
System.out.println("hello thread");
});
t.start();
}
}
2.5 多线程编程
多线程提高效率,如有两个变量进行10亿次自增,分别使用一个线程和两个线程来实现,进行比较
1.串行消耗的时间(单线程)
public class thread4 {
private static final long count = 10_0000_0000;
public static void func() {
//记录时间
long begin = System.currentTimeMillis();
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
long b= 0;
for (int i = 0; i < count; i++) {
b++;
}
long end = System.currentTimeMillis();
long time = end - begin;
System.out.println("消耗时间"+time+"ms");
}
public static void main(String[] args) {
func();
}
}
输出结果:28ms
2.多线程执行(要创建多个线程,同时引入join,main主线程等待创建的线程执行结束在执行计时)
//==============多线程执行=========================
public class thread4 {
private static final long count = 10_0000_0000;
public static void concurrent() throws InterruptedException {
long begin = System.currentTimeMillis();
Thread t1 = new Thread(() ->{
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
});
t1.start();
Thread t2 = new Thread(() ->{
long b = 0;
for (int i = 0; i < count; i++) {
b++;
}
});
t2.start();
//t1、t2 和main 是并发执行的关系,计算时间应该是main线程等待t1、t2执行结束之后,再计时
t1.join(); //join()就是等待线程结束, t1.join();就是main线程等待t1结束
t2.join(); //t2.join();就是main线程等待t2结束
long end = System.currentTimeMillis();
long time = end - begin;
System.out.println("消耗时间"+time+"ms");
}
public static void main(String[] args) throws InterruptedException {
concurrent();
}
}
输出结果:61ms
2.6 Thread常见构造方法
重点掌握给线程命名:使用jconsole来观察线程的名字(使用本地连接,选择你当前的文件名(我的是Thread6)进行连接即可)
此处的就是命名的线程名
public class Thead6 {
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
while (true) {
System.out.println("hello thread1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "thread1"); //把线程命名为thread1
t1.start();
Thread t2 = new Thread(() ->{
while (true) {
System.out.println("hello thread2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"thread2"); //把线程命名为thread2
t2.start();
}
}
注意!!!
- ID 是线程的唯一标识,不同线程不会重复------>getId()
- 名称是各种调试工具用到------>getName()
- 状态表示线程当前所处的一个情况,下面我们会进一步说明------>getState()
- 优先级高的线程理论上来说更容易被调度到------>getPriority()
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。------>isDaemon()
- 是否存活,即简单的理解,为 run 方法是否运行结束了------>isAlive()
- 是否中断------>isInterrupted()
创建一个线程,默认不是后台线程:若main方法结束,线程还未结束,JVM进程不会结束;
若当前线程是后台线程:main方法结束,线程还未结束,JVM进程直接结束
创建对象(Thread t )之后,在调用start之前,系统中没有对应的线程;而在run方法之后,系统中的线程就销毁了,但是 t 对象可能还存在。(通过isAlive可判断当前系统中线程的运行情况):
- 调用start之后,run执行之前,isAlive返回true
- 调用start之前,run执行之后,isAlive返回false
三、Thread中的重要方法
run方法只是一个普通的调用方法,描述任务的执行逻辑。start是一个特殊的方法,内部会在系统中创建线程。(main线程中调用run方法,并没有创建新的线程,仍然是在main线程中执行,故代码遵循顺序执行,先运行完第一个循环,再运行第二个循环,此处第一个循环为死循环,故只输出hello thread1)
public class thread7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while (true) {
System.out.println("hello thread1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// t.start(); //hello thread1和hello main交替输出
t.run(); //只输出hello thread1
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
3.1线程中断
线程中断的关键:run方法执行完毕或者main执行完毕
(1) 设置标志位(isQuit)中止线程:
public class Thread {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(() ->{
while (!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Thread.sleep(1000);
isQuit = true;
System.out.println("终止 t 进程");
}
}
(2)使用Thread中·内置的一个标志位来判断:
- Thread.interrupted()静态方法
- Thread.currentThread().isInterrupted()实例方法,currentThread可以获取到当前线程的实例
public class thread8 {
public static void main(String[] args) {
Thread t = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start(); //上述为线程逻辑
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt(); //中断逻辑
}
}
t.interrupt(); //中断逻辑,调用此方法产生2种情况:
- 线程处于就绪状态,设置标志位为true
- 线程处于阻塞状态(sleep休眠了),就会触发一个interruptException (常用)
3.2 等待一个线程 join
多个线程之间,调度顺序是不确定的,可通过线程等待控制线程执行顺序。
哪个线程调用 join ,哪个线程就会阻塞等待 (join是死等),可设置等待时间join(10000):等待10s
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start(); //首先是main线程执行
t.join(); // main线程等待t线程执行完再执行
}
代码执行到 join 之后,main线程进入阻塞状态(暂时不运行),而t线程开始执行run方法·直至完毕。
3.3 休眠线程sleep
一个进程对应一组PCB,一个线程对应一个PCB
sleep()方法本质 : 把线程PCB从就绪队列移动到阻塞队列
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
3.4线程的状态
线程的状态决定系统按照什么样的方式来调度(状态其实是绑定在线程上的)线程对应一个PCB,而进程对应的是一组PCB。
线程的6种状态(java中Thread类的状态):
- 1.NEW: 安排了工作,但还没开始行动。创建了Thread t对象,但还没具体执行(未调用start),(内核PCB还没创建);
- 2.RUNNABLE: 就绪状态。可工作的, 又可以分成正在工作中和即将开始工作(等待随时被调度到CPU)
- 3.TERMINATED: 当前pcb已经结束(run方法已经执行完毕,并销毁),Thread对象还在。
3种阻塞等待:
- 1.TIMED_WAITING: 当前pcb在阻塞队列中等待,带有时间的等待(Thread.sleep(1000))
- 2.WAITING: 线程中调用 wait 方法,也会阻塞等待,(死等)除非被唤醒
- 3.BLOCKED: 线程中尝试进行加锁,若发现锁被其他进程占用,出现的阻塞等待(使用synchronized进行加锁时可能出现的等待)
!!!开发过程中,程序出现"卡死"的现象,可能是由于一些关键线程出现阻塞:可查看关键线程的一个当前状态:Thread.currentThread()获取到当前线程,并获取状态:getState()