继承java.lang.Thread类
public class FirstThread extends Thread{
private int i ;
@Override
public void run() { // 真正去运行线程,执行任务
for (i = 0; i < 50; i++) {
System.out.println(getName() + " 运行 : " + i);
}
}
public static void main(String[] args) {
//创建第一个线程
new FirstThread().start(); // 就绪
//创建第二个线程
new FirstThread().start(); // 就绪,何时开始
}
}
- 上述程序有3个线程,main方法作为程序运行默认的主线程,还有main方法体中的创建的2个线程Thread-0 和 Thread-1。
- Thread-0 和 Thread-1是乱序执行的,且不共享实例变量i。也即是 当使用继承Thread类来创建多
线程时,多个线程之间无法共享线程类的实例对象。 - Thread类的run()方法不执行任何操作并返回,因此继承Thread类一定要重写run(),否则没有线程
执行体。
实现java.lang.Runnable接口
public class SecondThread implements Runnable{
private int i ;
@Override
public void run() {
for (i = 0; i < 50; i++) {
System.out.println(Thread.currentThread() + " 运行 : " + i);
}
}
public static void main(String[] args) {
SecondThread st = new SecondThread();
//通过new Thread(target, name)的方式创建线程
//以Runnable接口的实现类作为Thread的target来创建Thread线程对象
new Thread(st, "Thread-0").start();
new Thread(st, "Thread-1").start();
}
}
- 必须重写Runnable接口中的run()方法,以此来作为线程执行体。
- 实际的线程对象仍然是Thread实例(即new 出来的Thread对象,只是该Thread对象负责执行其
target中的run方法。 - Runnable接口下创建的多个线程可以共享线程类的实例变量(因为打印结果中i是连续的),主要
是因为多个线程共享一个target即上述代码中的st变量对应的SecondThread实例。 - 输出结果中,Thread[Thread-0,5,main] ,5为线程的默认优先级。线程优先级值范围1-10,其中1
为优先级最高。
推荐使用Runnable接口的方式
- 多个线程可以共享一个 target ,适合多个相同线程来处理同一份资源
- 线程类还可以继承其他类
线程的生命周期
线程有五种状态
新建状态(New)
:新创建了一个线程对象。就绪状态(Runnable)
:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程
位于可运行线程池中,变得可运行,等待获取CPU的使用权。注意: 启动线程用start()方法,不要直接
调用run()方法,否则JVM会把run()方法当作普通方法来执行,而不是多线程的线程执行体。运行状态(Running)
:就绪状态的线程获取了CPU,执行程序代码。阻塞状态(Blocked)
:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程
进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
– 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
– 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
– 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。死亡状态(Dead)
:run()或call()方法执行结束或者因异常退出了run()方法,该线程结束生命周期。可以调用线程对象的isAlive()方法查看线程状态,线程处于new和dead状态时,该方法返回false。另外,不要对一个已经死亡的线程调用start()让它重新启动。
join线程
Thread 提供的join方法让一个线程等待另一个线程完成。当A线程调用B线程的join()方法,A线程将被阻塞,知道B线程执行完成后,再继续执行A线程。
public class JoinThread extends Thread{
public void run(){
// do something
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinThread joinThread = new JoinThread();
for (int i = 0; i < 100; i++){
System.out.println("我是主线程:" + i);
if (i == 20){
var jt = new Thread(joinThread);
jt.start();
jt.join();
}
}
}
}
就像插队一样
运行结果:
我是主线程:0
...
...
我是主线程:19
我是主线程:20
Thread[Thread-1,5,main] 0
Thread[Thread-1,5,main] 1
Thread[Thread-1,5,main] 2
Thread[Thread-1,5,main] 3
Thread[Thread-1,5,main] 4
我是主线程:21
我是主线程:22
...
...
我是主线程:99
sleep线程
让当前执行的线程暂停一段时间,进入阻塞状态,而非就绪状态。
public class SleepThread {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println("系统当前时间" + new Date());
Thread.sleep(1000);
}
}
}
打印结果:
系统当前时间Fri May 28 20:04:34 CST 2021
系统当前时间Fri May 28 20:04:35 CST 2021
系统当前时间Fri May 28 20:04:36 CST 2021
系统当前时间Fri May 28 20:04:37 CST 2021
系统当前时间Fri May 28 20:04:38 CST 2021
系统当前时间Fri May 28 20:04:39 CST 2021
系统当前时间Fri May 28 20:04:40 CST 2021
系统当前时间Fri May 28 20:04:41 CST 2021
系统当前时间Fri May 28 20:04:42 CST 2021
系统当前时间Fri May 28 20:04:43 CST 2021
yield 线程
让当前执行的线程暂停,进入就绪状态,而非阻塞状态。(和sleep()线程不同)
调用A线程的yield()方法后,A让出CPU的使用权,进入就绪状态和其他线程一起参与下一次的资源调度竞争。处于就绪状态的多个线程中,谁的优先级最高会先获得执行的机会。
和sleep()方法不同的,yield()只会给同级优先级或更高优先级的线程执行机会,而sleep()暂停当前线程后会给其他线程执行,不区分优先级。
后台线程
后台线程(Daemon Thread)又称为守护线程,在后台运行并为其他线程提供服务,比如JVM的GC线程。
当所有前台线程都死亡之后,后台线程会自动死亡。
后台线程设置:
对象.setDaemon(true);
sleep和wait的异同
相同点:
- sleep()通过传入“睡眠时间”作为方法的参数,时间一到就从“睡眠”中“醒来”,继续执行程序;wait()称为线程等待,传入等待时间,等待时间结束,就会继续执行。
不同点:
- wait不传入等待时间便会进入无期限的等待,只能用notify()方法唤醒;
- sleep()释放CPU执行权,但不释放同步锁;wait()释放CPU执行权也释放同步锁,使得其他线程可以使用同步控制块或者方法。