3.3变形写法.
- 说明:在有时候我们只是临时使用一次线程,此时就没必要专门定义线程类,浪费资源。那么我们就可以通过以下两种写法实现多线程。
3.3.1在同一个文件里面创建Thread子类.
3.3.2匿名内部类.
补充lambda表达式写法。
3.4注意事项.
- 子线程之间的运行时随机的,到底运行谁,由CPU分配时间片决定。
- 不是main线程执行结束后才执行子线程。程序是先从main方法开始执行的(即从main线程开始执行),如果main方法里执行了其他线程的start方法,其它线程就会进入就绪状态,这时就看CPU分配时间片给谁,谁就执行。
- 如果自己手动通过线程对象调用run()方法,那么就只是调用一个普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
- 想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出异常“IllegalThreadStateException”。
4.实现Runable接口方式.
- Java有单继承的限制,当我们无法继承Thread类时,那么该如何做呢?在核心类库中提供了Runnable接口,我们可以实现Runnable接口,重写run()方法,然后再通过Thread类的对象代理启动和执行我们的线程体run()方法。
4.1使用步骤.
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target参数(线程要执行的内容)来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法,启动线程。调用Runnable接口实现类的run方法。
4.2代码演示.
- 定义Runnable接口实现类.
- 测试:
4.3变形写法.
4.3.1在同一个文件里面创建Runnable接口实现类.
4.3.2匿名内部类方式.
- 写法一:未使用Lambda表达式。
- 写法二:使用Lambda表达式。
4.4注意事项.
- 通过实现Runnable接口,使得该类有了多线程类的特征。所有的分线程要执行的代码都在run方法里面。
- 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
- 实际上,所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
- 说明:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。可以理解为 Runnable对象就是一个数据体,里面包含了线程要执行的内容。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
5.继承Thread类和实现Runnable接口两种方式对比.
- Thread类实际上也是实现了Runnable接口的类。
- 继承Thread方式适合线程之间处理各自任务(不共享资源)情况。
- 实现Runnable接口比继承Thread类所具有的优势:
- 避免了单继承的局限性;
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源;
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
6.相关方法.
6.1设置线程名.
- 不设置线程名,默认是:Thread-0、Thread-1、Thread-2....这样命名。可以在线程的 run方法里面获取当前线程名测试 :Thread.currentThread().getName();
- 手动设置线程名:
- 继承Thread类方式:
- 通过创建的线程对象调用:setName("线程名");方法进行设置;
- 在线程类里面定义带参构造函数,然后调用父类构造函数。如:
- 实现Runnable接口方式:
6.2设置线程休眠- .
- Thread.sleep():是Thread类的一个静态方法。作用:使当前线程休眠,进入阻塞状态(暂停执行),简单理解就是“线程暂停方法”。该方法可接收长整型毫秒和整型纳秒,可读性差(因为需要根据毫秒数或者纳秒数换算成其它时间单位)。
- TimeUnit.SECONDS.sleep():是 concurrent 包下的一个类,提供了可读性更好的线程暂停操作。里面提供了很多枚举类,如:
枚举 |
单位 |
DAYS |
天 |
HOURS |
小时 |
MINUTES |
分钟 |
SECONDS |
秒 |
MILLISECONDS |
毫秒(千分之一秒) |
MICROSECONDS |
微秒(一百万分之一秒(就是毫秒/1000)) |
NANOSECONDS |
毫微秒(十亿分之一秒(就是微秒/1000)) |
- 使用方式如下:
- 除了枚举之外可以进行时间转换:
6.3设置&获取线程优先级.
- getPriority() :返回线程优先级;
- setPriority(int newPriority) :改变线程的优先级,范围在[1,10]之间。
6.4join方法.
- join方法的作用是:在当前 线程执行的某个阶段,加入并执行其它线程。
- 案例:
6.5yield方法.
- yield方法的作用是当前正在运行的线程放弃CPU资源,重新回到就绪状态.
- 案例:
6.6stop方法.
- stop():已过时,不建议使用。强行杀死执行,直接进入死亡状态。run()即刻停止,可能会导致一些清理性的工作得不到完成,如文件,数据库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。比如转账转了一半,突然停止。
- 案例演示:
- 运行结果:
- 当线程2运行到第10次的时候就会暴力停止线程1的运行。但是从结果上发现,当线程2运行完第10次后,线程1又运行了一次,然后才被停止的,这是因为当线程2执行完第10次的输出语句后,还没来得及执行暴力停止线程1的代码,CPU马上给线程1分配时间片,线程1运行,然后CPU再给线程2分配时间片,线程2运行,马上暴力停止线程1.
- 这样会导致线程1在停止的时候没有来得及处理后事,不安全。就好比,你在工厂里面生成产品,别人突然叫你停止生产,连给你关闭设备的时间都没有留。正确的应该是先提前通知你要停止生产了,给你时间关闭设备。