1. 创建线程
1.1 继承Thread类
将一个类声明为 Thread 的子类,这个子类重写 Thread 类的方法 run() 。然后可以分配并启动子类的实例。使用这个方法可以用 this 来表示当前线程。
重写代码如下:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("in mythread");
}
}
调用时,只需要 new 一个新的对象,并且使用 start() 方法开始执行线程。
代码如下:
MyThread t1 = new MyThread();
t1.start();
1.2 实现Runnable接口
声明实现类 Runnable 接口 ,然后这个类重写了 run 方法。再分配类的实例对象。使用构造器的方法更利于封装,统一代码。
实现接口代码如下:
class MyRunner implements Runnable {
@Override
public void run() {
System.out.println("in myrunner");
}
}
实现Runnable接口之后,再传入Thread类 的构造方法作为构造参数,并使用Thread对象来开启线程。
代码如下:
MyRunner runner = new MyRunner();
Thread t2 = new Thread(runner);
t2.start();
1.3 匿名类
匿名类能够为我们提供更加便捷的重写 Runnable 接口的方法,如果开启线程比较少的的话就不需要我们再去另外写一个类去实现 Runnable 接口,能够为我们减少一部分代码量。
代码如下:
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名类创建 Runnable 子类对象");
}
});
2. Thread类与方法
Thread 类是 JVM 用来管理线程的一个类,也就是说,每一个线程都对应着唯一一个 Thread 类的对象。每一个线程执行流,都需要一个对象来描述,Thread类就是用来描述线程执行流的,JVM 会把这些 Thread 对象组织起来进行统一管理和调度。
2.1 常见属性和获取方法
getId() : ID 是线程的唯一标识,不同线程不会重复 。
getName() : 名称是在调试时可以使用到。
getState() : 状态表示线程当前所处的一个情况,下面我们会进一步说明
getPriority():优先级高的线程理论上来说更容易被调度到。
isDaemon() : 关于后台线程,JVM会在一个进程的所有非后台线程结束后,才会结束运行。
isAlive():是否存活,run() 方法是否运行结束。
2.2 start()和run()方法的区别
start 调用了 native 本地方法,启动了一个线程,启动的子线程和javamain主线程同时执行。
run 方法(普通方法),是在 main 线程中使用这个方法,只是执行任务的部分代码,实际上是不会启动线程的。
2.3 main 和 start()顺序
通常情况下,我们都是在main主线程内来启动线程的,当我们创建一个线程时,需要申请 CPU 的调度,这是有一定的时间消耗的。这就会导致有时候 main 主线程的代码会比我们新建线程的代码之前执行。
2.4 中断线程
interrupt() 方法
表示中断线程的动作。修改标志位,并且可以中断wait()/join()/sleep()的阻塞线程,并抛出异常 InterruptedException。
interrupted() 方法
获取标志位并返回,再重置标志位为false ,也就是清除中断标志。
该方法实现步骤为:
boolean temp = 标志位;
标志位=false;
return temp;
isInterrupted
获取当前真正标志位,不会清除中断标志
阻塞状态下的中断操作
调用sleep()/wait()/join()方法时,线程会进入阻塞状态,此时可以中断。中断后会抛出 InterruptedException 异常,但捕获异常以后,标志位会被重置(清除中断标志)。
代码示例
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
Thread.currentThread().interrupt();
System.out.println("interrupt");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.interrupted());
}
}
});
thread.start();
这段代码运行结果可由上述三个方法的特点来推断出来。
重写的 run() 方法内,第一个循环是打印当前线程真实的中断的标志位,由于此时线程没有被中断,所以连续打印五个false。
在当前线程调用了 interrupt() 方法之后,打印一个 “interrupt” ,在执行这行代码以后,线程的中断标志位被修改为 true ,所以在第二个 for 循环中,首先打印了一个 true 。但由于 interrupted() 方法在返回当前标志位以后会将其重置为 false。所以在打印了一个 true 以后,连着打印了四次 false 。
2.5 yield()方法
yield()
yield方法会将线程的运行状态转换成就绪状态,当前线程调用yield方法后,这个线程就会无私地让出CPU来给其他线程使用,直到其他线程执行完毕之后,才会继续执行调用yield方法的线程。
代码示例
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " 正在执行");
Thread.yield();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " 正在执行");
}
}
});
t1.start();
t2.start();
这段代码我们启动了两个线程 t1 和 t2 ,但由于第一个线程 t1 内部调用了yield方法,所以他会先让出CPU,此时CPU就会去执行 t2 ,t1 就会等待 t2 结束之后才会执行。因为 t2 中是一个无限循环,所以这段代码会一直死循环打印第二个线程正在执行,而不会执行第一个线程。
2.6 等待一个线程:join()方法
join()
让当前代码行的线程阻塞,等待调用该方法的线程执行完毕再往下执行。
join(long millis)
当前代码行等待给定时间或者调用join方法的线程结束再执行下面的线程。取两者最小时间。
代码示例
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("t1 正在执行");
}
}
});
t1.start();
t1.join();
System.out.println("main");
}
在文章前面讲过,由于创建线程需要消耗一定量的时间,所以正常情况下这段代码会首先打印 “main”,但由于 t1 线程调用了 join() 方法,此时当前代码行表示的就是 main 主线程,所以这段代码会先打印十次 “t1 正在执行”,最后打印 “main” ,程序结束。可以把 t1.join() 这行代码去掉来观察程序运行会有什么变化。
|
|