Java 线程的状态
文章目录
- Java 线程的状态
- 线程的基础状态
- 1 常见方法
- 1.1 休眠
- 1.2 放弃
- 1.3 加入
- 1.4 优先级
- 1.5 线程打断
- 1.6 守护线程
- 1.7 线程的状态 - 等待
- 2 线程安全问题
- 2.1 线程同步: 同步代码块
- 2.2 线程同步: 同步方法
- 2.3 同步规则
- 2.4 线程的状态 - 阻塞
- 2.5 特殊现象: 死锁
线程的基础状态
初始状态(New) : 线程对象被创建,即为初始状态。只在堆中开辟内存,与常规对象无异。
就绪状态(Ready) : 调用start()之后,进入就绪状态。等待OS选中,并分配时间片。
运行状态(Running) : 获得时间片之后,进入运行状态,如果时间片到期,则回到就绪状态。
终止状态(Terminated) : 主线程main()或独立线程run()结束,进入终止状态,并释放持有的时间片。
1 常见方法
1.1 休眠
- public static void sleep(long millis)
- 当前线程主动休眠 millis 毫秒,不再参与CPU竞争,直达休眠结束。
eg:
public class TestSleep {public static void main(String[] args) {SleepThread s1 = new SleepThread();SleepThread s2 = new SleepThread();s1.start();s2.start();}static class SleepThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"..."+i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}
1.2 放弃
- public static void yield()
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
eg:
public class TestYield {public static void main(String[] args) {YieldThread y1 = new YieldThread();YieldThread y2 = new YieldThread();y1.start();y2.start();}static class YieldThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"..."+i);//主动放弃CPUThread.yield();}}}
}
1.3 加入
- public final void join()
- 允许其他线程加入到当前线程中。当前线程会阻塞,直到加入线程执行完毕。
内存分析:
eg:
public class TestJoin {public static void main(String[] args) throws InterruptedException {JoinThread j1 = new JoinThread();j1.start();//把j1加入到主线程中,造成主线程阻塞,直到j1执行完毕j1.join();for (int i = 0; i < 10; i++) {System.out.println("主线程........"+i);}}static class JoinThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"..."+i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}
1.4 优先级
- 线程对象.setPriority(int)
- 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
eg:
public class TestPriority {public static void main(String[] args) {PriorityThread p1 = new PriorityThread();PriorityThread p2 = new PriorityThread();PriorityThread p3 = new PriorityThread();//线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多p1.setPriority(1);p3.setPriority(10);//优先级设置要在线程开始之前p1.start();p2.start();p3.start();}static class PriorityThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"..."+i);}}}
}
1.5 线程打断
- 线程对象.interrupt();
- 打断线程,被打断线程抛出InterruptedException异常。
eg:
public class TestInterrupt {public static void main(String[] args) {InterruptThread thread = new InterruptThread();thread.start();System.out.println("10秒内输入任意字符打断子进程休眠");Scanner input = new Scanner(System.in);input.next();thread.interrupt();}static class InterruptThread extends Thread{@Overridepublic void run() {System.out.println("子线程开始休眠...");try {Thread.sleep(10000);System.out.println("正常醒来");} catch (InterruptedException e) {System.out.println("被打醒了");}}}
}
1.6 守护线程
- 线程有两类:用户线程(前台线程)、守护线程(后台线程)。
- 如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
- 垃圾回收器线程属于守护线程。
- setDaemon(true)设置为守护线程。
eg:
public class TestDaemon {public static void main(String[] args) throws InterruptedException {DaemonThread daemonThread = new DaemonThread();//设置守护线程daemonThread.setDaemon(true);//默认false,关闭daemonThread.start();for (int i = 0; i < 10; i++) {System.out.println("主线程...."+i);Thread.sleep(1000);}}static class DaemonThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName()+"..."+i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}
1.7 线程的状态 - 等待
2 线程安全问题
需求:A线程将“Hello”存入数组;B线程将“World”存入数组。
线程不安全:
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源,一次仅允许一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
2.1 线程同步: 同步代码块
同步:一个线程接着一个线程等待执行。异步:多个线程并发同时执行。
语法:
同步代码块:
synchronized(锁对象){ //使用共享资源对象加锁//同步代码(原子操作)
}
eg:
4个窗口共卖1000张票
Ticket:
public class Ticket implements Runnable{private int count = 1000; //票数@Overridepublic void run() {while (true) {//一般选择共享资源synchronized (this) { //锁, 任何引用类型的对象都可以作为锁,但要保证唯一性if (count<=0) {break;}System.out.println(Thread.currentThread().getName() +"卖了第" + count + "张票");count--;}}}
}
Test:
public class Test {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(ticket,"窗口1").start();new Thread(ticket,"窗口2").start();new Thread(ticket,"窗口3").start();new Thread(ticket,"窗口4").start();}
}
注意:
- 任何的引用类型对象都可以作为锁,但是保证多个线程使用唯一对象,一般使用共享资源作为锁。
- 每个对象都有一个互斥锁标记,用来分配给线程的,只有拥有对象互斥锁标记的线程,才能进入同步代码。
- 线程退出同步代码块时,会释放相应的互斥锁标记。
2.2 线程同步: 同步方法
语法:
同步方法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁// 代码(原子操作)
}
eg:
4个窗口共卖1000张票
Ticket:
public class Ticket implements Runnable{private int count = 1000;@Overridepublic void run() {while (true) {if (!sale()) {break;}}}//同步方法public synchronized boolean sale() {if (count<=0) {return false;}System.out.println(Thread.currentThread().getName() +"卖了第" + count + "张票");count--;return true;}
}
注意:
- 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
- 线程退出同步方法时,会释放相应的互斥锁标记。
- 如果当前方法是非静态方法锁是"this",如果方法是静态方法锁是"类名.class"
2.3 同步规则
注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
已知JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为synchonized修饰的同步方法。
eg:
你和你女朋友共用一张银行卡,你向卡中存钱,你女朋友从卡中取钱,使用程序模拟过程?
BankCard:
public class BankCard {private double money;public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}
}
Test:
public class Test {public static void main(String[] args) {BankCard card = new BankCard();//匿名内部类Runnable save = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {synchronized (card) {card.setMoney(card.getMoney() + 1000);System.out.println(Thread.currentThread().getName()+"存了1000, 余额为"+card.getMoney());}}}};//匿名内部类Runnable take = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {synchronized (card) {if (card.getMoney() >= 1000) {card.setMoney(card.getMoney() - 1000);System.out.println(Thread.currentThread().getName()+"取了1000, 余额为"+card.getMoney());} else {System.out.println("赶紧存钱...");i--;}}}}};new Thread(save,"小明").start();new Thread(take,"小红").start();}
}
2.4 线程的状态 - 阻塞
注:JDK5之后就绪、运行统称Runnable
2.5 特殊现象: 死锁
死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
eg:
吃饭问题
Test:
public class TestDeadLock {public static void main(String[] args) {new Boy().start();new Girl().start();}static class Lock {static Object LockA = new Object();static Object LockB = new Object();}static class Boy extends Thread {@Overridepublic void run() {synchronized (Lock.LockA) {System.out.println("Boy拿到了A锁");synchronized (Lock.LockB) {System.out.println("Boy拿到了B锁");System.out.println("Boy可以吃饭了");}}}}static class Girl extends Thread {@Overridepublic void run() {synchronized (Lock.LockB) {System.out.println("Girl拿到了B锁");synchronized (Lock.LockA) {System.out.println("Girl拿到了A锁");System.out.println("Girl可以吃饭了");}}}}
}
情况一: 死锁
Boy拿到了A锁
Girl拿到了B锁
情况二: 未死锁
Boy拿到了A锁
Boy拿到了B锁
Boy可以吃饭了
Girl拿到了B锁
Girl拿到了A锁
Girl可以吃饭了