一、线程的创建和使用
1. 方式一:继承Thread类创建多线程
1.1 创建步骤
- 创建一个继承于Thread类的子类
- 重写Thread类的run()方法,将此线程要完成的操作写在run()方法中
- 创建Thread类的子类对象
通过此对象调用start()方法
public class Test extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(i + " 子线程");}}}public static void main(String[] args) {Test p = new Test();//创建Thread子类对象p.start();//启动子线程,此时main方法主线程和子线程并发执行for (int i = 0; i < 100; i++) {if(i % 2 != 0){System.out.println(i + " main方法");}}}}
1.2 Thread类的特性
JVM允许程序运行多个线程,通过java.lang.Thread类体现,所有的线程对象都必须是Thread类或其子类的实例。
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,run()方法的方法体就代表了线程需要完成的任务,常把run()方法称为线程执行体。
- 通过该Thread对象的start()方法来启动线程,而不是直接调用run()方法。
- start()方法有两个作用:1.启动当前线程。2.调用当前线程的run()方法。
main()方法是Java程序运行时默认的主线程,main()方法的方法体就是主线程的线程执行体。
1.3 创建线程的两个问题
直接调用run()方法不会创建一个新的线程,只是相当于调用了一个普通方法,想要创建一个新线程来调用这个方法,只能使用start()。
- 如果想要再创建一个子线程,已经调用start()的对象不能再次调用start()方法,会报IllegalThreadStateException异常。一个对象只能使用一次start()方法,要想再创建一个线程,只能再创建一个对象。
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
public class Test extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(i + " " + getName());}}}public static void main(String[] args) {Test p = new Test();//创建Thread子类对象p.start();//启动子线程,此时main方法主线程和子线程并发执行//p.start();//IllegalThreadStateException异常Test p1 = new Test();p1.start();//重新创建一个线程只能再创建一个对象for (int i = 0; i < 100; i++) {if(i % 2 != 0){System.out.println(i + " " + Thread.currentThread().getName());}}}}
上面程序使用到了如下两个方法:
Thread.currentThread()是Thread类的静态方法,该方法总是返回当前正在执行的线程对象。
- getName():该方法是Thread的实例方法,该方法返回调用该方法的线程名字。
程序可以通过setName(String name)方法为线程设置名字,在默认情况下,主线程的名字为main,用户启动的多个线程的名字依次为Thread-0、Thread-1、Thread-2等。
1.4 继承Thread类创建多线程练习
练习:创建两个分线程,一个遍历100以内的偶数,另一个遍历100以内的奇数。
//方式一:分别创建两个不同的Thread子类。public class Test {public static void main(String[] args) {new Thread1().start();new Thread2().start();}}class Thread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + " " + i);}}}}class Thread2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + " " + i);}}}}
//方式二:使用匿名内部类简化,在实际开发中,如果一个线程只需使用一次,//则用这种方法,把上面四个步骤合并在一起。public class Test {public static void main(String[] args) {new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + " " + i);}}}}.start();new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 != 0){System.out.println(Thread.currentThread().getName() + " " + i);}}}}.start();}}
1.5 Thread类的常用方法
| void start() | 启动线程,并执行对象的run()方法 | | —- | —- | | run() | 通常需要重写Thread类的此方法,将线程在被调度时要执行的操作声明在此方法中 | | String getName() | 返回线程名称 | | void setName(String name) | 设置线程名称 | | static Thread currentThread() | Thread的静态方法,返回当前线程。在Thread子类中就是this,通常用于主线程和Runable实现类 | | static void yield() | 线程让步:暂停当前正在执行的线程,把执行机会释放给优先级相同或更高的线程,如果队列中没有同优先级的进程则忽略此方法。(机会释放后不是说一定会执行其他线程,可能cpu在调度时又分配给这个线程执行) | | join() | 在当前线程中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完毕为止。这样,优先级低的线程也能先执行,并且抛出InterruptedException异常。 | | static void sleep(long millis) | 令当前线程在指定时间内放弃对cpu的控制,使其他线程有机会被执行,时间到后重新排队,并且抛出InterruptedException异常。 | | stop() | 强制线程生命周期结束,不推荐使用 | | boolean isAlive() | 判断线程是否还活着 |
public class Test {public static void main(String[] args) {MyThread p = new MyThread("线程一");//p.setName("线程一");p.start();//给主线程命名Thread.currentThread().setName("主线程");for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);//i为3后,插入子线程,直到子线程执行完if(i == 3){try {p.join();} catch (InterruptedException e) {e.printStackTrace();}}}//判断子线程是否还存活,因为上面使用join加入子线程,此时子线程已经结束System.out.println(p.isAlive());}}class MyThread extends Thread{//使用构造器设置线程名public MyThread(String name){super(name);}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {sleep(1000);//阻塞一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":" + i);//当i大于1时,释放当前线程的cpu执行权if(i > 1){this.yield();//this可省略}}}}
2.方式二:实现Runable接口创建多线程
2.1 创建步骤
- 创建一个实现了Runable接口的类
- 实现类去实现Runable接口中的抽象run()方法
- 创建Runable实现类的的对象
- 将此对象作为参数传入到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()方法启动该线程
class MyThread implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(i);}}}public class Test {public static void main(String[] args) {MyThread myThread = new MyThread();Thread p = new Thread(myThread);//调用Thread类的含Runable对象的构造器p.setName("线程一");p.start();//再启动一个线程,可以在创建Thread对象时为该对象指定一个名字Thread p1 = new Thread(myThread,"线程二");p1.start();}}
2.2 两种方式的比较
Runable接口中只包含一个run()抽象方法。
- 当线程类实现Runable接口时,如果想获取当前线程,只能使用Thread.currentThread()方法;而继承方式中获得当前线程对象直接使用this即可。
- 采用Runable接口的方式创建的多个线程可以共享实现类的实例变量,即上面的线程一和线程二共享同一个Runable对象,Runable对象里定义的实例变量可以被这两个变量共享。
- 开发中,优先选择第二种方式,原因:
- 1)实现的方式没有单继承的局限性(如果子类又要用多线程,又要继承其他父类,但因为java单继承的限制,无法达到这一目的);
- 2)实现的方式更适合处理多个线程共享数据的情况。
相同点:两种方式都要重写run()方法。
class MyThread implements Runnable{private int i;//该变量可以被多个线程共享@Overridepublic void run() {for ( ; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}}public class Test {public static void main(String[] args) {MyThread myThread = new MyThread();Thread p = new Thread(myThread);p.setName("线程一");p.start();//再启动一个线程,可以在创建Thread对象时为该对象指定一个名字Thread p1 = new Thread(myThread,"线程二");p1.start();}}
3.方式三:使用Callable和Future创建线程
3.1 Callable接口简介
Callable接口是Runable接口的增强版,其提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更加强大。
call()方法可以有返回值。
- call()方法可以声明抛出异常,被外面的操作捕获,获取异常信息;而run()方法只能使用try…catch…,因为Thread类中的run()方法没有抛出异常,继承Thread类的子类也不能抛出异常。
-
3.2 创建步骤
创建并启动有返回值的线程的步骤如下:
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且有返回值,再创建Callable实现类的实例。
- 将此Callable实现类对象作为参数传递到FutureTask的构造器中,创建FutureTask对象。
- 使用FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,即Callable中call()方法的返回值。
Future接口:
- 可以对具体的Runable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutureTask类是Future接口的唯一实现类。
FutureTask接口同时实现了Runable、Future接口,它既可以作为Runable被线程执行,又可以作为Future得到Callable的返回值。
class MyThread implements Callable {@Override//重写call()方法,该方法有返回值,且是线程执行体public Object call() throws Exception {int sum = 0;for (int i = 0; i < 10; i++) {sum += i;System.out.println(i);}return sum;}}public class Test {public static void main(String[] args) {MyThread myThread = new MyThread();//创建Callable对象FutureTask futureTask = new FutureTask(myThread);//将Callable对象传入到FutureTask构造器中new Thread(futureTask).start();//把FutureTask对象作为参数传入到Thread构造器中,启动子线程try {//get()方法的返回值即为Callable实现类中重写的call()方法的返回值Object sum = futureTask.get();//该方法会抛出异常需要处理System.out.println("总和为" + sum);//打印输出该返回值} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
二、线程的生命周期
1.线程的5种状态
在线程的生命周期中,它要经过新建(New)、就绪(Ready)、运行(Runtime)、阻塞(Blocked)和死亡(Dead)5种状态。
新建:当程序中使用new关键字创建一个Thread类或其子类对象时,该线程就处于新建状态。
- 就绪:处于新建状态的线程调用start()方法后,将进入等待队列等待cpu执行,此时已具备运行条件,但该线程并未真正执行,还要等待分配cpu。所以,不要觉得调用start()方法后,线程就马上开始执行了。
- 运行:当就绪的线程被调度并获得cpu时,线程进入运行状态。
- 阻塞:在某些特殊情况下,线程在运行过程中被中断,放弃cpu并停止执行,线程进入阻塞状态。
死亡:线程完成了它的全部工作或被强制中止或异常结束,线程死亡。
2.线程阻塞和死亡的原因
2.1 线程阻塞
线程调用sleep()方法主动放弃所占用的处理器。
- 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
- 线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有。
- 线程等待某个通知(notify)。
- 程序调用了线程的suspend()方法将该线程挂起,但这个方法容易导致死锁,尽量避免使用此方法。
被阻塞的线程在合适的时候会重新进入就绪状态等待cpu调度。如:
- 调用sleep()方法的线程经过了指定的时间。
- 线程调用的阻塞式IO方法已经返回。
- 线程成功地获得了试图取得的同步监视器。
- 线程正在等待的通知发出。
-
2.2 线程死亡
run()方法或call()方法执行完成,线程正常结束。
- 线程抛出一个未捕获的Exception或Error。
- 直接调用该线程的stop()方法结束该线程,该方法容易导致死锁,不推荐使用。
为了测试某个线程是否死亡,可以调用线程对象的isAlive()方法,当线程处于就绪、运行、阻塞三种状态时,返回true;当线程处于新建、死亡两种状态时,返回false。

