Java、安卓的线程理解系列一(一篇就够了)
基础知识篇
一、线程的状态
(这方面知识极力推荐去看孙老师的《Java面向对象编程》)
首先我们要理解线程的状态。
1.新建状态:用new语句创建的线程处于新建状态,与其他new出来的对象一样,仅仅是在堆区分配了内存
2.就绪状态:当一个线程对象被创建后,调用其start()方法,该线程就进入就绪状态,处于这个状态的线程位于可运行池中,等待CPU的使用权
3.运行状态:一般计算机只有一个CPU,那么任何时刻只有一个线程占用CPU,处于这个状态。当然,有些计算机有多个CPU,那么同一时刻就有几个线程占用不同的CPU。记住只有处于就绪状态的线程才有机会转到运行状态。
4.阻塞状态:线程因为某些原因放弃CPU,暂时停止运行。主要有三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5.死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二、线程的状态变化有关的方法
线程的状态发生改变总是与下面几个方法有关。
1、sleep()线程睡眠方法:当一个线程在运行状态执行了sleep()方法,它就会放弃CPU,转到阻塞状态。当它结束了阻塞状态,它就进入就绪状态,在可运行池中等待获得CPU(获得CPU的时机往往是另一个线程运行结束)。
2、yield()线程让步方法:当一个线程在运行状态执行了yield()方法,如果此时具有相同优先级或者更高优先级的其他线程处于就绪状态,yield()方法将把当前运行状态的线程转为就绪状态,放到可运行池中并使另外一个线程处于运行状态。如果没有具有相同优先级的其他线程处于就绪状态,yield()方法上面也不做。
3、join()线程等待其他线程结束方法:当一个线程在运行状态调用了另一个线程的join()方法,当前线程将转到阻塞状态,直至另一个线程运行结束,它才回复到就绪状态,最后恢复运行状态。
这里注意:sleep()和yield()都是使线程放弃CPU,让其他线程获得运行的机会。但是两者是有区别的:sleep()方法是不考虑其他线程优先级给其他线程运行机会的,yield()方法考虑优先级。sleep()方法使当前线程转到堵塞状态,yield()方法使当前线程转到就绪状态。sleep()方法据说比yield()方法具有更好的移植性。
4、wait()线程通信的方法:当一个线程执行到wait()方法时,它就进入一个和该对象相关的等待池中,同时释放了该对象锁。使得其他线程可以访问。
5、notify()、notifyAll()线程通信的方法:notify(),执行该方法的线程随机唤醒在对象相关的等待池中等待的一个线程,把它转到对象的锁池中,如果对象相关的等待池中没有线程,则notify()什么也不做。notifyAll(),执行该方法的线程唤醒在对象相关的等待池中等待的所有线程,把它们转到对象的锁池中。
这里注意:wait()、notify()、notifyAll()必须放在synchronized修饰的代码块中,否则会抛出异常。
例子:在waitAndNotifyAll()主线程函数中,会启动一个WaitThread线程,在该线程中将会调用sleep函数睡眠3秒钟。线程启动后,主线程调用sLockObject对象的wait函数,使主线程进入sLockObject对象的等待池,此时主线程不会继续执行,等WaitThread在run函数中睡眠3秒后会调用sLockObject的notifyAll函数,唤醒sLockObject对象等待池中的主线程,主线程继续执行。
三、线程的调度
上面说到计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是各个线程轮流获得CPU的使用权,分别执行各自的任务。线程调度是指按照特定的机制为多个线程分配CPU的使用权,有两种调度模型:分时调度模型和抢占式调度模型。
分时调度模型是指让所有线程轮流获得CPU的使用权,平且平均分配每个线程占用CPU的时间片。
Java虚拟机采用的抢占式调度模型,它是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中线程的优先级同相同,那么就随机地选择一个线程运行,使其占有CPU,并且一直处于运行状态直到该线程自己运行结束。当然,线程也会不得不放弃CPU,其原因有多种:Java虚拟机让当前线程暂时放弃CPU使其他线程获得运行机会、当前线程因为某些原因进入阻塞状态。
下面这个例子就能说明两个同优先级的线程1、2同时启动,Java虚拟机随机选择一个线程运行,使其占有CPU,运行结束后再执行另一个线程。
线程:
public class Machine implements Runnable{
private int a = 1;
@Override
public void run() {
for(int i= 0; i < 5; i++){
a+=i;
a-=i;
Log.i("Test:"+currentThread(),"a="+a);//按当前线程打印
}
}
}
主函数:
Machine machine1 = new Machine();
Thread t1 = new Thread(machine1);
Thread t2 = new Thread(machine1);
t1.start();//线程1启动
t2.start();//线程2启动
一种可能的打印结果:(由图可见先执行完[Thread-3,5,main],再执行[Thread-2,5,main])
02-25 13:33:57.223 3399-3415/com.pikamouse.thread I/Test:Thread[Thread-3,5,main]: a=1
02-25 13:33:57.223 3399-3415/com.pikamouse.thread I/Test:Thread[Thread-3,5,main]: a=1
02-25 13:33:57.223 3399-3415/com.pikamouse.thread I/Test:Thread[Thread-3,5,main]: a=1
02-25 13:33:57.223 3399-3415/com.pikamouse.thread I/Test:Thread[Thread-3,5,main]: a=1
02-25 13:33:57.223 3399-3415/com.pikamouse.thread I/Test:Thread[Thread-3,5,main]: a=1
02-25 13:33:57.225 3399-3414/com.pikamouse.thread I/Test:Thread[Thread-2,5,main]: a=1
02-25 13:33:57.225 3399-3414/com.pikamouse.thread I/Test:Thread[Thread-2,5,main]: a=1
02-25 13:33:57.225 3399-3414/com.pikamouse.thread I/Test:Thread[Thread-2,5,main]: a=1
02-25 13:33:57.225 3399-3414/com.pikamouse.thread I/Test:Thread[Thread-2,5,main]: a=1
02-25 13:33:57.225 3399-3414/com.pikamouse.thread I/Test:Thread[Thread-2,5,main]: a=1
既然上面说到了运行池中的线程有优先级,我们来谈谈怎样设置线程的优先级。Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。Thread类有3个静态常量:
MAX_PRIORITY:取值为10,表示优先级最高
NORM_PRIORITY:取值为5,表示默认优先级(主线程的默认优先级)
MIN_PRIORITY:取值为1,表示优先级最低
注意:不同的系统线程的优先级分类是不同的,所以一般为了程序能移植到各中系统中使用,建议只用上面提到的三种优先级