每个线程都会有一个线程组,如果没指定一个线程组,那么该线程将会被加入到父线程(创建该线程的线程)所在的线程组。而在Java中线程组则是使用类ThreadGroup 进行抽象描述。线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
1.线程优先级
线程调度器是JVM的一部分,JVM通常Java线程直接映射为本地操作系统上的本机线程
线程。可运行池中放了处 于umablle状态的线程,由线程调度器决定何时运行哪个线程。用
调用户无法控制线程调度器,也无法指定哪个线程行运。
大多数JVM采用基于优先级的抢占式调度策略。新进人可运行池的线程如果优先级比池中任意线程及当前运行的线程的优先级更高,则发生抢占,被选择运行,被抢占线程返回到nummable 状态:当运行池中线程具有相同优先级,或者当前运行线程与运行池中线程具有
相同优先级时,线程调度器随机选择线程行。
注意:在设计多线程应用程序时不能够依赖线程的优先级。线程优先级调度没有保证,只能够作为一种提高程序效率的手段。
Thread 的优先级可以是1~10,默认值是5,数值越大优先级越高。
不同的平台对优先级的支持不同,各个平台不能保证都识别10个优先级,为了更好地实现跨平台特性,通常是使用Thread 类的以下3个静态常量设定线程的优先级
①Thread. MIN_ PRIORYTY(1)。
②Thread. NORM_ PRIORTTY(5)。
③Thread. MAX_PRIORITY(10)。
2.线程生命周期和线程状态
每个Java程序都有一个主线程,即main()方法对应的线程。要实现多线程,必须在主线程中创建新的线程。每个线程要经历新生(new)、就绪(rumnablle)、运行(rumning)、阻害(blocked)、死亡(dead)五种状态,线程从新生到死亡的状态变化过程称为线程的生命周期。
(1)新生状态
无论采用哪种实现线程的方式,在创建Thread 实例后,这个Thread 对象就处于新生状态,即此时线程处于已被创建还未开始执行的一个特殊状态。当线程处于新生状态时,仅仅是一个空线程对象,它还没有分配到系统资源,因此只能启动或终止它,其他操作会引发异常。
(2)就绪状态
为了得到实际的线程,为线程创建属于它的内存资源,需要使用start()方法启动线程,这样,线程进人了就绪状态。处于就绪状态的线程已经具:备了运行的条件,但还没有获得
一旦获得CPU时间片,线程就进入运行状态
CPU时间片,因此进人线程就绪队列中排队。
并自动调用自己的run()方法。至于线程什么时候被选中运行,取决于优先级和进入就绪队
列的时间,优先级相同的线程则采用“先来先服务”的调度原则。
主线程会继续执行start()方法下面的语句,这时run()方法可能还在运行,从而实现了多任务操作。
(3)运行状态
线程一旦获得CPU时间片,就开始执行run()方法中的线程执行体,线程进人运行状态。
如果线程不能够在一个时间片内执行完毕,就会被中断,由执行状态回到就绪状态,进入到线程就绪队列中排队等待下一次被调度执行。
如果优先级高的线程进入就绪状态,它会抢占正在执行的优先级低的线程的CPU,优先级高的线程会进入运行状态,优先级低的线程会回到就绪状态。
另外,运行状态的线程可以调用yield()方法主动放弃执行,转人到就绪状态。但是它
仍然可能会被选中执行。对于yield()方法,只给相同优先级的线程可执行的机会。
(4)阻塞状态
阻塞状态其实是睡眠、资源阻塞、等待这三种状态的结合体。这三种状态有一个共同点:线程是活的,但是当前缺乏运行它的条件,等条件满足了,就返回到就绪状态。当运行的线程遇到如下情况会进入阻塞状态。
①睡眠:线程调用sleep()方法睡眠一段时间。
②资源阻塞:线程在等待一种资源,比如等待某个I/O流完成,或试图获得一个同步锁,但同步锁正被其他线程持有。
③等待:调用了wait()方法使线程挂起,直到线程得到了notify()或notifyAll ()消息。
当正在执行的线程被阻塞后,会让出CPU,其他就绪线程可以获得执行的机会。
(5)死亡状态
如果线程的run()方法执行完毕,线程正常结束;或者线程执行过程中抛出一个未捕获的异常或错误,线程异常结束。结束后,线程处于死亡状态。
一旦线程死亡,就不能复生。。如果在死亡状态的线程对象上再次调用start()方法,则会出现运行时异常IllegalThreadException。
3.线程的其他方法
(1).sleep()方法
让当前线程暂时停止下来,睡眠的时间由指定的毫秒数决定。线程醒来之后返回到就绪状态。sleep()方法指定的时间是线程不会运行的最短时间,而不是不会运行的实际时间。
static void sleep( long millis)
throws lllegalArgumentException,InterruptedException
参数millis:以毫秒为单位的睡眠时间长度。
异常IllegalArgumentException:如果 millis 值为负数。
异常InterruptedException:如果任何线程中断当前线程。当抛出此异常时,当前线程的中断状态将被清除。
try {
Thread.sleep(5000); //使线程休眠5000毫秒
} catch (InterruptedException e) { //捕获异常
e.printStackTrace(); //输出异常信息
}
注意: sleep()是Thread 类的静态方法,它只能够指定当前线程进入睡眠,不能够控制其他线程的睡眠。无论是哪个Thread 对象调用sleep()方法,实际上都是当前线程进入睡眠状态。不需要写成“t.sleep()”(t为Thread 对象)的形式,对于静态方法,直接用Thread. sleep()方式调用。
(2) yield()方法
yield()方法也是Thread类中的静态方法,作用是暂时中止当前正在执行的线程对象的运行。若存在其他同优先级线程,则随机调用下一个同优先级线程。若不存在其他同优先级
线程,则这个被中断的线程继续。这个方法可以保证CPU不会空闲,而 sleep ()方法则有可能浪费CPU时间,比如所有合作线程都在等待,则CPU什么也不做。
(3)join()方法
join()方法令当前线程“加入到”调用join()方法的线程的尾部。
final void join()
throws InterruptedException
如果线程A在线程B完成工作之前不能执行其任务,则可以将线程A“加人到”线程B,这意味在线程B进人死亡状态之前,线程A不会变为就绪状态。如果等待这个线程死亡的时间最多为millis毫秒,可以调用带参数的join(long millis)方法。
以下是事例代码:
public class Main extends Thread{
String threadId;
public Main(String threadId){
this.threadId=threadId;
}
public void run() {
try {
System.out.println("Thread started:"+this.threadId);
for(int i=0;i<6;i++){
sleep((int)(500*Math.random()));
System.out.println(threadId);
}
System.out.println("Thread stopped:"+this.threadId);
} catch (InterruptedException e) {
return;
}
}
public static void main(String[] args) {
Main t1=new Main("1");
Main t2=new Main("2");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
System.out.println("main stopped");
}
}
(4)wait()与notify()方法
这两个方法是在Object类中声明的方法。线程间的通信可以通过wait()和notify()进行。
wait()方法使线程进人等待状态,直到被另一线程唤醒。notify()方法把线程状态的变化通
知并唤醒另一等待线程,使之进人runnable 状态。 notifyAll()方法唤醒所有等待该资源的线程
wait()方法有3种重载形式: void wait(), void wait( long timeout),void wait ( long timeout,im naous)。第一种没有参数,线程会一直等待,直到被其他线程用notify ()方法或
notifyAIl()方法唤醒;后两者带参数的wait()指定了等待时间,如果没有在等待时间内被
notify ()方法或notifyAll()方法唤醒,时间到了后将自动苏醒。
编程时应该用notifyAll ()方法取代notify()方法,因为notify()方法只从锁池中唤醒一个线程,不能保证是哪一个线程是,全由调度器决定。
wait()和sleep()的区别:
①两个方法来自不不同的类,sleep()是Thread类中的静态方法,wait()是Object类
中的实体方法。
②wait()只能够在同步方法或同步代码块中使用,sleep()可以在任何地方使用。
③wait()方法被引用后会解除对象的锁,使其他线程可以执行同步代码块或方法,而sleep()方法则保持已有的任何销,不会将它们释放。