文章目录
- 1.认识线程
- 1.1 概念
- 1.1.1 线程是什么
- 1.1.2 为啥要有线程
- 1.1.3 进程和线程的区别
- 1.1.4 Java的线程和操作系统线程的关系
- 1.2创建线程
- 1.2.1 方法1继承Thread类
- 1.2.2 方法2实现Runnable接口
- 1.2.3 其他变形
- 1.3多线程的优势-增加运行速度
- 2.Thread 类及常见方法
- 2.1Thread的常见构造方法
- 2.2Thread的几个常见属性
- 2.3后台线程和前台线程
- 2.4 中断一个线程
1.认识线程
1.1 概念
1.1.1 线程是什么
多进程可以充分利用CPU资源去处理一些复杂业力,从而提升业务的处理效率。
创建进程—>申请资源—>加入PCB链表—>销毁进程—>释放资源—>把该进程从PCB链表中删除。
其中申请资源和释放资源对系统的性能影响比较大,涉及到内存和文件资源,处理一件事申请一份资源就够了,基于这样的思想,引出了线程的概念。线程用的是进程启动时从操作系统中申请的资源,线程也可以叫做轻量级的进程。
当创建一个进程的时候,每个进程都会包含一个线程,这个线程叫做主线程。每一个进程相当于一个公司,每个线程就相当于公司中的员工。
1.1.2 为啥要有线程
线程的优势:
- 线程的创建速度比进程快
- 线程的销毁速度比进程快
- 线程的CPU调度的速度比进程快
1.1.3 进程和线程的区别
进程和进程之间,涉及的各种资源彼此之间不受影响,也就是说进程与进程之间是相互独立的。
一个进程内线程之间是容易受到影响的。
通过多线程的方式可以提高程序处理任务的效率,创建进程的个数,根据CPU逻辑处理器的数量来作为参考。
问:如果无限制的创建线程,会不会进一步提升效率?
答:不一定。 当线程数小于逻辑处理器数时,会提升效率;当线程数大于逻辑处理器数时,由于过多的线程在阻塞等待状态,并没有真正的发挥并发的效果,反而因为创建线程消耗了系统资源。
问:会不会出现线程争抢资源的问题 ?
答:有可能会出现。某一个线程出现问题,就会影响其他的线程,从而影响整个进程。如果一个线程崩溃就会导致整个进程崩溃。
进程与线程的区别:
- 进程中包含线程,至少有一个主线程。
- 进程是申请资源的最小单位。
- 线程是CPU调度的最小单位。
- 线程共享进程申请来的所有资源。
- 一个线程如果崩溃了,就会影响整个进程。
1.1.4 Java的线程和操作系统线程的关系
线程是轻量级的进程,是操作系统的该娘,但是操作系统提供了一些API(应用程序编程接口,别人写好的一些函数/方法,直接使用即可
- List item
)供程序员使用。每个操作系统提供的API都不一样。Java对不同操作系统的API进行了封装,对外统一提供了一种调用方法,在Java中提供了Thread类(标准库中的线程类)
1.2创建线程
1.2.1 方法1继承Thread类
自定义的线程是一个类,run方法
public class Demo01_Thread {public static void main(String[] args) {MyThread01 myThread01 = new MyThread01();myThread01.start();//启动线程,申请操作系统中的PCB }
}//自定义一个线程类,继承JDK中的Thread类
class MyThread01 extends Thread{//定义线程的任务,根据业务需求,写代码逻辑即可。@Overridepublic void run() {while (true){System.out.println("hello my thread....");}}
}
输出结果:
启动线程之后,一个Java中的Thread对象就和操作系统中的PCB相对应。
操作系统中的线程PCB 和 Java中的线程,Thread类的一个对象,是对PCB的一个抽象。
Java中创建一个线程对象---->JVM调用系统中的API—>创建系统 中的线程。(系统中的线程才参与CPU的调度)
定义两个线程
public class Demo02_Thread {public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread();myThread.start();while (true){Thread.sleep(1000);System.out.println("hello main thread....");}}
}
class MyThread extends Thread{@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello my thread....");}}
}
输出结果:
两个线程同时进行,互不干扰,线程的执行顺序没有什么规律,这个和CPU调度有关,由于CPU调度是抢占式执行的,所以哪个线程占用当前CPU资源是不确定的。
Thread类中的run()方法和start()方法之间的区别?
- start()方法,是真实的申请系统线程PCB,从而启动一个线程,参与CPU调度。
- run()方法,定义线程时指定线程要执行的任务,只是Java对象的一个普通方法而已。
可以通过 JDK安装目录\bin目录下的jconsole.exe(JDK提供的一个查看JVM运行状态的工具)查看JVM中线程的状态。
1.2.2 方法2实现Runnable接口
public class Demo03_Thread {public static void main(String[] args) throws InterruptedException {//创建Runnable的实例MyRunnable01 myRunnable01 = new MyRunnable01();//创建线程Thread thread = new Thread(myRunnable01);//线程需要执行什么任务就对应的Runnable即可//启动线程,创建PCB,参与CPU调度thread.start();while (true){Thread.sleep(1000);System.out.println("hello main thread....");}}
}//单独定义了线程的任务,将线程类和业务解耦
class MyRunnable01 implements Runnable{//实现具体的任务@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello my runnable...");}}
}
输出结果:
1.2.3 其他变形
函数式接口:接口中只定义一个方法。
1. 通过Thread匿名内部类的方式创建线程
public class Demo05_ThreadCreat {public static void main(String[] args) {Thread thread = new Thread(){@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("通过Thread匿名内部类的方式创建线程...");}}};thread.start();}
}
输出结果:
- 创建了一个Thread类的子类,但没有为子类定义类名,匿名
- 在0里可以编写子类的代码定义,写法与正常类的实现方式相同
- 创建好的匿名内部类的实例赋值给thread变量
2. 通过Runnable接口的匿名内部类的方式创建线程
public class Demo06_ThreadCreate {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("通过Runnable接口匿名内部类的方式创建线程...");}}});thread.start();}
}
输出结果:
3. 通过lambda表达式创建线程(推荐使用)
public class Demo07_ThreadCreate {public static void main(String[] args) {Thread thread = new Thread(()->{System.out.println("通过Lambda表达式创建线程...");});thread.start();}
}
输出结果:
1.3多线程的优势-增加运行速度
情景:自增两个10亿次。
public class Demo01_Thread {public static long count = 10_0000_0000l;public static void main(String[] args) throws InterruptedException {//串行serial();//并行concurrency();}private static void concurrency() throws InterruptedException {//记录开始时间long begin = System.currentTimeMillis();//定义两个线程,分别进行累加操作//第一个线程Thread thread01 = new Thread(()->{long a = 0l;for (int i = 0; i < count; i++) {a++;}});thread01.start();//第二个线程Thread thread02 = new Thread(()->{long a = 0l;for (int i = 0; i < count; i++) {a++;}});thread02.start();//等待thread01和thread02两个线程执行完成thread01.join();thread02.join();//记录结束时间long end = System.currentTimeMillis();System.out.println("并行执行耗时:" + (end - begin));}private static void serial() {//记录开始时间long begin = System.currentTimeMillis();long a = 0l;for (int i = 0; i < count; i++) {a++;}long b = 0l;for (int i = 0; i < count; i++) {b++;}//记录结束时间long end = System.currentTimeMillis();System.out.println("串行执行耗时:" + (end - begin));}
}
输出结果:
串行执行耗时:1937
并行执行耗时:1079
通过多线程的方式,可以明显的提升效率,并行的耗时是串行的一半多一点的时间(有创建线程的消耗)。
问:如果仅将10亿改成10万,那么多线程还会不会提升效率?
答:不会。并不是任何时候多线程的效率都要比单线程高,当任务量很少的时候,单线程的效率可能会比多线程更高,创建线程本身也会有一定的系统开销,这个开销没有创建进程的开销大,两个线程在CPU上调度也需要一定的时间。
2.Thread 类及常见方法
2.1Thread的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象并命名 |
Thread(Runnable target, String name) | 使用Runnable对象创建线程对象,并命名 |
public class Demo02_Thread {public static void main(String[] args) {Thread t1 = new Thread(()->{while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello thread...");}});t1.start();Thread t2 = new Thread(()->{while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("我是一个有名字的线程...");}}, "我是一个线程");t2.start();}
}
输出结果:
可以通过Thread。currentThread().getName()来获取线程名。
public class Demo02_Thread {public static void main(String[] args) {Thread t1 = new Thread(()->{while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + " hello thread...");}});t1.start();Thread t2 = new Thread(()->{while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + " 我是一个有名字的线程...");}}, "我是一个线程");t2.start();}
}
输出结果:
public class Demo02_Thread {public static void main(String[] args) {Thread t2 = new Thread(()->{//获取当前类名String cName = Demo02_Thread.class.getName();//获取当前线程名Thread thread = Thread.currentThread();String tName = thread.getName();//获取当前方法名String fName = Thread.currentThread().getStackTrace()[1].getMethodName();while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("当前类:" + cName + " 当前方法:" + fName + " 当前线程:" + tName);}}, "我是一个线程");t2.start();}
}
输出结果:
当前类:lesson02.Demo02_Thread 当前方法:lambda$main$0 当前线程:我是一个线程
当前类:lesson02.Demo02_Thread 当前方法:lambda$main$0 当前线程:我是一个线程
当前类:lesson02.Demo02_Thread 当前方法:lambda$main$0 当前线程:我是一个线程
......
2.2Thread的几个常见属性
属性 | 获取方法 | 说明 |
---|---|---|
ID | getID() | JVM中默认为Thread对象生成的一个编号,是Java层面的,要和操作系统层面的PCB区分开 |
名称 | getName() | |
状态 | getState() | 是Java层面定义的线程状态,要和PCB区分开 |
优先级 | getPriority() | |
是否后台线程 | isDaemo() | 线程分为前台线程和后台线程,通过这个标识位来区分当前线程是后台还是前台 |
是否存活 | isAlive() | 表示的是系统中PCB是否销毁,与thread对象没啥关系 |
是否被中断 | isInterrupted() | 通过设置一个标志位让线程在执行时判断是否要退出 |
Thread是Java中的类–>创建Thread对象–>调用start()方法–>JVM调用系统API生成一个PCB–>PCB与Thread对象一一对应
Thread对象与PCB所处的环境不同 ,所以它们的生命周期也不相同。
线程是否存活
public class Demo04_Thread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("hello thread....");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("线程执行完成");});System.out.println("启动之前是否存活:" + thread.isAlive());thread.start();System.out.println("启动之后是否存活:" + thread.isAlive());//等待线程执行完成thread.join();//等待一会,确保PCB已销毁Thread.sleep(1000);System.out.println("启动之后查看线程是否存活:" + thread.isAlive());}
}
输出结果:
启动之前是否存活:false
启动之后是否存活:true
hello thread....
hello thread....
hello thread....
hello thread....
hello thread....
线程执行完成
启动之后查看线程是否存活:false
线程中断
- 通过共享的标记来沟通
public class Demo05_Thread {//定义一个标志位static boolean isQuit = false;//lambda表达式里面如果使用局部变量,会触发“变量捕获”,需要把这个变量定义为全局的。public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (!isQuit) {System.out.println("hello thread....");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}//线程退出System.out.println("线程退出");});//启动线程thread.start();//休眠5秒Thread.sleep(5000);//修改标志位isQuit = true;}
}
输出结果:
2. 调用interrupt()方法来通知
public class Demo06_Thread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{//通过线程对象内部维护的中断标识,判断当前线程是否需要中断while (!Thread.currentThread().isInterrupted()){System.out.println("hello thread...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("线程已退出");});//启动线程thread.start();//休眠Thread.sleep(5000);//中断线程,发出中断信号thread.interrupt();}
}
输出结果:
可以看到输出的结果是抛出了一个异常,然后继续往下执行,抛出的异常信息显示的是睡眠中断。
线程中具体的任务是打印一句话,所以线程中的大部分时间是在休眠,也就意味着线程是在休眠的时候中断的,所以就是中断的休眠状态下的线程,而不是执行线程任务的线程。
2.3后台线程和前台线程
public class Demo03_Thread {public static void main(String[] args) {Thread t1 = new Thread(()->{while (true) {System.out.println("hello thread...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//设置为后台线程t1.setDaemon(true);t1.start();System.out.println("线程是否存活:" + t1.isAlive());System.out.println("main方法执行完成");}
}
输出结果:
如果不设置为后台线程呢?
设置为后台线程之后,main方法执行完成之后整个程序就退出了,子线程也就自动结束了。
如果是前台线程,子线程不会受main方法的影响,会一直运行下去。
创建线程时默认时前台线程。
前台线程可以阻止进程的退出,后台线程不阻止进程的退出。
2.4 中断一个线程
正常执行任务的状态下中断线程
public class Demo07_Thread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{//通过线程对象内部维护的中断标识,判断当前线程是否需要中断while (!Thread.currentThread().isInterrupted()){System.out.println("hello thread...");}System.out.println("线程已退出");});//启动线程thread.start();//休眠Thread.sleep(5000);//中断线程,发出中断信号thread.interrupt();}
}
输出结果:
调用thread.interruptO方法时
- 如果线程在运行状态,直接中断线程,不会报异常,符合程序预期。
- 如果线程在等待状态,就会报一个中断异常,要在异常处理代码块中进行中断逻辑实现。