Thread类的常用结构
构造器
- public Thread():分配一个新的线程对象。
- public Thread(String name):分配一个指定名字的新的线程对象。
- public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run()方法。
- public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
常用方法系列1
- public void run():此线程要执行的任务在此处定义代码。
- public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。
- public String getName():获取当前线程名称。
- public void setName():设置该线程名称。
- public static Thread currentThread():返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类。
- public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static void yield(): yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
常用方法系列2
-
public final boolean isAlive(): 测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
-
void join(): 等待该线程终止。
void join(long millis): 等待该线程终止的时间最长为millis毫秒。如果millis时间到,将不再等待。
void join(long millis,int nanos): 等待该线程终止的时间最长为millis毫秒 + nanos 纳秒。
-
public final void stop():
已过时
,不建议使用。强行结束一个线程的执行,直接进入死亡状态。run()即刻停止,可能会导致一些请理性的工作得不到完成,如文件,数据库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。 -
void suspend() / void resume(): 这两个操作就好比播放器的暂停和恢复。二者必须成对出现,否则非常容易发生死锁。suspend()调用会导致线程暂停,但不会释放任何锁资源,导致其他线程都无法访问被它占用的锁,直到调用resume()。
已过时
,不建议使用。
常用的方法系列3
每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。
- Thread类的三个优先级常量:
- MAX_PRIORITY(10):最高优先级
- MIN_PRIORITY(1): 最低优先级
- NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级。
- public final int getPriority(): 返回线程优先级
- public final void setPriority(int newPriority): 改变线程的优先级,范围在[1——10]之间。
练习:获取main线程对象的名称和优先级。
声明一个匿名内部类继承Thread类,重写run方法,在run方法中获取线程名称和优先级。设置该线程优先级为最高优先级并启动该线程。
一、线程的常用结构
1. 线程中的构造器
- public Thread():分配一个新的线程对象。
- public Thread(String name):分配一个指定名字的新的线程对象。
- public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run()方法。
- public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
2.线程中的常用方法:
> start(): (1)启动线程 (2)调用线程的run()
> run(): 将线程要执行的操作,声明在run()中。
> currentThread(): 获取当前执行代码对应的线程。
> getName(): 获取线程名
> setName(): 设置线程名
> sleep(long millis): 静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
> yield(): 静态方法,一旦执行此方法,就释放CPU的执行权
> join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
> isAlive(): 判断当前线程是否还存活
过时方法:
> stop(): 强行结束一个线程的执行,直接进入死亡状态。不建议使用
> void suspend() / void resume() : 可能造成死锁,所以也不建议使用
3.线程的优先级:
getPriority(): 获取线程的优先级
setPriority(): 设置线程的优先级。范围是[1,10]
Thread类内部声明的三个常量:
- MAX_PRIORITY(10):最高优先级
- MIN_PRIORITY(1): 最低优先级
- NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级。
二、线程的生命周期
常用方法练习:
package thread.demo02;
public class EvenNumberTest {
public static void main(String[] args) {
//测试1
PrintNumber t1 = new PrintNumber("线程1:偶数");
t1.setName("线程1(修改):偶数");//修改上面的线程名"线程1:偶数"为"线程1(修改):偶数"
t1.setPriority(Thread.MAX_PRIORITY);//注意不是哪个优先级高就先运行完哪个线程,是CPU几率更高优先来调用优先级高的线程。
// 打印出来发现还是主线程第一个运行,因为现在都是多核CPU,这是概率问题。
t1.start();
//测试setName()
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + " 优先级为:" +
Thread.currentThread().getPriority() + ": " + i);
}
// if (i == 20){
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
//测试isAlive()
// System.out.println("线程1(修改):偶数 是否存活?" + t1.isAlive());
//测试2
PrintNumber1 p = new PrintNumber1();
Thread t2 = new Thread(p,"线程2:奇数");
t2.start();
}
}
//测试1:-public Thread(String name):分配一个指定名字的新的线程对象。
class PrintNumber extends Thread{
//无参构造
public PrintNumber(){
}
//分配一个指定名字的新的线程对象。
public PrintNumber(String name){
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//测试sleep()
// try {
// Thread.sleep(1000);//设置了1000毫秒也就是一秒,对应的这个线程1一秒钟执行一下,直到结束
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + " 优先级为:" +
Thread.currentThread().getPriority() + ": " + i);
}
//测试yield()
// if (i % 20 == 0){
// Thread.yield();
// }
}
}
}
//测试2:- public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
class PrintNumber1 implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + " 优先级为: " +
Thread.currentThread().getPriority() + " " + i);
}
}
}
}
多线程的生命周期
Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下一些状态:
JDK1.5之前:5种状态
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。
1.新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它和其他Java对象一样,仅仅由JVM为其分配了内存,并初始化了实例变量的值。此时的线程对象并没有任何线程的动态特征,程序也不会执行它的线程体run()。
2.就绪
但是当线程对象调用了start()方法之后,就不一样了,线程就从新建状态转为就绪状态。JVM会为其创建方法调用栈和程序计数器,当然,处于这个状态中的线程并没有开始运行,只是表示已具备了运行的条件,随时可以被调度。至于什么时候被调度,取决于JVM里线程调度器的调度。
注意:
程序只能对新建状态的线程调用start(),并且只能调用一次,如果对非新建状态的线程,如已启动的线程或已死亡的线程调用start()都会报错IllegalThreadStateException异常。
3.运行
如果处于就绪状态的线程获得了CPU资源时,开始执行run()方法的线程体代码,则该线程处于运行状态。如果计算机只有一个CPU核心,在任何时刻只有一个线程处于运行状态,如果计算机有多个核心,将会有多个线程并行(Parallel)执行。
4.阻塞
当在运行过程中的线程遇到如下情况时,会让出CPU并临时中止自己的执行,进入阻塞状态:
- 线程调用了sleep()方法,主动放弃所占用的CPU资源;
- 线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
- 线程执行过程中,同步监视器调用了wait(),让它等待某个通知(notify);
- 线程执行过程中,同步监视器调用了wait(time);
- 线程执行过程中,遇到了其他线程对象的加塞(join);
- 线程被调用suspend方法被挂起(已过时,因为容易发生死锁);
当前正在执行的线程被阻塞后,其他线程就有机会执行了。针对如上情况,当发生如下情况时会解除阻塞,让该线程重新进入阻塞状态,等待线程调度器再次调度它:
- 线程的sleep()时间到;
- 线程成功获得了同步监视器;
- 线程等到了通知(notify);
- 线程wait的时间到了
- 加塞的线程结束了;
- 被挂起的线程又被调用了resume恢复方法(已过时,因为容易发生死锁);
5.死亡
线程会以以下三种方式之一结束,结束后的线程就处于死亡状态:
- run()方法执行完成,线程正常结束
- 线程执行过程中抛出了一个未捕获的异常(Exception)或错误(Error)
- 直接调用该线程的stop()来结束该线程(已过时)
JDK1.5及之后:6种状态
在java.lang.Thread.State的枚举类中这样定义:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
下图中在JAVA基础阶段暂时了解一下,不去看里面具体方法,以后在JUC里面再看