本片文章主要挑了多线程中的一些常用的重要的属性进行了一个讲解,包括一些获取属性的方法,线程的状态都有哪些,这些都应该是我们程序员提笔就知道知识,请完全消化!!!
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
设置后台线程 | setDaemon() |
获取线程当前引用 | currentThread() |
是否存活 | isAlive() |
线程是否中断 | Interrupted() |
获取线程状态 | getState() |
线程等待 | join() |
休眠 | sleep() |
1.ID是线程的唯一标识,一个进程中的每个线程都有一个唯一的ID,但是,这个ID不是真正的ID,这个ID是Java自己分配的,不是系统API分配的
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("hello Thread");
});
thread.start();
System.out.println("hello main");
//获得thread的id
System.out.println(thread.getId());
}
2.getName() 在创建线程时,给线程指定名称后,使用getName( )获取,在后期进行调试时方便观察
public static void main(String[] args) {
//在创建thread对象时,指定线程名称
Thread thread = new Thread(() -> {
System.out.println("hello Thread");
}, "线程1");
thread.start();
System.out.println("hello main");
//获取线程名称
System.out.println(thread.getName());
}
方法 | 说明 |
---|---|
public static Thread currentThread() | 返回当前线程对象的引用 |
方法 | 说明 |
---|---|
public static void sleep(long millis) | 休眠当前线程 millis 毫秒 |
这里的睡眠时间的精度是有误差的,比如以下代码:
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println(end-start);
}
通过代码可以看到,想要休眠1000 毫秒,却休眠了1005秒,因为:系统会按照1000这个时间来控制线程的睡眠,等达到1秒后,就会唤醒线程,就会使线程从阻塞状态转化为就绪状态,但是呢,当线程转换为就绪状态后,并不是说就会直接去CPU上执行,这中间会有一个“调度开销”;
3.设置后台线程
先解释以下什么是前台线程:当我们正常创建一个线程时,它是一个前台线程,而只要前台线程不结束,整个Java进程都不会结束
后台线程:进程否结束,与后台线程结束都没有关系,就算后台线程未结束,进程也可以结束
public static void main(String[] args) throws InterruptedException {
//在创建thread对象时,指定线程名称
Thread thread = new Thread(() -> {
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "线程1");
//设置线程为后台线程
thread.setDaemon(true);
thread.start();
}
可以看到线程中的循环语句只打印了一次,同时main方法也执行完了,整个进程就结束了,这一点可以打开java的JDK中的bin文件中的jconsole观察线程状态补充,所以证明了,不管后台线程有没有执行完,都不会影响整个进程是否为结束;
4.是否存活
当使用Thread实例化对象后,在线程执行完之后,内核中的线程就会被销毁,也可以简单理解为run方法是否运行结束了
public static void main(String[] args) throws InterruptedException {
//在创建thread对象时,指定线程名称
Thread thread = new Thread(() -> {
System.out.println("hello Thread");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
thread.start();
System.out.println(thread.isAlive());
//这里休眠3秒,等待新线程执行完
Thread.sleep(3000);
System.out.println("线程结束");
System.out.println(thread.isAlive());
}
需要知道的一点是,true 和 hello Thread 不一定那个先打印,因为,线程是并发执行的,调度顺序不确定,不一定是先执行主线程还是先执行新线程,这取决于操作系统的调度器;
在Java中,当线程正在运行时,我们想要让线程立即终止,就可以使用以下两种措施:
中断线程两种方法:
示例1.设置标志位
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(!flag) {
System.out.println("线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程工作结束");
});
thread.start();
Thread.sleep(5000);
//当flag设为true时,线程就会结束
flag = true;
System.out.println("设置flag=true");
}
注意点:如果把标志位flag放到main方法中,就会出现错误;因为这就涉及到了Lambda中的变量捕获的一些问题,如果您不懂的话,可以去看看这篇文章
实例2:使用Thread内部的标志位
上述自己设置标志位有一点不太好,那就是如果当发出中断信号时,线程正在睡眠时(sleep() 正在执行时),这时候,它不会立即中断,而是会等到睡眠结束后,再次回到while语句判断时,才会中断,换句话讲就是,当线程处于阻塞状态时,不会立即中断;如果使用Thread内部提供的标志位,就不会出现这种情况,而在Thread的内部,提供了一个boolean类型的变量作为线程是否被中断的标记
方法 | 解释 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupt() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
//currentThread() 获取当前线程对象也就是thread
while(!Thread.currentThread().isInterrupted()) {
//Thread.Interrupted
//如果标志位被设置了就返回true,否则返回false
System.out.println("线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(5000);
System.out.println("设置标志位");
//设置标志位
thread.interrupt();
}
可以看到,抛出异常之后,线程还在执行,并没有终止退出,这是为什么呢?
因为调用interrupt() 方法后,线程收到通知方式有两种:
1.当调用interrupt时,如果线程因为sleep/join/wait等方法正在阻塞,则会以InterruptExection的异常方式通知,并且清除中断标志
以异常方式通知以后,线程是否要结束,取决于catch里面的代码,如果需要结束线程就可以直接break;
在上述代码中,因为这是通过sleep()来抛出的异常,当Thread发出中断信号时,正在处于休眠的线程就会被强制唤醒,唤醒之后,sleep() 就会抛出异常,同时会再将标志位复原,导致线程并没有终止,而是继续执行;
而这种根据异常来终止线程的方式也给程序员提供了一些**“可操作空间”**,举个例子:
比如你现在正在打游戏,但是呢,你的老妈让你去买瓶酱油,这时候,你就会有三种选择:
1、你马上退出游戏,飞奔去买酱油
2、先把这局游戏打完,然后再去买
3、直接忽略掉,就是不去(这样你可能就会被打屁屁了)
2.如果没有阻塞,则会设置标志位
- Thread.interrupted 判断标志位是否被设置,如果被设置,则返回true,并且清除中断标志
- Thread.currentThread().isInterrupted 判断标志位是否被设置,如果被设置,则返回true,并且清除中断标志
对于上述代码,抛出异常之后,我们可以使用break()来立即的终止线程,或者是在break()之前先执行一段代码逻辑再中断 或者是 直接忽略掉
while(!Thread.currentThread().isInterrupted()) {
System.out.println("线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//1.直接break
//break()
//2.执行一些代码逻辑再break
//3.忽略掉
}
}
而以上这种具有可操作空间的线程终止必须与异常互相结合
在多线程调度执行时,是没有顺序的,都是随机调度执行的,这就会带来很多麻烦,所以Java中就使用join()方法,使线程之间有一个执行的顺序:
线程等待:一个线程正在执行时,另一个线程处于等待状况,等到第一个线程执行完之后,第二个线程才开始执行
举个例子:现在有两个多线程,一个名为张三,一个名为李四,先让张三去工作10次,然后再让李四去工作10次,按照这样的顺序执行;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
for(int i = 0; i<3; i++) {
System.out.println(Thread.currentThread().getName()+"正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"工作结束");
};
Thread thread1 = new Thread(runnable, "张三");
Thread thread2 = new Thread(runnable, "李四");
thread1.start();
//一旦调用join(),主线程和thread2线程就会触发阻塞
//让主线程和thread2线程等待thread1线程执行结束,这是后thread1线程就可以趁机工作
//一直阻塞到thread1线程代码执行完,join()才会解除阻塞
thread1.join();
thread2.start();
thread2.join();
}
等待顺序:
哪个对象调用join(), 那个线程就被等待,从上述代码中可以看到,thread1调用了join(), thread2就会等待thread1执行完之后再执行而这个等待,是一种“死等”, 就是,只要是一个线程不结束,另一个线程就会一直等,为了解决这种“死等”的方式,也提供了一种带参数的join();
方法 | 解释 |
---|---|
public void join() | 没有时间限制,等待线程结束 |
publid void join(long millis) | 等待线程结束,有一个等待时间的限制 |
NEW:线程对象创建后,start方法没有调用
TERMINATED: 线程对象还在,内核中的线程已经没有了
RUNNABLE:就绪状态(线程已经在CPU上执行了,或者线程正在排队等待执行)
TIMED_WAITING: 阻塞状态,由于sleep这种固定时间方式产生的阻塞
WAITING:阻塞状态,由于wait这种不是固定时间方式产生的阻塞
BLOCKED:阻塞状态,由于锁竞争产生的阻塞
代码演示:
1.NEW:Thread对象创建后,start方法没有调用
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程正在工作");
});
//此时,线程已经定义好了,但是并没有调用start,所以是new
System.out.println(thread.getState());
}
2.TERMINATED: Thread对象还在,内核中的线程已经没有了
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程正在工作");
});
thread.start();
thread.join();
//此时,线程已经执行结束,内核线程已经销毁,但是,thread对象还存在,状态就是TERMINATED
System.out.println(thread.getState());
}
3.RUNNABLDE:就绪状态(线程已经在CPU上执行了,或者线程正在排队等待执行)
当while一直在执行时,此时线程就是就绪状态
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true) {
}
});
thread.start();
System.out.println(thread.getState());
}
4.TIMED_WAITING: 阻塞状态,由于sleep这种固定时间方式产生的阻塞
当thread线程执行时,就进入了睡眠状态,此时,在主线程中查看thread的状态就是阻塞状态,只有第一次时就绪状态;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
for(int i = 0; i < 3; i++) {
System.out.println(thread.getState());
}
}
5.WAITING:阻塞状态,由于wait这种不是固定时间方式产生的阻塞,关于wait可以看这篇文章
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("hello");
try {
//调用wait,产生阻塞
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
//休眠一秒,让t1线程先执行
Thread.sleep(1000);
System.out.println(t1.getState());
}
6.BLOCKED:阻塞状态,由于锁竞争产生的阻塞,关于锁竞争可以看这篇文章
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker) {
System.out.println("hello");
//让t1休眠5秒钟再释放锁
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("world");
}
});
t1.start();
t2.start();
//让主线程休眠一秒钟再执行,此时t1先执行,t2处于阻塞
Thread.sleep(1000);
//一秒钟之后,得到t2的状态,就是由于锁竞争而导致的阻塞状态
System.out.println(t2.getState());
}
}