JavaEE 【知识改变命运】03 多线程(2)
文章目录
- 复习
- 1.1 进程和线程的区别
- 1.2 线程创建的方式
- 1.3 两者创建的区别
- 2 多线程
- 2.1多线程的优势-增加了运行的速度
- 2.2Thread类及常用的方法
- 2.2.1常用见的构造方法
- 2.2.2获取当前类的信息
- 2.2.3Thread 的⼏个常⻅属性
- 1 演示后台线程
- 2 线程是否存活
- 3 名称
- 4 线程中断
- 5 等待⼀个线程 - join()
- 6 获取当前线程引⽤
- 7 休眠当前线程
- 2.3.4 线程的状态
复习
1.1 进程和线程的区别
- 进程是申请资源的最小单位
- 线程是cpu调度的最小单位
- 创建一个进程,里面会自动生成一个主线程,进程里面包含线程
- 进程与进程之间互不影响,线程与线程之间可能会造成影响,一个线程的崩溃会导致整个进程的结束。
5 线程之间共享进程的资源
1.2 线程创建的方式
继承Thread类创建方式(线程对象),重写run()方法(线程执行的任务)
实现Runnable接口创建方式,重写run()方法(线程执行的任务)
其他三种:匿名类Thread,匿名Runnable,lambda表达式Runnable接口创建方式
1.3 两者创建的区别
因为java是单继承模式,所以当一个类继承了父类,就不能再继承Thread这个类了,Runnable接口式实现,解决了单继承的这种限制
接口式创建,有高内聚低耦合的特点,把线程类和要执行的代码块分开了,后面修改代码对程序的影响较小,
2 多线程
2.1多线程的优势-增加了运行的速度
场景1:进行两次自增10_0000_0000次的自增
private static long count=10_0000_0000; public static void main(String[] args) { //串行方式 serivalThread(); //并行方式 parallelThread(); } private static void parallelThread() { long start = System.currentTimeMillis(); Thread thread01 = new Thread(()-> { int a = 0; for(int i = 0; i <count; i++) { a += i; } }); Thread thread02 = new Thread(()-> { int b = 0; for(int i = 0; i <count; i++) { b += i; } }); thread01.start(); thread02.start(); try { thread02.join(); thread01.join();//一定要加这个join等待,要不然算出来的时间只是创建一个线程的时间 } catch (InterruptedException e) { throw new RuntimeException(e); } long end = System.currentTimeMillis(); System.out.println("并行执行时间:"+(end-start)); } private static void serivalThread() { long start = System.currentTimeMillis(); int a = 0; for(int i = 0; i <count; i++) { a += i; } int b = 0; for(int i = 0; i <count; i++) { b += i; } long end = System.currentTimeMillis(); System.out.println("串行执行时间:"+(end-start)); } }
执行结果:
这里注意一下:
通过多线程的方式明显提升效率,并行的耗时是串行的一半多一点时间,多一点的时间是创建线程时候消耗的时间
场景2:我们把自加次数降为50万次 执行结果;
注意:并不是任何时候多线程的效率比单线程的高,当任务量很少的时候,单线程的效率可能会比多线程更高
因为创建线程本身就是也有一定的系统开销,这个开销没有进程的开销大,两个线程在cpu上面调度也需要一定的时间
2.2Thread类及常用的方法
2.2.1常用见的构造方法
创建线程对象
使用Runnable创建线程对象
创建线程对象,并命名
使用Runnable创建线程对象,并命名
默认的线程名Thread-N,N>=0;public static void main(String[] args) { Thread thread = new Thread(new Runnable(){ @Override public void run() { System.out.println("我的线程名:"+Thread.currentThread().getName()); } },"Runnable线程"); thread.start(); } } ```执行结果: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c68e1eee57af43ed948b4cc0e3d53176.png)
2.2.2获取当前类的信息
public class Mian {
public static void main(String[] args) {
Thread thread = new Thread(()->{
//获取类名
String className = Mian.class.getName();
System.out.println(className);
//获取线程对象
Thread thread1 = Thread.currentThread();
System.out.println(thread1);
//获取线程名
String name = thread1.getName();
System.out.println(name);
String mName= thread1.getStackTrace()[1].getMethodName();
System.out.println(mName);
},"我是一个线程");
thread.start();
}
}
2.2.3Thread 的⼏个常⻅属性
- id:jvm层面的,jvm默认为Thread对象生成一个编号,和PCB区分开(操作系统层面) 名字:java层面
- 状态:java层面定义的状态
- 优先级:
- 是否后台线程:java层面,线程分为前台线程和后台线程,通过这个表示位来区分当前前台线程还是后台线程,关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
- 是否存活:PCB层的,表示系统中的PCB是否销毁,与Thread对应没啥关系 ,即简单的理解,为 run ⽅法是否运⾏结束了
- 是否被中断:通过设置一个标志位让线程执行时候判断是否要退出
- 名称:名称是各种调试⼯具⽤到
- 注意:Thread是java中的类-----创建Thread对象----->调用strat()方法----->JVM调用系统的API生成一个PCB----PCB与Thread对象一一对应
Thread对象与PCB所处的环境不同,所以他们的生命周期也不同。public static void main(String[] args) { Thread thread = new Thread(() -> { for (int i = 0; i < 10; i++) { try { System.out.println(Thread.currentThread().getName() + ": 我还活着"); Thread.sleep(1 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ": 我即将死去"); }); System.out.println(thread.getName() + ": ID: " + thread.getId()); System.out.println(thread.getName() + ": 名称: " + thread.getName()); System.out.println(thread.getName() + ": 状态: " + thread.getState()); System.out.println(thread.getName() + ": 优先级: " + thread.getPriority()); System.out.println(thread.getName() + ": 后台线程: " + thread.isDaemon()); System.out.println(thread.getName() + ": 活着: " + thread.isAlive()); System.out.println(thread.getName() + ": 被中断: " + thread.isInterrupted()); thread.start(); while (thread.isAlive()) {} System.out.println(thread.getName() + ": 状态: " + thread.getState()); } }
1 演示后台线程
public static void main(String[] args) { Thread thread = new Thread(new Runnable(){ @Override public void run() { while (true){ System.out.println("hello myRunnable"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); // thread.setDaemon(true); thread.start(); System.out.println("thread 是否存活"+thread.isAlive()); System.out.println("main thread 已经结束"); } }
- 子线程没有设置为后台线程,main线程结束,子线程不会结束 ,进程不结束
- 子线程设置为后台线程,main线程结束,子线程会跟着结束,进程结束
- 补充:后台线程类似守护线程,main线程类似用户线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为了工作线程服务的,当所有的用户线程结束守护线程自动结束,
- 常见的守护线程:垃圾回收机制
- 前台线程可以阻止进程结束
- 后台线程不能阻止进程结束
2 线程是否存活
InterruptedException { Thread thread = new Thread(new Runnable(){ @Override public void run() { { System.out.println("hello myRunnable"); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); System.out.println("thread 是否存活"+thread.isAlive());//main thread.start();//子线程 System.out.println("thread 是否存活"+thread.isAlive());//main thread.join(); // 保证PCB 已经结束 System.out.println("thread 是否存活"+thread.isAlive());//main,虽然PCB结束但是子线程java层面的对象还在,因为生命周期不一样 //System.out.println("main thread 已经结束"); } }
3 名称
public static void main(String[] args) { Thread thread = new Thread(()->{ //获取类名 String className = Mian.class.getName(); //获取方法名对象 Thread thread1 = Thread.currentThread(); //获取线程名 String name = thread1.getName(); //获取线程的方法名 String mName= thread1.getStackTrace()[1].getMethodName(); System.out.println("当前类名"+className+"方法名"+mName+"()"+"线程名"+name); },"我是一个线程"); thread.start(); thread.setName("hha")//设置线程名称,使之与参数name相同 } }
通过类+方法+线程的名称的方法 可以明确的记录某一个线程所产生的日志
4 线程中断
- 通过共享的标记来进⾏沟通
private static class MyRunnable implements Runnable { public volatile boolean isQuit = false; @Override public void run() { while (!isQuit) { System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + ": 啊!险些误了⼤事"); } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。"); thread.start(); Thread.sleep(10 * 1000); System.out.println(Thread.currentThread().getName() + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!"); target.isQuit = true; } } ```
- 调⽤ interrupt() ⽅法来通知 使⽤ Thread.interrupted() 或者
Thread.currentThread().isInterrupted() 代替⾃定义标志位.private static class MyRunnable implements Runnable { @Override public void run() { // 两种⽅法均可以 while (!Thread.interrupted()) { //while (!Thread.currentThread().isInterrupted()) { System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(Thread.currentThread().getName() + ": 有内⻤,终⽌交易!"); // 注意此处的 break break; } } System.out.println(Thread.currentThread().getName() + ": 啊!险些误了⼤事"); } } public static void main(String[] args) throws InterruptedException { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target, "李四"); System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。"); thread.start(); Thread.sleep(10 * 1000); System.out.println(Thread.currentThread().getName() + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!"); try { thread.interrupt(); } catch (Exception e) { throw new RuntimeException(e); } } } ```
调用thread.interrput();方法时候
1:如果线程在运行状态,直接中断线程,不会报异常,符合程序预期
2:如果线程处于等待状态,就会报一个中断异常,要在异常处理代码块中段逻辑实现
当线程sleep状态时,执行中断操作,中断的是休眠状态的线程,就会抛出这个异常
3:PCB依然存在,任务继续执行
4. thread 收到通知的⽅式有两种:
- 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志 ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法.可以选择忽 略这个异常, 也可以跳出循环结束线程.
- 否则,只是内部的⼀个中断标志被设置,thread 可以通过 ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到。
5 等待⼀个线程 - join()
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在⼯作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我结束了!");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始⼯作");
thread1.start();
thread1.join();
System.out.println("李四⼯作结束了,让王五开始⼯作");
thread2.start();
thread2.join();
System.out.println("王五⼯作结束了");
}
}
6 获取当前线程引⽤
Thread.currentThread()在那个线程中调用获取的就是那个线程的对象。7 休眠当前线程
因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。
2.3.4 线程的状态
线程的状态继承在一个枚举中
public class Main {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
总共有六种状态:
NEW:表示创建好了一个java线程对象,安排好了任务,但是没有启动,没有调用start()方法之前是不会创建PCB的,和PCB没有关系
RUNNABLE::运行+就绪状态,在执行任务时候最常见的状态之一,在系统中有对应PCB
BLOCKED:等待锁状态,阻塞的一种
WATING:没有等待时间,一直死等,直到被唤醒 TIMED_WAIRING:指定了等待时间的阻塞状态,过时不候
TERMINATED:结束,完成状态,PCB已经销毁,但是java线程对象还在
有时侯会说有七种状态,是因为又把RUNNABLE又细分为两个状态:Ready(就绪)Running
其中的yield:线程的礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功
观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0; i < 1000_0000; i++) { } }, "李四"); System.out.println(t.getName() + ": " + t.getState());; t.start(); while (t.isAlive()) { System.out.println(t.getName() + ": " + t.getState());; } System.out.println(t.getName() + ": " + t.getState());; } ``
观察 2: 关注 WAITING 、 BLOCKED TIMED_WAITING 状态的转换
public static void main(String[] args) { final Object object = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized (object) { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }, "t1"); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (object) { System.out.println("hehe"); } } }, "t2"); t2.start(); } ``` 使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED
修改上⾯的代码, 把 t1 中的 sleep 换成 wait
public static void main(String[] args) { final Object object = new Object(); Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized (object) { while (true) { try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }, "t1"); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized (object) { System.out.println("hehe"); } } }, "t2"); t2.start(); } `` 使⽤ jconsole 可以看到 t1 的状态是 WAITING
结论:
BLOCKED 表⽰等待获取锁, WAITING 和 TIMED_WAITING 表⽰等待其他线程发来通知. •
TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在⽆限等待唤醒
wait()是Object类里面的,Join()和sleep()是Thread类里面的。