线程的生命周期:
当线程被创建并被启动时,它既不是一启动就进入了执行状态,在线程的生命周期中,它要经过new(新建),就绪(Runnable),运行(Running),阻塞(Blocked),dead(死亡)。
当线程启动之后,它不可能一直霸占着cpu独自运行,所有cpu需要在多条线程轮流切换,于是线程就也会多次在运行.就绪之间切换。
新建和就绪状态
当程序使用new关键字创建了一个线程时,该线程就处于新建状态,此时它和其它java对象一样,仅有虚拟机分配内存,并初始化成员变量的值。此时的线程对象并没有表现出线程的任何动态特征,程序也不会执行线程的线程执行体。
当线程对象调用了start()方法后,该线程就处于就绪状态,java虚拟机会为其创建方法调用栈和程序计数器,处于该状态的线程并没有开始执行,只是表明该线程可以运行了,至于该线程何时运行,取决于JVM的调度。
启动线程要调用start方法,而不是run方法,永远不要调用线程的run方法,如果调用run方法,系统会把线程对象当作普通的对象,会吧线程的执行体当作普通方法来调用!
在调用了run方法之后,该线程就不在处于新建状态,不要再调用该线程的start方法!
java中只能对处于新建状态的线程使用start方法,否则将会引发IllegalThreadStateException异常!
如果希望调用子线程的start()方法后子线程立即开始执行,可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠一毫秒,这样CPU就会立即启动另一个处于就绪状态的线程,需要注意的是,使用Thread.sleep()方法需要声明InterruptedException异常!
运行和阻塞状态:
当发生如下的几种情况时,将会进入阻塞状态:
当线程调用sleep方法主动放弃所占用的处理器资源
线程调用了一个阻塞时的IO方法,在该方法返回之前,线程会被阻塞
线程试图获得一个同步监视器,但该同步监视器正被其他线程锁持有
线程正在等待某个通知(notify)
程序调用了线程的suspend方法将该线程挂起
当以上几个情况,当发生如下的情况将会重新进入就绪状态
线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。就绪和运行状态之间的转换通常不受程序控制,而是系统线程的调度决定的。
调用yield()方法可以让处于运行时的线程转入就绪状态。
线程死亡:
线程会以以下三种方式结束,结束后处于死亡状态
run或call方法执行完成,程序结束
线程抛出一个未捕获的Exception或者Error
直接调用该线程的stop方法来结束线程
当主线程结束时,其它线程不受任何影响,并不会随之结束。一旦子线程启动起来后,他就会拥有和主线程相同的地位,它不会受主线程影响。
为了测试某个线程是否死亡,可以调用该线程的isAlive方法,当线程处于就绪,运行,阻塞三种状态时,将返回true;当线程处于新建,死亡两种状态时返回为false。
不要试图对一个已经死亡的线程调用start方法让它重新启动,死亡后的线程无法作为线程使用。
如果处于非新建状态的线程使用start方法,就会引发IllegalThreadStateException异常。
控制线程:
join线程:
Thread提供了让一个线程等待另一个线程完成的方法--join方法,当在某个程序执行流中调用其他线程的join方法,调用线程将被阻塞,直到被join方法加入的join线程执行完毕为止。
join方法通常由使用线程的程序调用,以将大问题划为许多小问题,每个小问题分配一个线程,当所有的小问题都被处理之后,再调用主线程进行下一步操作!
package Test1; public class JoinThread extends Thread{ public JoinThread(String name) { super(name); } @Override public void run() { for (int i = 0; i <10; i++) { System.out.println(getName()+" "+i); } } } package Test1; public class Test { public static void main(String[] args) throws InterruptedException { new JoinThread("新线程").start(); for (int i = 0; i <10; i++) { if(i==5) { JoinThread j1=new JoinThread("被join的线程"); j1.start(); j1.join(); } System.out.println(Thread.currentThread().getName()+" "+i); } } }
在被join的线程执行前,两个线程交替执行,而主线程处于等待状态,直到被join的线程执行完毕,主线程继续执行!
后台线程:
有一种线程,是在后台运行的,其任务是为其他线程提供服务,这种线程称之为后台线程(Daemon Thread),又称之为守护线程。jvm的垃圾回收器就是典型的后台进程。
当前台线程全部死亡,后台线程会自动死亡
调用Thread的setDaemon(ture)方法可以将指定线程设置成为后台线程。
当整个虚拟机只剩下后台线程时,程序就没有运行的必要了,所有虚拟机将退出
Thread类还提供了一个isDaemon方法,用于指定该线程是否是后台线程!
前台创建的线程默认为前台线程,而后台创建的线程默认为后台线程。
前台线程死亡时,jvm会通知后台线程死亡,但它从接受指令到做出响应需要一段时间 此外,如果要将某个线程设置为后台线程,必须要在该线程启动之前设置
,也就是setDaemon(true)必须在start方法之前调用,否则会引发IllegalThreadStateException异常。
线程睡眠:sleep
当前线程调用sleep方法进入阻塞状态时,在其睡眠时间内,该线程不会获得执行的机会
即便系统中没有其它可执行的线程,处于sleep的线程也不会执行,因此sleep方法常用于暂停程序的执行!
线程让步:
yield会让该线程暂停,但是它不会阻塞线程,其只是将线程转入就绪状态,也就是说,yield方法只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能出现这种情况,--某个线程调用了yield后,线程调度器又将其调用出来执行
在多CPU并行的环境下,yield功能有时并不明显
sleep()方法和yield方法的区别:
sleep方法暂停当前线程后会给其它线程执行机会,不会理会其它线程的优先级,但yield方法之后给优先级相同,或优先级更高的线程执行机会。
sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转为就绪状态,而yield方法不会转入阻塞状态,只是强制将当前线程转入就绪状态
sleep方法声明抛出了InterruptedException异常,所有调用sleep方法就要捕获此异常,而yield方法则没有
sleep方法比yield方法有更好的执行!
改变线程的优先级:
每个线程都有一定的优先级,优先级更高的线程将会有更多的执行机会
每个线程默认的优先级都与创建它的父进程的优先级相同,默认情况下,main进程具有普通优先级
Thread类提供setPriority(int newPriority)和getPriority()方法来设置和返回线程的优先级其中setPriority参数是int类型,范围0到10之间
Thread类有三个静态常量:MAX_PRIORITY :10 MIN_PRIORITY :1 NORM_PRIORITY:5
线程同步:
同步代码块:
synchronize(obj){ }
obj:同步监视器,含义:线程开始执行同步代码块时,必须先获得对同步监听器的锁定
package Test2; public class Account { private String AccountNo; private double balance; public Account(){ } public Account(String accountNo, double balance) { AccountNo = accountNo; this.balance = balance; } public String getAccountNo() { return AccountNo; } public double getBalance() { return balance; } public void setAccountNo(String accountNo) { AccountNo = accountNo; } public void setBalance(double balance) { this.balance = balance; } public synchronized void draw(double drawcount) { if(balance>=drawcount) { System.out.println(Thread.currentThread().getName()+"取款成功,取出"+drawcount+"元"); try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } balance-=drawcount; System.out.println("余额:"+balance); } else { System.out.println(Thread.currentThread().getName()+" "+"余额不足"); } } public int hashCode() { return AccountNo.hashCode(); } public boolean equals(Object obj) { if(this==obj) return true; if(obj!=null&&obj.getClass()==Account.class) { Account a=(Account)obj; return a.getAccountNo().equals(AccountNo); } return false; } }
package Test2; public class Test extends Thread { private Account account; private double drawAccount; /** * @param account * @param drawAccount */ public Test(String name,Account account, double drawAccount) { super(name); this.account = account; this.drawAccount = drawAccount; } @Override public void run() { account.draw(drawAccount); } public static void main(String[] args) { Account a=new Account("3242332",1000); Test t1=new Test("甲", a, 600); Test t2=new Test("乙", a, 600); t1.start(); t2.start(); } }