多线程(二)
线程的生命周期
线程的生命周期有:新建状态、就绪状态、运行状态、阻塞状态、死亡状态
获取线程对象、名字、修改线程名
package se5.thread;
/*
1.怎么获取当前线程对象
Thread t = Thread.currentThread();
返回值t就是当前线程
2.获取线程对象的名字:String name = 线程对象.getName();
3.修改线程对象的名字:线程对象.setName("线程名字");
4.当线程没有设置名字的时候,默认的名字的规律如下:
Thread-0
Thread-1
Thread-2
...
*/
public class ThreadTest05 {
public static void main(String[] args) {
//currentThread就是当前线程对象
//这个代码出现在main方法中,所以当前线程就是主线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName());//main
//创建线程对象
MyThread2 t = new MyThread2();
//设置(修改)线程的名字,若不设置线程的名字是:Thread-0
t.setName("t1");
//获取线程的名字
String tName = t.getName();
System.out.println(tName);//t1
MyThread2 t2 = new MyThread2();
t2.setName("t2");
System.out.println(t2.getName());//t2
//启动线程
t2.start();
t.start();
/**
* 部分执行结果:
* t1--->0
* t2--->19
* t1--->1
* t2--->20
*/
}
}
class MyThread2 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
//currentThread就是当前线程对象
//当t1线程执行run方法,当前线程就是t1
//当t2线程执行run方法,当前线程就是t2
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "--->" + i);
//以下方式也可以,不过有局限性
//System.out.println(super.getName() + "--->" + i);
//System.out.println(this.getName() + "--->" + i);
/**
* 这样不行:
* this.getName();
* super.getName();
* 这样可以:
* Thread.currentThread().getName();
*/
}
}
}
线程的sleep方法
- sleep(时间):指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
package se5.thread;
/*
关于线程的sleep方法
static void sleep(long millis)
1.静态方法
2.参数是毫秒
3.作用:让当前线程进入休眠,进入阻塞状态,放弃占有CPU时间片,让给其他线程使用
这行代码出现在A线程中,A线程就会进入休眠
这行代码出现在B线程中,B线程就会进入休眠
4.Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
*/
public class ThreadTest06 {
public static void main(String[] args) {
//让当前线程进入休眠,睡眠5秒钟
//当前线程是主线程!!!
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后执行这里的代码
//System.out.println("Hello World!");
for (int i = 1; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
面试题
package se5.thread;
/*
关于Thread.sleep方法的面试题
*/
public class ThreadTest07 {
public static void main(String[] args) {
//创建线程对象
Thread t = new MyThread3();
t.setName("t");
t.start();
//调用sleep方法
try {
//问题:这行代码会让线程t进入休眠状态吗?不会!!!
t.sleep(1000 * 5);//在执行的时候还是转换成Thread.sleep(1000 * 5);
//这行代码的作用:让当前线程进入休眠,也就是说main线程进入休眠
//这行代码出现在main方法中,main线程睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World!");
}
}
class MyThread3 extends Thread{
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+ "--->" +i);
}
}
}
案例:模拟倒计时
package com.thread2;
//模拟倒计时
public class TestSleep2 {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0){
break;
}//从10开始倒计时
}
}
}
案例:龟兔赛跑
注意:故事中乌龟是赢家,兔子需要睡觉,所有我们需要模拟兔子睡觉(兔子:MMP)
package com.thread;
//模拟龟兔赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子") && i % 10 ==0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
//如果比赛结束了,就停止程序
if (flag){
break;
}
System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps) {
if (winner != null) {
return true;
}
{
if (steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("Winnner is" + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
/**
* 本次运行结果:
* ...
* 乌龟--->跑了9步
* 兔子--->跑了8步
* 乌龟--->跑了10步
* 兔子--->跑了9步
* 乌龟--->跑了11步
* ...
*乌龟--->跑了98步
*乌龟--->跑了99步
* Winnner is乌龟
*/
}
}
静态代理
package com.thread2;
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.HappyMarry();
/**
* 输出:
* 结婚之前,布置现场
* 阿波要结婚了,超开心!
* 结婚之后,收尾款
*/
}
}
interface Marry{
void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("阿波要结婚了,超开心!");
}
}
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
//代理谁--->真实目标角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//这就是真实对象
after();
}
private void after() {
System.out.println("结婚之后,收尾款");
}
private void before() {
System.out.println("结婚之前,布置现场");
}
}
静态代理总结
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
好处:
-
代理对象可以做很多真实对象做不了的事情
-
真实对象专注做自己的事情
线程停止
终止线程的睡眠
package se5.thread;
/*
sleep睡眠太久了,如果希望半道上醒来,怎么办?如何叫醒一个正在睡眠的线程?
注意:这个不是终断线程的执行,是终止线程的睡眠
*/
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
//希望5秒之后,t线程醒来
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制)
t.interrupt();//干扰
/**
* 运行结果:
* t--->begin
* java.lang.InterruptedException: sleep interrupted
* at java.lang.Thread.sleep(Native Method)
* at se5.thread.MyRunnable2.run(ThreadTest08.java:29)
* at java.lang.Thread.run(Thread.java:748)
* t--->end
* 注意:若不想看的异常信息可以去掉下面的e.printStackTrace();
*/
}
}
class MyRunnable2 implements Runnable{
//重点:run方法当中的异常不能throws,只能try catch
//因为run方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->begin");
//睡眠1年
try {
Thread.sleep(1000 * 60 *24 * 365);
} catch (InterruptedException e) {
//打印异常信息,若不想看到异常信息,可以去掉
e.printStackTrace();
}
//1年之后才会执行这里
System.out.println(Thread.currentThread().getName() + "--->end");
}
}
强制终止线程的执行
package se5.thread;
/*
在java中如何强行终止一个线程
stop:这种方式有很大的缺点,容易丢失数据,因为这种方式直接将线程杀死了,线程没有保存的数据会丢失,不建议使用。
仅作为了解
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后,强行终止t线程
t.stop();//已过时,不建议使用!!!
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
合理的终止线程
package se5.thread;
/*
合理的终止一个线程,这种方式是很常用的
*/
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 r = new MyRunnable4();
Thread t = new Thread(r);
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//想什么时候终止t的执行,把标记修改为false,就结束了
r.run = false;
/*
...
t--->3
t--->4
t--->5
Process finished with exit code 0
五秒后线程停止~
*/
}
}
class MyRunnable4 implements Runnable{
//打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (run){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//return 就结束了,在结束之前还有什么没保存的,在这里可以保存
//save...
//终止当前线程
return;
}
}
}
}
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU心情
注意:在回到就绪之后,有可能会再次抢到
package com.thread2;
/*
测试礼让线程
*/
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
/*
礼让的结果: 未礼让的结果:
a线程开始执行 a线程开始执行
b线程开始执行 a线程停止执行
b线程停止执行 b线程开始执行
a线程停止执行 b线程停止执行
*/
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
合并线程
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
package se5.thread;
/*
线程合并
*/
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable7());
t.setName("t");
t.start();
//合并线程
try {
t.join();//t合并到当前线程,当前线程受阻塞,t线程执行直到结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
/**
* main begin
* t--->1
* t--->2
* t--->3
* ...
* t--->8
* t--->9
* t--->10
* 这里1-10执行完后main over才会继续执行
* main over
*/
}
}
class MyRunnable7 implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程调度概述(了解)
常见的线程调度模型有哪些?
-
抢占式调度模型:
哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些
java采用的就是抢占式调度模型
-
均分式调度模型:
平均分配CPU时间片,每个线程占用CPU时间片时间长度一样
平均分配,一切平等
线程调度的方法(了解)
实例方法:
- void setPriority(int newPriority) 设置线程的优先级
- int getPriority() 获取线程优先级
最低优先级1,默认优先级是5,最高优先级是10
package com.thread2;
//测试线程的优先级
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
System.out.println("最高优先级" + Thread.MAX_PRIORITY);//最高优先级10
System.out.println("最低优先级" + Thread.MIN_PRIORITY);//最低优先级1
System.out.println("默认优先级" + Thread.NORM_PRIORITY);//默认优先级5
System.out.println("#############################################");
//先设置优先级,再启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY = 10
t4.start();
t5.setPriority(8);
t5.start();
t6.setPriority(7);
t6.start();
/**
* main--->5
* 最高优先级10
* 最低优先级1
* 默认优先级5
* #############################################
* Thread-3--->10
* Thread-0--->5
* Thread-4--->8
* Thread-5--->7
* Thread-2--->4
* Thread-1--->1
*/
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
}
}
观测线程
线程状态:
- NEW:尚未启动的线程处于次状态
- RUNNABLE:在Java虚拟机中执行的线程处于次状态
- BLOCKED:被阻塞等待监听器锁定的线程处于此状态
- WAITING:正在等待另一个线程执行特定动作的线程处于次状态
- TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于次状态
- TERMINATED:已退出的线程处于此状态
package com.thread2;
//观察测试线程的状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("*****");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);//NEW
//观察启动后
thread.start();//启动线程
thread.getState();
System.out.println(state);//Run
while (state != Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState();//更新线程状态
System.out.println(state);//输出状态
}
/*
thread.start();
死亡之后的线程不能再次启动,会报错
*/
}
}
守护线程
java语言中线程分为两大类:
-
一类是用户线程
-
一类是守护线程(后台线程),其中具有代表性的就是垃圾回收线程
特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程。
package se5.thread;
//守护线程
public class ThreadTest14 {
public static void main(String[] args) {
Thread t = new Thread();
t.setName("备份数据的线程");
//启动线程之前将线程设置为守护线程
t.setDaemon(true);
//若没有设置守护线程,main线程执行完后,备份数据的线程会陷入死循环,一直执行!!!
t.start();
//主线程:主线程是用户线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
public void run(){
int i = 0;
//即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止
while (true){
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器
作用:间隔特定的时间,执行特定的程序。例如:每周要进行银行账户的总账操作,每天要进行数据的备份操作。在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的。
在java中其实可以采用多种方式实现:
- 可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
- java类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际开发中,目前使用较多的是Spring框架中提供的SpringTask框架。这个框架只要进行简单的配置,就可以完成定时器的任务。
package se5.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
//使用定时器指定定时任务
public class TimerTest {
public static void main(String[] args) throws Exception {
//创建定时器对象
Timer timer = new Timer();
//Timer timer = new Timer(true);守护线程的方式
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-11-24 15:58:30");
timer.schedule(new LogTimerTask(),firstTime,1000 * 10);//这里间隔10秒
/**
* 运行结果:
* 2020-11-24 15:58:53成功完成了一次数据备份!
* 2020-11-24 15:59:03成功完成了一次数据备份!
* 2020-11-24 15:59:13成功完成了一次数据备份!
* 2020-11-24 15:59:23成功完成了一次数据备份!
* 2020-11-24 15:59:33成功完成了一次数据备份!
*/
}
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
@Override
public void run() {
//这里编写需要执行的任务就行了
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + "成功完成了一次数据备份!");
}
}