#进程和线程
进程:这里不讲枯燥的概念,举一个例子:你在windows系统中,看到后缀为.exe的文件,都是一个程序。不过程序是死的,静态的。当你双击这个.exe执行的时候,这个.exe文件的指令就会被加载,那么你就能得到一个有关这个.exe程序的一个进程。进程是活的,或者说是正在被执行的。
线程:进程中可以容纳若干个线程,他们并不是看不见、摸不着的。也可以通过工具看到他们。用稍微专业点的术语说,线程就是轻量级的进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调用的成本远远小于进程。
一个线程的生命周期:
new状态表示刚刚创建的线程,这种线程还没开始执行。等到线程的start方法调用时,才表示线程开始执行。当线程执行时,处于runnable状态,表示线程所需的一切资源都经准备好了。如果线程在执行过程中遇到了synchronized同步块,就会进入blockeduserid状态,这时线程就会暂停执行,知道获得请求的锁。waiting和timed-waiting都表示等待的状态,他们的区别是waiting会进入一个无时间限制的等待,time-waiting会进行一个有时限的等待,那等待的线程究竟在等待什么呢?一般来说,waiting的线程正是在等待一些特殊的事件。比如,通过wait方法等待的线程在等待notify方法,而通过join方法等待的线程则会等待目标线程的终止。一旦等到了期望的事件,线程就会再次执行,进入runnable状态。当线程执行完后,则进入terminated状态,表示结束。
#初始线程:线程的基本操作
##新建线程
java中如何新建一个线程。有两种方案:
Thread thread = new Thead();
thread.start();
2.常用做法
Thread thread = new Thead(new TaskRunnalbe());
thread.start();
线程start之后,会干什么呢?start方法会新建一个线程并让这个线程执行run方法。
特别注意:
Thread thread = new Thead();
thread.run();
这个方法是在主线程中执行的。
##终止线程
一般来说,线程在执行完毕后就会结束,无须手工关闭。但是,凡事都有例外,一些服务器的后台线程可能会常驻系统,他们通常不会正常终结。比如,他们的执行体本身就是一个大大的后端的无穷循环,用于提供某些服务。
那如何正确的关闭线程呢?
1.jdk提供的stop方法。(已被废弃,不推荐使用)
已被废弃,不推荐使用,因为可能会引起数据的不一致。为什么呢?因为Thread.stop方法在结束线程时,会直接终止线程,并且会立即释放这个线程所持有的锁。而这些锁恰恰是用来维持对象一致性的。如果此时,写线程写入数据正写到一半,并强行终止,那么对象就会被写坏,同时,由于锁已经被释放,另外一个等待该锁的读线程就顺理成章的读到了这个不一致的对象,悲剧就发生了。如果在线上环境跑出不一致的结果,那么加班加点估计是免不了的了,因为这类问题一旦出现,就很难排查,因为它们甚至没有任何错误信息,也没有线程堆栈。这种情况一旦混杂在动则十几万行的程序代码中时,发现他们全凭经验,时间还有一点点的运气了。因此,除非你很清楚你在做什么,否则不要随便使用stop方法来停止一个线程。
2.在线程中增加一个stopMe方法即可。
public class ChangeObjectThread extends Thread{
volatile boolean stopMe = false;
public void setStopMe() {
stopMe = true;
}
@Override
public void run() {
while (true) {
if (stopMe) {
System.out.println("exit by stop me");
break;
}
//..........具体的业务代码
}
}
}
##线程中断
在java中,线程中断是一种重要的线程协作机制。从表面上理解,中断就是让目标线程停止执行的意思,实际上并非完全如此。在上一节中,我们已经介绍了stop方法停止的害处,并且使用了一套自有的机制完善线程退出的功能。那在jdk中是否提供了更强大的支持呢?那就是线程中断。
严格来讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出了,至于目标线程接到通知后如何处理,则完全由目标线程自行决定。这点很重要,如果中断后,线程立即无条件退出,我们就又会遇到stop方法的老问题。
与线程中断相关有三个方法:
public void Thread.interrupt()// 中断线程
public boolean Thread.isInterrupted()//判断是否被中断
public static boolean Thread.interrupted()//判断是否被中断,并清除当前中断状态
Thread.interrupt来置中断状态,Thread.isInterrupted来书写中断处理逻辑。具体看起来好像跟前面的增加stopMe标记的手法非常相似,但是中断的功能更为强劲。Thread.sleep会抛出一个中断异常,如果在这种情况下依然想保持数据的一致性,Thread.interrupt就显得尤为重要了。
##等待和通知
涉及方法:
wait()
notify()
当在一个对象实例上调用wait方法后,当前线程就会在这个对象上等待。这是什么意思呢?比如,线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,而转为等待状态。等待何时结束呢?线程A会一直等到其他线程调用了obj.notify()方法为止。这时,obj对象就俨然成为多个线程之间的有效通信手段。
那wait和notify究竟是如何工作的呢?如果一个线程调用了obj.wait(),那么它就会进入obj对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当obj.notify()被调用时,它就会从这个等待队列中,随机选择一个线程,并将其唤醒。这里希望大家注意的是,这个选择是不公平的,并不是先等待的线程就优先被选择,这个选择完全是随机的。
除了notify方法外,obj对象还有一个notifyall方法,它和notify的功能基本一致,但不同的是,它会唤醒在这个等待队列中所有等待的线程,而不是随机选择一个。
这里要强调一点,obj.wait方法并不是可以随便调用的。它必须包含在对应的synchronized语句中,无论是wait还是notify都需要首先获得目标对象的一个监视器。
例如:
/**
* Created by niehongtao on 16/7/14.
*/
public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + "t1 start");
try {
System.out.println(System.currentTimeMillis() + "t1 wait for object");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + "t1 end");
}
}
}
public static class T2 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + "T2 start");
object.notify();
System.out.println(System.currentTimeMillis() + "T2 end");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
t1.start();
t2.start();
}
}
输出结果:
1468468648501t1 start
1468468648501t1 wait for object
1468468648502T2 start
1468468648502T2 end
1468468650507t1 end
这里T1执行wait之后,会释放这个监视器
T2执行notify之前也获得这个监视器,然后执行notify,尝试唤醒一个等待线程,唤醒了T1,T1被唤醒之后,要做的第一件事并不是执行后续代码,而是要尝试重新获得obj的监视器,而这个监视器此时T2还没释放掉,只有T2执行完Thread.sleep(2000);才会释放掉,所以T1必须等2s,才能得到这个监视器,当监视器顺利获得后,T1才可以真正意义上的继续执行。
##挂起和继续执行线程
suspend和resume也是被废弃的方法。
原因是suspend在导致线程暂停的时候,并不会释放资源。
##等待线程结束和谦让
###join
很多时候,一个线程的输入可能非常的依赖于另外一个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。jdk提供了join方法来实现这个功能:
public final void join() throws InterruptedException
public final synchronized void join(long millis)throws InterruptedException
第一个join方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。第二个方法给出了一个最大的等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为等不及了,而继续往下执行。
应为join的翻译,通常是加入的意思。在这里感觉也非常贴切。因为一个线程要加入另一个线程,那么最好的方法就是等着它一起走。
举一个简单的例子:
/**
* Created by niehongtao on 16/7/14.
*/
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread {
@Override
public void run() {
for (i = 0; i < 10000000; i++);
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
at.join();
System.out.println(i);
}
}
结果:
10000000
主函数中,如果不使用join等待AddThread,那么得到的i很可能是0或者一个非常小的数字。因为AddThread还没执行完,i的值就已经输出了。但在使用了join方法后,表示主线程愿意等待AddThread执行完毕,跟着AddThread一起往前走,故在join返回时,AddThread已经执行完毕,故i总是10000000。
###yield
这个方法是一个静态方法,一旦执行,它会让当前线程让出cpu后,还会进程cpu的正度,但是是否能够再次被分配到,就不一定了。具体调用:Thread.yield().
#线程组
用得很少,不讲
#守护线程
用得少,不讲
#线程优先级
直接调用setPriority来给线程设置优先级。