线程常用操作方法
线程的主要方法都定义在 Thread类 之中,因此此处就是学习Thread中的方法。
一. 线程的命名与取得
- 构造方法:
public Thread(Runnable target, String name)
- 设置名字:
public final synchronized void setName(String name)
- 取得名字:
public final String getName()
范例1:观察线程的名字
class MyThread implements Runnable{
@Override
public void run() {
// MyThread 代表着 Runnable 子类,是一类资源,并不是线程,因此不能使用this.getName() 并且也没有这个方法
// 因此要使用 Thread 的类方法,获得当前线程
System.out.println(Thread.currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread(); // Runnable 子类对象
new Thread(mt, "线程A").start();
new Thread(mt).start();
new Thread(mt, "线程B").start();
}
}
结果:
线程A
线程B
Thread-0
总结: 说明开发者设置名字的线程返回名字,未设置名字的线程自动分配名字。主要是通过 static 属性来完成的,在 Thread 类中定义有如下操作
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
范例2:观察线程的名字
class MyThread implements Runnable{
@Override
public void run() {
// MyThread 代表着 Runnable 子类,是一类资源,并不是线程,因此不能使用this.getName() 并且也没有这个方法
// 因此要使用 Thread 的类方法,获得当前线程
System.out.println(Thread.currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception{
MyThread mt = new MyThread(); // Runnable 子类对象
new Thread(mt, "线程A").start();
mt.run(); // 对象直接调用 run() 方法
}
}
结果:
main
线程A
总结:
- 直接在主方法中调用线程类对象的
run()
方法,得到的名称是 “main”,即——主方法也是一个线程; - 在任何的开发之中,主线程可以创建若干个子线程,创建子线程的目的是可以将一些复杂逻辑或者比较耗时的逻辑交由子线程处理;
- 主线程负责处理整体流程,而子线程负责处理耗时操作。
例如,如下程序会在执行任务一之后,发生轻微卡顿。
public class ThreadDemo {
public static void main(String[] args){
System.out.println("1. 执行任务一");
int temp = 0;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
temp += i;
}
System.out.println("2. 执行任务二");
System.out.println("3. 执行任务三");
}
}
将任务一放在子线程中进行,运行情况明显改善
public class ThreadDemo {
public static void main(String[] args){
System.out.println("1. 执行任务一");
new Thread(()->{ // 子线程负责统计
int temp = 0;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
temp += i;
}
});
System.out.println("2. 执行任务二");
System.out.println("3. 执行任务三");
}
}
二. 线程休眠(sleep())
目的:希望某一个线程可以暂缓执行,就可以使用休眠处理。
方法:
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)throws InterruptedException;
范例:
public class ThreadDemo {
public static void main(String[] args){
new Thread(()->{ // 子线程负责统计
for (int i = 0; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() + temp);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();;
}
}
三. 线程中断(interrupt())
在之前发现线程的休眠里面提供有一个中断异常,实际上就证明线程的休眠是可以被打断的,而这种打断肯定是由其它线程完成的。
方法:
- 判断线程是否被中断
public boolean isInterrupted()
- 中断线程
public void interrupt()
范例:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException{
Thread th = new Thread(()->{
System.out.println("休眠中...");
try {
Thread.sleep(10000); // 子线程休眠10s
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("休眠结束!");
});
th.start();
Thread.sleep(1000); // 主线程休眠1s后中断子线程
if (!th.isInterrupted()) { // 判断 th 子线程是否被中断
th.interrupt(); // 中断 th 子线程
System.out.println("子线程被主线程所中断。。");
}
}
}
结果:
休眠中...
子线程被主线程所中断。。
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at ThreadDemo.lambda$0(ThreadDemo.java:7)
at java.base/java.lang.Thread.run(Thread.java:833)
休眠结束!
所有正在执行的线程都是可以被中断的,中断线程必须进行异常的处理。
四. 线程强制执行(join())
定义
强制执行:当满足于某些条件之后,某一个线程对象将可以一直独占资源,一直到该线程的程序执行结束。
正常范例
正常情况下,以主线程和子线程为例,两者都在交替执行着
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException{
Thread th = new Thread(() -> {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行,i = " + i);
}
}, "正常的子线程");
th.start();
for(int i = 0; i < 10; i++) {
Thread.sleep(100);
System.out.println("正常的主线程,执行,i = " + i);
}
}
}
结果:
正常的子线程执行,i = 0
正常的主线程,执行,i = 0
正常的子线程执行,i = 1
正常的主线程,执行,i = 1
正常的子线程执行,i = 2
正常的主线程,执行,i = 2
正常的主线程,执行,i = 3
正常的子线程执行,i = 3
独占范例
但是如果说现在你希望主线程独占执行。那么就可以利用 Thread 类中的方法:
public final void join() throws InterruptedException
需要注意的是:在进行线程强制执行的时候一定要获取强制执行线程对象之后才可以执行 join()调用。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException{
Thread th = new Thread(() -> {
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行,i = " + i);
}
}, "优先的子线程");
th.start();
for(int i = 0; i < 5; i++) {
Thread.sleep(100);
if(i == 1)
th.join(); // 主线程执行1次后,子线程独占资源,直到执行完毕
System.out.println("被迫等待的主线程,执行,i = " + i);
}
}
}
结果:
优先的子线程执行,i = 0
被迫等待的主线程,执行,i = 0
优先的子线程执行,i = 1
优先的子线程执行,i = 2
优先的子线程执行,i = 3
优先的子线程执行,i = 4
被迫等待的主线程,执行,i = 1
被迫等待的主线程,执行,i = 2
被迫等待的主线程,执行,i = 3
被迫等待的主线程,执行,i = 4
五. 线程的礼让(yield())
线程的礼让指的是先将资源让出去让别的线程先执行。线程的礼让可以使用 Thread 中提供的方法;
public static native void yield(); // 这是一个静态方法
但与强制执行不同,礼让执行的时候每一次调用yield()
方法都只会礼让一次当前的资源。
范例:在之前 join()
的基础上,让强占资源的子线程通过 yield()
让出线程
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException{
Thread th = new Thread(() -> {
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行,i = " + i);
// 当执行到第二次时进行一次礼让
if(i > 1){
Thread.yield();
System.out.println("------礼让执行------");
}
}
}, "优先的子线程");
th.start();
for(int i = 0; i < 5; i++) {
Thread.sleep(100);
if(i == 1)
th.join(); // 主线程执行1次后,子线程独占资源,直到执行完毕
System.out.println("被迫等待的主线程,执行,i = " + i);
}
}
}
结果:
被迫等待的主线程,执行,i = 0
优先的子线程执行,i = 0
优先的子线程执行,i = 1
优先的子线程执行,i = 2
------礼让执行------
优先的子线程执行,i = 3
------礼让执行------
优先的子线程执行,i = 4
------礼让执行------
被迫等待的主线程,执行,i = 1
被迫等待的主线程,执行,i = 2
被迫等待的主线程,执行,i = 3
被迫等待的主线程,执行,i = 4
思考(关于 join 和 yield)
通过观察结果我们发现,并没有实现预期的效果:
子线程礼让,被迫等待main线程执行。
通过查阅资料后,我们发现了 join()
和 yield()
方法对应于操作系统的细节:
我们知道操作系统对于进程和线程的调度,有五大状态之分:
而对于上述的两个方法而言:
yield()
是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。操作系统会从就绪态中的线程重新调度(当然运气好的话,你刚让出来又被调进去,就会出现礼让无效果的情况)join()
方法在线程实例中被调用,当前运行的线程会被堵塞,当前线程变为堵塞态,直到线程实例运行完成。
因此,对于被子线程的join()
所堵塞的main线程,其本身的状态就不在就绪态之中,所以即使子线程使用yield()
礼让,操作系统也会从就绪态的线程中选择调用,而不会运行处于堵塞态的main线程。
六. 线程优先级(setPriority())
从理论上来讲线程的优先级越高越有可能先执行(越有可能先抢占到资源)。在 Thread 类里面针对于优先级的操作提供有如下的两个处理方法:
- 设置优先级:
public final void setPriority(int new Priority)
- 获取优先级:
public final int getPriority()
在进行优先级定义的时候都是通过 int 型的数字来完成的,而对于此数字的选择在 Thread 类里面就定义有三个常量: - 最高优先级:
public static final int MAX PRIORITY;
——10 - 中等优先级:
public static final int NORMPRIORITY;
——5 - 最低优先级:
public static final int MIN PRIORITY;
——1