1.进程、线程(难度:⭐⭐⭐)
1.1什么是进程?什么是线程?
- 进程是一个应用程序(1个进程是一个软件)。
- 线程是一个进程中的执行场景/执行单元。
- 一个进程可以启动多个线程。
1.2(举例)对于Java程序来说,什么是进程?什么是线程?
- 当在DOS命令窗口中输入:Java Helloword 回车之后。
- 首先会先启动JVM,而JVM就是一个进程。
- JVM再启动一个主线程调用main方法。
- 同时再启动一个垃圾回收线程负责看护,回收垃圾。
- 最起码,现在的Java程序中至少有两个线程并发,
- 一个是垃圾回收线程,一个是执行main方法的主线程。
1.2.进程和线程是什么关系?举个例子
- 阿里巴巴:进程
马云:阿里巴巴的一个线程
保安:阿里巴巴的一个线程
- 京东:进程
强东:京东的一个线程
妹妹:京东的一个线程
- 进程可以看做是现实生活中的公司
- 线程可以看作是公司中的某个员工
1.2.1注意:
- 进程A和进程B的内存独立不共享(阿里巴巴和京东内存不会共享的!)
- 线程A和线程B呢?
- 在Java语言中:
- 线程A和线程B,堆内存和方法区内存共享。
- 但是栈内存独立,一个线程一个栈。
- 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行小各自的,这就是多线程并发。
- 火车站中的每一个售票窗口可以看作是一个线程。
- 我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
- 所以多线程并发可以提高效率。
- Java中之所以有多线程机制,目的就是为了提高程序的处理效率。
1.3.思考一个问题:
使用了多线程之后,main方法结束,是不是有 不会结束。
main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。
1.3.1一个线程一个栈 [ 图 文 ]
1.4.对于单核的CPU来说,真的可以做到真正的多线程并发吗?
1、对于多核的CPU电脑来说,真正的多线程并发是没有问题的。
- 4核CPU表示同一时间点上,可以真正的有4个进程并发执行
2、什么是真正的多线程并发?
- t1线程执行t1的
- t2线程执行t2的
- t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发。
3、单核的CPU表示只有一个大脑
- 不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
- 对于单核CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人的感觉是:多个事情同时在做!!
1.5.分析程序有几个线程
package com.newXianCheng.XianC01; /** * @Description: 分析有几个线程 * @auther:Li Ya Hui * @Time:2021年5月10日上午10:40:54 */ public class Test01 { public static void main(String[] args) { System.out.println("main begin"); m1(); System.out.println("main over"); } private static void m1() { System.out.println("m1 begin "); m2(); System.out.println("m1 over "); } private static void m2() { System.out.println("m2 begin "); m3(); System.out.println("m2 over "); } private static void m3() { System.out.println("m3 execute!"); } }
- 只有一个线程 主栈
- 没有启动分支栈,没有启动分支线程
- 所以这个只有一个主线程
2.实现线程的两种方式(难度:⭐⭐⭐)
其他后期再补充)
2.1.继承Thread
java支持多线程机制,并且Java已经实现了,我们只需要继承就行了
- 第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法
package com.newXianCheng.ThreadTest02; /** * @Description: 实现线程的第一种方式 * 编写一个类,直接继承java.lang.thread 重写run方法 * * 怎么创建线程对象? * 怎么启动线程呢? * @auther:Li Ya Hui * @Time:2021年5月12日上午8:36:11 */ public class Test01 { public static void main(String[] args) { //这里是main方法,这里的代码属于主线程,在主栈中运行 //新建一个分支线程对象 MyThread myThread = new MyThread(); //启动线程 //myThread.run(); //不会启动线程,不会分配新的分支栈。(这种方式就是单线程) //start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 //这段代码的任务只是为了开启一个新的栈空间出来,start方法就结束了,线程就启动成功了。 //启动成功的线程会自动调用run方法,并且run方法在分支线程的栈底部(压栈) //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。 myThread.start(); //这里的代码还是运行在主线程中。 for (int i = 0; i < 1000; i++) { System.out.println("主线程"+i); } } } class MyThread extends Thread{ @Override public void run() { //编写程序,这里程序运行在分支线程中(分支栈)。 for (int i = 0; i < 1000; i++) { System.out.println("分支线程"+i); } } }
2.2.实现Runnable接口实现Run方法
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
//定义一个可运行的类 class MyRunnable implements Runnable{ public void run() { } } //创建线程对象 Thread t = new Thread(new MyRunnable()); //启动线程 t.start
package com.newXianCheng.RunnableTesto1; /** * @Description:实现线程的第二种方式 java.lang.Runnable接口 * @auther:Li Ya Hui * @Time:2021年5月12日上午10:42:58 */ public class Test01 { public static void main(String[] args) { //创建一个可运行对象 MyRunnable myRunnable = new MyRunnable(); //将可运行的对象封装到一个线程对象 Thread t = new Thread(myRunnable); //启动线程 t.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()); } } } //这并不是一个线程类,是一个可运行的类,他还不是一个线程类。 class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()); } } }
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
2.2.1采用匿名内部类可以吗?
package com.newXianCheng.RunnableTesto1; /** * @Description: 采用匿名内部类可以吗? * @auther:Li Ya Hui * @Time:2021年5月12日上午11:36:02 */ public class Test02 { public static void main(String[] args) { //创建线程对象,采用匿名内部类方式 Thread t = new Thread(new Runnable() { @Override public void run() { } }); } }
2.3.run方法和start的区别
- myThread.run(); //不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
- start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
- 这段代码的任务只是为了开启一个新的栈空间出来,start方法就结束了,线程就启动成功了。
- 启动成功的线程会自动调用run方法,并且run方法在分支线程的栈底部(压栈)
- run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
run方法运行图
2.4.线程的生命周期
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
3.线程的一些内置方法(难度:⭐⭐⭐)
3.1如何设置/获取线程的名字
- 获取当前线程对象?
static Thread.currentThread()
class MyThread02 extends Thread{ public void run() { for (int i = 0; i < 100; i++) { //获取当前线程的对象 System.out.println(Thread.currentThread()); } } }
- 获取线程对象的名字
String name = 线程对象。getName();
- 修改线程对象名字
线程对象.setName(“线程名字”);
- 当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread-0
Thread-1
Thread-2
…
package com.newXianCheng.ThreadTest02; /** * @Description: 怎么获取当前线程对象 * 怎末获取对象的名字 * 修改线程的名字 * @auther:Li Ya Hui * @Time:2021年5月12日下午7:52:37 */ public class Test02 { public static void main(String[] args) { //创建线程对象 MyThread02 myThread02 = new MyThread02(); //设置线程的名字 myThread02.setName("tttt"); //获取线程的名字 String name = myThread02.getName(); System.out.println(name); //启动线程 myThread02.start(); } } class MyThread02 extends Thread{ public void run() { for (int i = 0; i < 100; i++) { System.out.println("分支线程-->"+i); } } }
3.2.线程睡眠
- static void sleep(long millis)
- 静态方法 sleep(1000);
- 参数是毫秒
- 作用:让当前的线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用。
- 这行代码出现在A线程,A线程进入睡眠
class MyThread02 extends Thread{ public void run() { for (int i = 0; i < 100; i++) { //获取当前线程的对象 System.out.println(Thread.currentThread().getName()); try { //让当前线程每次循环运行睡眠1秒 Thread.sleep(1000*1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
3.2.2sleep睡眠方法的面试题
为什么分支线程的睡眠方法会让主线程睡眠,因为sleep是静态方法
package com.newXianCheng.ThreadTest02; /** * @Description: 关于Thread.slppe的一个面试题 * @auther:Li Ya Hui * @Time:2021年5月12日下午9:46:25 */ public class SleepExam { public static void main(String[] args) throws InterruptedException { //创建线程对象 Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(1); } } }); //尽管是分支线程调用的睡眠,但是因为 sleep是 static Thread.sleep(1000); } }
3.3.终止线程的睡眠
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程呢?
重点:
- run()方法当中的异常不能throws ,只能try catch
- 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
语法
package com.newXianCheng.ThreadTest02; /** * @Description: 唤醒正在睡眠的线程 * @auther:Li Ya Hui * @Time:2021年5月12日下午9:46:25 */ public class SleepExam { public static void main(String[] args) throws InterruptedException { //创建线程对象 MyThread03 myThread03 = new MyThread03(); Thread thread = new Thread( myThread03); //尽管是分支线程调用的睡眠,但是因为 sleep是 static thread.start(); //唤醒线程 thread.interrupt(); } } class MyThread03 extends Thread{ public void run() { try { //让当前线程睡眠一年 Thread.sleep(1000*60*60*24*365); System.out.println("s"); } catch (InterruptedException e) { System.out.println("线程被中断"); } } }
3.4.线程的终止方法
3.4.1.stop方法
- 缺点容易造成数据损坏(不推荐使用)
//终止线程,缺点容易造成数据丢失 thread.stop();
3.4.2.stop方法
- 设置一个布尔标记
- 什么时候想终止,直接改布尔为 false 就可以
package com.newXianCheng.ThreadTest02; /** * stop方法不推荐使用 * @Description: 怎末合理的终止一个线程 这种方式是很常用的 * @auther:Li Ya Hui * @Time:2021年5月13日下午3:29:11 */ public class Test03 { public static void main(String[] args) { //任务 MyRunnable03 myRunnable03 = new MyRunnable03(); //线程类 Thread t = new Thread(myRunnable03); //线程启动 t.start(); try { //线程睡眠三秒后 Thread.sleep(3000); } catch (InterruptedException e) { } //终止线程 想要什么时候终止线程t的执行,那么你把标记修改为false,就结束了 myRunnable03.run= false ; } } class MyRunnable03 implements Runnable { boolean run = true; @Override public void run() { for (int i = 0; i < 10; i++) { if(run==true) { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" "); } catch (InterruptedException e) { } } } } }
3.5.线程调度
3.5.1常见的线程调度模型有哪些?
- 抢占式调度模型
- 哪个线程的优先级比较高,抢到的cpu时间片的概率就高一些/多一些
- java采用的就是抢占式调度模型
- 均分式调度模型
- 平均分配cpu时间片,每个线程占有的cpu时间片时间长度一样。
- 平均分配,一切平等。
- 有一些编程语言,线程调度模型采用的是这种方式
3.5.2.Java中提供了哪些方法是和线程调度有关系的呢?
- 线程优先级
- 线程优先级越高,获得CPU 时间片的概率就越大,但线程优先级的高低与线程的执行顺序并没有必然联系
- void setPriority(int newPriority) 设置线程的优先级
- int getPriority()获取线程优先级
- 最低优先级1
- 默认优先级5
- 最高优先级10
- 优先级比较高的获取cpu时间片可能会多一些(但也不完全是,大概率是多的)
- 语法
package com.newXianCheng.ThreadTest02; /** * @Description: 线程优先级的使用与讲解 优先级指的是 处于运行状态的时间多一些 * @auther:Li Ya Hui * @Time:2021年5月13日下午4:46:43 */ public class Test04 { public static void main(String[] args) { //线程静态成员变量 System.out.println("最高优先级"+Thread.MAX_PRIORITY); System.out.println("最低优先级"+Thread.MIN_PRIORITY); System.out.println("默认优先级"+Thread.NORM_PRIORITY); //获取当前线程对象,获取当前线程的优先级 Thread curreThread = Thread.currentThread(); //main线程优先级默认是5 System.out.println(curreThread.getName() + "线程的默认优先级是:"+curreThread.getPriority()); //创建分支线程 Thread t = new Thread(new MyRunnable4()); //调整分支线程优先级 t.setPriority(10); //调整main线程优先级 Thread.currentThread().setPriority(1); //启动分支线程 t.start(); //优先级较高的,只是抢到的CPU时间片相对多一些 //大概率方向更偏向于优先级比较高的 for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } } class MyRunnable4 implements Runnable{ @Override public void run() { //获取线程优先级 // System.out.println(Thread.currentThread().getName() + "线程的默认优先级是:"+Thread.currentThread().getPriority()); for (int i = 0; i < 300; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }
让位方法
- static void yield()让位方法
- 暂停当前正在执行的线程对象,并执行其他线程
- yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用。
- yield ( )方法的执行会让当前从“运行状态”回到就绪状态。
- 注意:再回到就绪之后,有可能还会再次抢到。
- 语法
package com.newXianCheng.ThreadTest02; /** * @Description: 让位,当前线程暂停,回到就绪状态,让给其他线程。 * 静态方法:thrad.yield(): * @auther:Li Ya Hui * @Time:2021年5月13日下午5:18:23 */ public class Test05 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable5()); t.setName("分支线程1"); t.setPriority(1); t.start(); for (int i = 1; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + "-->" + i); } } } class MyRunnable5 implements Runnable{ @Override public void run() { for (int i = 1; i < 10000; i++) { if (i%100 == 0) { // 当前线程暂停一下,让给主线程。 //yield方法不是阻塞方法,让当前线程让位,让给其他线程使用。 //会让当前线程从运行状态回到就绪状态 回到就绪之后,有可能还会再次抢到。 Thread.yield(); } System.out.println(Thread.currentThread().getName() + "-->" + i); } } }
合并线程
- void join ()合并线程
class MyThread extends Thread{ public void doSome() { MyThread2 t = new MyThread2(); t.join();//当前线程进入阻塞状态,t线程执行,直到t线程结束。当前线程才可以继续执行 } }