线程的状态
- 新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
- 就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权
- 运行状态:处于这个状态的线程占用CPU,执行程序的代码
- 阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。 可以细分为三种情况:
- 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中
- 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。
- 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。
线程的优先级
- 当线程的优先级没有指定时,所有线程都携带普通优先级。
- 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
- 优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
- 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
- t.setPriority()用来设定线程的优先级。
- 在线程开始方法被调用之前,线程的优先级应该被设定。
- 你可以使用常量,如
MIN_PRIORITY
,MAX_PRIORITY
,NORM_PRIORITY
来设定优先级
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
线程的使用
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1 begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
}
});
t1.start();
线程中特殊函数
join()
join方法是一个属于对象的方法,主要作用是是的调用join方法的这个线程对象先执行,调用方法所在的线程等执行完了,在执行。
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1 begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
}
});
t1.start();
t1.join();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2 begin");
System.out.println("t2 end");
}
});
t2.start();
输出的结果:
//注释t1.join()
t1 begin
t2 begin
t2 end
t1 end
//没有注释t1.join()
t1 begin
t1 end
t2 begin
t2 end
wait()
表示等待获取某个锁执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒 notify() 执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。使线程由阻塞队列进入就绪状态(只能在同步代码块中使用)上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行
sleep()
是一个类的方法,让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后又会自动恢复运行状态。有一个用法可以代替yield函数——sleep(0)
yield()
这方法与sleep()类似,可以使用sleep(0)来达到相同的效果,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级或者高优先级的线程有执行的机会,注意这里并不是一定,有可能又会执行当前线程,执行完后,这个线程的状态从执行状态转到了就绪状态。
notify()
执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。使线程由阻塞队列进入就绪状态。注意:这里必须持有相同锁的线程
interrupt()
中断线程,被中断线程会抛InterruptedException
线程的停止
当线程启动时,我们怎么去停止启动的线程呢?一般来说,有
run()和start()的区别
我们从源码来学习,这两个方法的不同,Thread类的方法:
/**
* Package-scope method invoked by Dalvik VM to create "internal"
* threads or attach threads created externally.
*
* Don't call Thread.currentThread(), since there may not be such
* a thing (e.g. for Main).
*/
Thread(ThreadGroup group, String name, int priority, boolean daemon) {
synchronized (Thread.class) {
id = ++Thread.count;
}
if (name == null) {
this.name = "Thread-" + id;
} else {
this.name = name;
}
if (group == null) {
throw new InternalError("group == null");
}
this.group = group;
this.target = null;
this.stackSize = 0;
this.priority = priority;
this.daemon = daemon;
/* add ourselves to our ThreadGroup of choice */
this.group.addThread(this);
}
/**
* Initializes a new, existing Thread object with a runnable object,
* the given name and belonging to the ThreadGroup passed as parameter.
* This is the method that the several public constructors delegate their
* work to.
*
* @param group ThreadGroup to which the new Thread will belong
* @param runnable a java.lang.Runnable whose method <code>run</code> will
* be executed by the new Thread
* @param threadName Name for the Thread being created
* @param stackSize Platform dependent stack size
* @throws IllegalThreadStateException if <code>group.destroy()</code> has
* already been done
* @see java.lang.ThreadGroup
* @see java.lang.Runnable
*/
//带runnable参数的thread类的构造函数调用了这个方法
private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
Thread currentThread = Thread.currentThread();
if (group == null) {
group = currentThread.getThreadGroup();
}
if (group.isDestroyed()) {
throw new IllegalThreadStateException("Group already destroyed");
}
this.group = group;
synchronized (Thread.class) {
id = ++Thread.count;
}
if (threadName == null) {
this.name = "Thread-" + id;
} else {
this.name = threadName;
}
//建立的runnable接口赋值给thread中的target
this.target = runnable;
this.stackSize = stackSize;
this.priority = currentThread.getPriority();
this.contextClassLoader = currentThread.contextClassLoader;
// Transfer over InheritableThreadLocals.
if (currentThread.inheritableValues != null) {
inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
}
// add ourselves to our ThreadGroup of choice
this.group.addThread(this);
}
run
方法的源代码:
public void run() {
if (target != null) {
target.run();
}
}
在run方法中,直接调用的是我们传入的target(Runnable对象)的run方法,并没有开启新的线程
start
方法的源代码:
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
nativeCreate(this, stackSize, daemon);
}
start
方法最后调用了nativeCreate
的native方法,这个方法的主要作用是开启了一个新的线程。并且这个方法,会利用jni回调Thread的run方法。
总结:
- 如果直接调用run方法,并没有开启新的线程,而是直接运行run方法里面的内容,
- 而start方法,则会调用native方法 nativeCreate 开启线程
线程的停止
实际开发中,我们使用线程的场景一般是执行耗时任务,如果我们开启了多个新的线程来执行新的任务,最后又不在对他进行关闭,这样有时候会浪费资源和内存的泄露。那我们怎么来管理我们的线程呢?目前有两种方法:
- 我们自己手动开发,管理我们的线程,包括线程的启动,线程的回收, 线程的停止等
- 使用JDK中自带的线程池技术
今天我们不讲线程池,后面的文章会讲到。对于单个线程而言,上面我们将了他的启动,现在我们来讲他的关闭。
线程的关闭的二种方式:
1. 使用标志位
我们定义一个标志位,在线程的run方法中,不断的循环检测标志位,从而确定是否退出
public class ShutdownThread extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}
}
}
2. 使用interrupt方法
这里可以分为两种情况:
- 线程处于阻塞状态,如使用了sleep,同步锁的wait,socket的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,系统会抛出一个InterruptedException异常,代码中通过捕获异常,然后break跳出循环状态,使线程正常结束。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的,一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。
public class ShutdownThread extends Thread {
public void run() {
while (true){
try{
Thread.sleep(5*1000);阻塞5妙
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行break跳出循环。
}
}
}
}
- 线程未进入阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环,当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
public class ShutdownThread extends Thread {
public void run() {
while (!isInterrupted()){
//do something, but no tthrow InterruptedException
}
}
}
为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。
因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑:
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
try{
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行break跳出循环。
}
}
}
}