1.线程的状态
线程的状态分为4种,分别为新生状态、可运行状态、阻塞状态和死亡状态。一个具有生命的线程,总是处于上述4种状态之一。
1.1 新生状态(New Thread)
创建线程对象之后,尚未调用其start()方法之前,这个线程就有了生命,此时线程仅仅是一个空对象,系统没有为其分配资源。此时只能启动和终止线程,任何其它操作都会引发异常。
1.2 可运行状态(Runnable)
1.当调用了start()方法启动线程之后,系统为该线程分配除CPU外的所需资源,这个线程就有了运行的机会,线程处于可运行的状态,在这个状态当中,该线程对象可能正在运行,也可能尚未运行。
2.对于只有一个CPU的机器而言,任何时刻只能有一个处于可运行状态的线程占用处理机,获得CPU资源,此时系统真正运行线程的run()方法。
1.3 阻塞状态(Blocked)
1.一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态。阻塞状态是一种“不可运行”的状态,而处于这种状态的线程在得到一个特定的事件之后会转回可运行状态。
2.导致一个线程被阻塞有以下原因:
--》调用了Thread类的静态方法sleep()。
--》一个线程执行到一个I/O操作时,如果I/O操作尚未完成,则线程将被阻塞。
--》如果一个线程的执行需要用一个对象的锁,而这个对象的锁正被别的线程占用,那么此线程被阻塞。
1.4 死亡状态(Dead)
一个线程的run()方法运行完毕、stop()方法被调用或者在运行过程中出现未捕获的异常时,线程进入死亡状态。
2.线程调度
(1)当同一时刻有多个线程处于可运行状态,它们需要排队等待CPU资源,每个线程会自动获得一个线程的优先级(Priority),优先级的高低反映线程的重要或紧急程度。
(2)可运行的线程按优先级排队,线程调度依据建立在优先级基础上的“先到先服务”原则。
(3)线程调度管理器负责线程排队和在线程间分配CPU,并按线程调度算法进行调度。当线程调度管理器选中某个线程时,该线程获得CPU资源进入运行状态。
(4)线程调度是抢占式调度,即在当前线程执行过程中如果有一个更高优先级的线程进入可运行状态,则这个更高优先级的线程立即被调度执行。
2.1 线程优先级
线程的优先级用1~10表示,10表示优先级最高,默认值是5。
每个优先级对应一个Thread类的公用静态常量。
--》public static final int NORM_PRIORITY=5;
--》public static final int MIN_PRIORITY=1;
--》public static final int MAX_PRIORITY=10;
线程的优先级可以通过setPriority(int grade)方法更改,此方法的参数表示要设置的优先级,它必须是一个1-10之间的整数。
2.2 实现线程调度的方法
join()方法:使当前线程暂定运行,等待调用该方法的线程执行结束后再继续执行本线程。
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+"线程正在运行"+i);
}
}
}
//线程强制执行:join()方法
public static void main(String[] args) {
MyThread mt=new MyThread();
mt.setName("another");
for (int i =1; i <=20; i++) {
if (i==10) {
mt.start();//启动mt线程
try {
mt.join();;//当i=10时,mt线程加入进来,强制执行结束后,再执行main线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"线程正在运行"+i);
}
}
sleep()方法:线程休眠。使当前线程休眠millis毫秒,线程由运行中的状态进入不可运行状态,休眠时间过后线程会再次进入可运行状态。
//线程休眠:sleep(long millis)方法:参数是毫秒
public class Wait {
public static void waitTime(long s){
for (int i =1; i <=s; i++) {
System.out.println(i+"秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
yield()方法:线程礼让。让当前线程暂停执行,允许其它线程执行,但该线程仍处于可运行状态,并不变为阻塞状态。此时,系统选择其他相同或更高优先级线程执行,若无其它相同或更高优先级线程,则该线程继续执行。
//线程礼让
public class MyThread extends Thread {
public void run() {
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+"线程运行"+i);
if (i==5) {
System.out.print("线程礼让:");
Thread.yield();
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.setName("one");
mt2.setName("two");
mt1.start();
mt2.start();
}
}
3.线程同步
3.1 概述
上述所说的线程都是独立的,而且异步执行。但当一些同时运行的线程需要共享数据时,此时就需要考虑其他线程的状态和行为,否则就无法保证程序运行结果的正确性。
线程同步:当两个及以上的线程需要访问同一资源时,需要以某种顺序来确保该资源在某一时刻只能被一个线程使用的方式。
3.2 实现线程同步
采用同步来控制线程的执行有两种方式,即同步方法和同步代码块。这两种方式都使用synchronized关键字实现。
3.2.1 同步方法
语法格式:
访问权限修饰符 synchronized 返回值类型 方法名(){} 或
synchronized 访问权限修饰符 返回值类型 方法名(){}
代码说明:
编写线程类
public class Site implements Runnable {
private int count = 10;// 默认剩余票数10张
private int num = 0;// 记录买到第几张票
boolean flag=false;
//同步方法解决多线程操作共享数据引发的问题:synchronized关键字修饰方法,synchronized关键字放在访问权限修饰符前后都可以
public synchronized void safe(){
// 没有票时跳出循环
if (count <= 0) {
flag=true;//没有票时改变标记
return;//没有票时结束方法
}
// 记录票数的变化
count--;
num++;
try {
Thread.sleep(500);// 模拟网络延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num
+ "张票,剩余" + count + "张票");
}
@Override
public void run() {
//有票时执行同步方法
while(!flag){
safe();
}
}
}
测试
public static void main(String[] args) {
Site site=new Site();
Thread people1=new Thread(site, "淘泡泡");
Thread people2=new Thread(site, "抢票代理");
Thread people3=new Thread(site, "黄牛");
System.out.println("******开始抢票******");
people1.start();
people2.start();
people3.start();
}
说明:
1.使用synchronized修饰的方法控制对类成员变量的访问。每个类实例对应一把锁,方法一旦执行,就独占该锁,直到该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
2.这种机制确保了同一时刻对应每一个实例,其所有声明为synchronized的方法只能有一个处于可执行状态,从而有效的避免了类成员变量的访问冲突。
3.2.2 同步代码块
语法格式:
synchronized(){
//需要同步访问控制的代码
}
public class Site implements Runnable {
private int count = 10;// 默认剩余票数10张
private int num = 0;// 记录买到第几张票
@Override
public void run() {
while(true){
/*synchronized关键字修饰代码块解决多线程操作共享数据引发的问题
*当线程抢占到CPU资源时,synchronized修饰的代码块从头到尾执行完后,再允许线程抢占CPU资源
*this表示线程对象,哪个线程抢占到CPU资源,this就表示哪个线程
*/
synchronized (this) {
// 没有票时跳出循环
if (count <= 0) {
break;
}
// 记录票数的变化
count--;
num++;
try {
Thread.sleep(500);// 模拟网络延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num
+ "张票,剩余" + count + "张票");
}
}
}
}
测试代码和上述一样。