java并发包学习系列:java并行基础

来源:互联网 发布:椰族部落永久域名入口 编辑:程序博客网 时间:2024/05/01 22:59

进程和线程

  进程:这里不讲枯燥的概念,举一个例子:你在windows系统中,看到后缀为.exe的文件,都是一个程序。不过程序是死的,静态的。当你双击这个.exe执行的时候,这个.exe文件的指令就会被加载,那么你就能得到一个有关这个.exe程序的一个进程。进程是活的,或者说是正在被执行的。

  线程:进程中可以容纳若干个线程,他们并不是看不见、摸不着的。也可以通过工具看到他们。用稍微专业点的术语说,线程就是轻量级的进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调用的成本远远小于进程。

一个线程的生命周期:

  new状态表示刚刚创建的线程,这种线程还没开始执行。等到线程的start方法调用时,才表示线程开始执行。当线程执行时,处于runnable状态,表示线程所需的一切资源都经准备好了。如果线程在执行过程中遇到了synchronized同步块,就会进入blockeduserid状态,这时线程就会暂停执行,知道获得请求的锁。waiting和timed-waiting都表示等待的状态,他们的区别是waiting会进入一个无时间限制的等待,time-waiting会进行一个有时限的等待,那等待的线程究竟在等待什么呢?一般来说,waiting的线程正是在等待一些特殊的事件。比如,通过wait方法等待的线程在等待notify方法,而通过join方法等待的线程则会等待目标线程的终止。一旦等到了期望的事件,线程就会再次执行,进入runnable状态。当线程执行完后,则进入terminated状态,表示结束。

初始线程:线程的基本操作

新建线程

java中如何新建一个线程。有两种方案:

1.

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 start1468468648501t1 wait for object1468468648502T2 start1468468648502T2 end1468468650507t1 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 InterruptedExceptionpublic 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来给线程设置优先级。

0 0