java多线程

来源:互联网 发布:王凯歆私生活知乎 编辑:程序博客网 时间:2024/05/16 14:46

    • 一 Thread和Runnable
    • 二 Wait和notify
    • 三 过时的suspend和resume
      • 3-1 为什么suspend和resume这对方法会过时呢
      • 3-2 使用什么方法代替suspend和resume呢
    • 四 过时的stop和替代方法
      • 4-1 为什么Threadstop方法会过时呢
      • 4-2 用什么方法代替Threadstop呢
    • 五 过时的Threaddestroy
    • 六 一个不建议使用的类ThreadGroup

一、 Thread和Runnable

先举一个例子,然后再解释:

public class MyThread{    static class RunnableTest implements Runnable{        @Override        public void run() {            System.out.println("run something");        }    }    static class ThreadTest extends Thread{        @Override        public void run(){            System.out.println("run something");        }    }    public static void main(String[] args) {        Thread thread1 = new Thread(new MyThread.RunnableTest());        thread1.start();        MyThread.ThreadTest thread2 = new MyThread.ThreadTest();        thread2.start();    }}

上面的代码很简单,只是创建两个线程,然后分别执行这两个线程中的run方法。

java.lang.Thread是实现Runnable接口的类。

Runnable只有一个方法:

public abstract void run();

Thread中的run方法的实现为:

@Overridepublic void run() {    if (target != null) {        target.run();    }}

变量target的定义为:

 /* What will be run. */ private Runnable target;

其中target的初始化如下代码,是通过Thread构造方法传入并初始化的。这里涉及到了“向上转型”,因为我们传进去的Runnable类型的参数是Runnable实现类的对象的句柄。

public Thread(Runnable target) {        init(null, target, "Thread-" + nextThreadNum(), 0);}

初始化之后,我们会调用Thread类的start()方法,然后run方法就会被执行。从run方法中我们可以看到,它先判断target变量是否为空,如果不为空表示我们是使用Runnable构造Thread对象的,那么就需要运行Runnable实现类中的run方法。如果target变量为空,那就会运行该thread类中覆写(override)的run的方法(这里涉及到了面向对象的多态性)。如果target变量为空,又没有覆写run方法,那就相当于执行上面代码中的run方法。

start()方法最终会调用下面所示定义的本地方法。

private native void start0();

二 Wait和notify

Wait方法跟java.lang.Thread.sleep()方法类似,作用于使当前线程状态变成阻塞状态。通过某一对象的notify方法唤醒阻塞于该对象上的某一个线程。wait和notify作为超父类java.lang.Object中的方法,它们针对的是某一个对象,而不在单单局限于“线程”。我们可以拿Thread.sleep()方法来做对比:sleep方法是java.lang.Thread类的静态方法,只能通过Thread.sleep()来调用,所以它针对的是“线程”,而对于wait和notify,任何对象默认都有这两个方法,所以,它们针对的是所有的对象。可以把“这些对象”理解为资源,在多线程的环境下可以理解为被多个线程抢占的资源。既然这些对象作为被抢占的资源,那么在使用这些资源之前就需要先获得同步锁,否则很可能出现一致性问题,所以Java语言强制我们在调用wait或者notify方法之前进行代码同步,比如使用synchronized关键字。如果不同步,在程序执行的时候就是抛出IllegalMonitorStateException异常。可见wait和notify所面向的范围更加宽广,设计者所站的视角更加高远。

下面是wait和notify方法的使用实例:我们定义了一个资源,名字叫source,是java.lang.Integer类型。在使用这个资源之前必须要获得它的同步锁。所以你会看到source.wait()和source.notify()方法都在同步块中,并且被同步的对象是source,也就是一次只允许一个线程占用source这个对象。main线程先获得source对象的同步锁,然后调用source.wait()使自己处于阻塞状态,同时自动释放对source资源的占用。另一个线程获得source对象的同步锁,睡眠3秒钟之后调用source.notify()唤醒main线程。

先看代码的时序图:

Created with Raphaël 2.1.0mainThreadmainThreadcommonThreadcommonThreadsourcesourcestart and sleep 3s.ok, I'll sleep 3s.have your lock.ok ok.I will wait until notified.have your lock, and nofity mainThread.ok ok.awaked.

详见下面代码。

public class ThreadTest {    static Integer source = 1;    public static void main(String[] args) throws InterruptedException {        MyThread newThread = new MyThread();        newThread.start();        synchronized (source){            System.out.println("main thread wait");            source.wait();        }        System.out.println("main thread end");    }}class MyThread extends Thread{    @Override    public void run() {        System.out.println("MyThread is running");        try {            Thread.sleep(3000);            System.out.println("After 3 second");        } catch (InterruptedException e) {            e.printStackTrace();        }        synchronized (ThreadTest.source){            System.out.println("notify(wake up) main thread");            ThreadTest.source.notify();        }    }}

执行结果为:

main thread waitMyThread is runningAfter 3 secondnotify(wake up) main threadmain thread end

既然wait()和notify()方法针对于每一个对象,这些对象都可以作为被多个线程抢占的资源,线程类Thread有自己的对象,所以它也是资源。下面的例子把线程Thread类的对象newThread作为多线程同步的资源,调用它自己的wait方法使本线程处于阻塞状态,然后在main线程中调用newThread的notify方法唤醒该线程,使它处于就绪状态,执行完成程序。详见下面代码。

public class ThreadTest {    public static void main(String[] args) throws InterruptedException {        MyThread newThread = new MyThread();        newThread.start();        synchronized (newThread){            System.out.println("newThread wait");            newThread.wait();        }        System.out.println("main thread sleep");        Thread.sleep(3000);        synchronized (newThread){            System.out.println("newThread notify");            newThread.notify();        }    }}class MyThread extends Thread{    @Override    public void run() {        System.out.println("run");    }}

执行结果为:立即打印出下面所示的前三行,等待三秒中之后打印第四行。

newThread waitrunmain thread sleepnewThread notify

下面的程序是为了演示在调用wait和notify方法之前没有进行同步锁的情况。下面是正常的程序:

public class ThreadTest {    public static void main(String[] args) throws InterruptedException {        MyThread thread = new MyThread();        thread.start();        Thread.sleep(3000);        synchronized (thread){            thread.notify();        }    }}class MyThread extends Thread{    @Override    public synchronized void run() {        try {            System.out.println("before wait");            wait();            System.out.println("after wait");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

执行结果:运行程序之后,立即打印before wait,三秒之后打印after wait。

before waitafter wait

如果去掉main函数中的synchronized,或者去掉run方法中的synchronized,变成下面结果所示,那么执行结果会抛出一个IllegalMonitorStateException异常,具体解释详见上文:
修改的代码:

thread.notify();//去掉synchronized,即没有同步

或者

class MyThread extends Thread{    @Override    public void run() {//去掉synchronized,即没有同步        try {            System.out.println("before wait");            wait();            System.out.println("after wait");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

执行结果为:

before waitException in thread "main" java.lang.IllegalMonitorStateException    at java.lang.Object.notify(Native Method)    at com.huai.test.ThreadTest.main(ThreadTest.java:13)

或者

Exception in thread "Thread-0" java.lang.IllegalMonitorStateExceptionbefore wait    at java.lang.Object.wait(Native Method)    at java.lang.Object.wait(Object.java:502)    at com.huai.test.MyThread.run(ThreadTest.java:26)

上面便是wait和notify的用法和注意事项,对于notifyAll方法,用法和notify类似,notify会唤醒正在阻塞于某一对象的某一个线程,而notifyAll会唤醒正在阻塞于某一对象的所有线程。


三、 过时的suspend和resume

3-1 为什么suspend和resume这对方法会过时呢?

Thread.suspend()方法天生容易造成死锁。调用线程TA的suspend()方法之后,该线程并不会释放之前同步锁,会一直占有这个资源S。如果另外一个线程TB在调用TA线程的resume()方法之前需要先获得资源S的同步锁,那么这时候就会发生死锁。因为线程TA一直占有资源S,线程TB申请却得不到资源S,线程TB在得到资源S之前又不会调用TA的resume方法。僵局无法打破。[^deprecation]

3-2 使用什么方法代替suspend和resume呢?

我们使用wait和notify方法来代替它们。

举一个例子:我们分别使用suspend和resume、wait和notify实现一个相同的阻塞/唤醒演示代码。

下面是suspend和resume:

class Suspender extends Thread{    public Suspender(){        start();    }    public void run(){        while(true){            doSomething();            suspend();        }    }    public synchronized void doSomething(){        System.out.println("doing something");    }}class Resumer extends Thread{    private Suspender suspender;    public Resumer(Suspender suspender){        this.suspender = suspender;        start();    }    public void run(){        while(true){            try {                sleep(2000);                this.suspender.resume();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

主函数:

public static void main(String args[]){        Suspender suspender = new Suspender();        Resumer resumer = new Resumer(suspender);}

执行结果:

doing somethingdoing somethingdoing somethingdoing something

下面是wait和notify:

class Blockable extends Thread{    public Blockable(){        start();    }    public synchronized void run(){        while(true){            doSomething();            try{                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public synchronized void doSomething(){        System.out.println("doing something");    }}class Notifier extends Thread{    private Blockable blockable;    public Notifier(Blockable blockable){        this.blockable = blockable;        start();    }    public void run(){        while(true){            try{                sleep(2000);            } catch (InterruptedException e) {                e.printStackTrace();            }            synchronized (blockable){                blockable.notify();            }        }    }}
public static void main(String args[]){        Blockable blockable = new Blockable();        Notifier notifier = new Notifier(blockable);}

执行结果是一样的。


四、 过时的stop()和替代方法

4-1 为什么Thread.stop方法会过时呢?

因为它天生就是不安全的。举一个栗子:当线程A正在锁住某一个资源(或者说对象)进行相关运算工作,工作只进行到一半,突然线程B调用了线程A的stop方法,不管线程A是否完成,不管三七二十一线程A必须立即停止当前的事儿,并释放并发锁。这样就会导致被上锁的资源可能失去一致性。这样的错误有时候不会立即就会反映出来,而是在将来的某个时间点出现。所以jdk使这个方法过时

4-2 用什么方法代替Thread.stop()呢?

简单来讲,我们可以设置一个标记字段,然后把它作为while循环中的条件,通过改变这个标志字段来决定是否停止这个线程。下面我们使用两段代码来做对比。具体如下面代码所示:

  • 如果使用Thread.stop(),先运行一个新的线程,然后再“停止”。

    public class ThreadTest {  public static void main(String[] args) throws InterruptedException {      Task task = new Task();      Thread.sleep(3000);      task.stopTask();  }}class Task extends Thread{  public Task(){      start();  }  public void stopTask(){      stop();  }  public void run(){      while(true){          System.out.println("do run");      }  }}

  • 现在我们不使用Thread.stop()方法,同样达到相同的运行结果,修改代码如下所示:

    public class ThreadTest {  public static void main(String[] args) throws InterruptedException {      Task task = new Task();      Thread.sleep(3000);      task.stopTask();  }}class Task extends Thread{  private boolean isContinue = true;  public Task(){      start();  }  public void stopTask(){      this.isContinue = false;  }  public void run(){      while(isContinue){          System.out.println("do run");      }  }}

例外情况: 如果我们已经设置了标志位isContinue = false;但是线程却因为某种阻塞(比如在等待io读取、正在Thread.sleep()、正在Object.wait()、join())迟迟没有运行完(或者说退出)run方法呢?我们又不希望等待太长时间,怎么办?我们可以使用Thread.interrupt()方法。isContinue 标志位还是不可缺少的。

public class ThreadTest {    public static void main(String[] args) throws InterruptedException {        Task task = new Task();        Thread.sleep(1000);        task.stopTask();    }}class Task extends Thread{    private boolean isContinue = true;    public Task(){        start();    }    public void stopTask(){        isContinue = false;        super.interrupt();    }    public void run(){        try {            while(isContinue){                System.out.println("do run");                sleep(3000);                System.out.println("waked up");            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

执行结果为:

do runjava.lang.InterruptedException: sleep interrupted    at java.lang.Thread.sleep(Native Method)    at com.huai.test.Task.run(ThreadTest.java:31)

如果调用Thread.interrupt()之后还是不能停止该线程呢?是不是就需要用Thread.stop()? 我们需要记住:如果线程对Thread.interrupt()都没有反应,那么对Thread.stop()也不会有所反应。

如果当前线程不处在阻塞状态,那么调用Thread.interrupt()是没有用的。举个栗子:把下面代码run方法中的sleep(3000)注释掉,运行这段代码,结果为:程序一直运行下去不会自动退出。

五、 过时的Thread.destroy

Thread.destroy方法是过时的,其实这个方法从来都没有被实现过。如果它被实现了,那么它很容易造成死锁。为什么?从jdk的开发文档中了解到,这个方法最开始设计用来破坏一个线程并且不做任何清除工作,该线程之前所获得的资源锁是不会自动释放的。这样子就跟Thread.suspend一样,容易造成死锁。具体可以回头看前面的Thread.suspend。

六、 一个不建议使用的类–ThreadGroup

ThreadGroup是什麽?顾名思义,它是用来管理一组线程的类。具体有哪些作用呢?

  • 可以把逻辑上相同的线程组织在一起。便于我们进行问题的跟踪诊断和解决
  • 可以调用它的interrupt()方法中断所有在这个组中的线程。
  • 可以很便捷地为这个组中的线程设置最高的线程权限。
  • 可以这个组为后台线程,这个组中的所有线程相应地也就变成了后台线程。
  • 可以覆写UncaughtExceptionHandler中的方法,这样的话如果这个组中某一线程抛出异常,我们就可以得到一个回调,然后通过我们覆写的方法处理此异常。
  • ThreadGroup可以算是一个工具类,通过它可以轻松管理这些线程,因为它提供了一些有用的方法。比如可以知道有多少active的线程。

下面的程序参考了Thinking in java,随着jdk的更新,本人使用的jdk1.8,程序运行的结果和Thinking in java中的有几处不一样。所以还是以实际运行结果为准。通过观察下面程序的运行结果,我们可以得出以下结论:

  • parent group的优先级总等于或大于child group的。
  • thread的优先级不能变到大于该group的最大优先级。
  • group的优先级变小,并小于该组中某thread的优先级的时候,该thread的优先级不会自动变小。
  • 如果group的优先级小于默认优先级(默认为5),那么新线程的优先级依然等于该group的优先级。这一点和thinking in java不一样。

对于下面的程序和thinking in java有一点不一样,其中用到了自定义的Thread2Test类,该类的构造方法中有start()函数。如果没有调用这个start()函数的话,输出的结果没有线程的信息。1

public class ThreadGroupTest {    public static void main(String[] args) {        ThreadGroup sys = Thread.currentThread().getThreadGroup();        System.out.println("-------1--------");        sys.list();        sys.setMaxPriority(Thread.MAX_PRIORITY - 1);        Thread curr = Thread.currentThread();        curr.setPriority(curr.getPriority() + 1);        System.out.println("--------2-------------");        sys.list();        ThreadGroup g1 = new ThreadGroup("g1");        g1.setMaxPriority(Thread.MAX_PRIORITY);        Thread t = new Thread2Test(g1, "A");        t.setPriority(Thread.MAX_PRIORITY);        System.out.println("-------3--------");        g1.list();        g1.setMaxPriority(Thread.MAX_PRIORITY - 2);        System.out.println("-------4--------");        g1.list();        g1.setMaxPriority(Thread.MAX_PRIORITY);        System.out.println("--------5-------");        g1.list();        t = new Thread2Test(g1, "B");        t.setPriority(Thread.MAX_PRIORITY);        System.out.println("-------6--------");        g1.list();        g1.setMaxPriority(Thread.MIN_PRIORITY + 2);        t = new Thread2Test(g1, "C");        System.out.println("-------7--------");        g1.list();        t.setPriority(t.getPriority() - 1);        System.out.println("-------8--------");        g1.list();        ThreadGroup g2 = new ThreadGroup(g1, "g2");        System.out.println("--------9-------");        g2.list();        g2.setMaxPriority(Thread.MAX_PRIORITY);        System.out.println("--------10-------");        g2.list();        for(int i = 0; i < 5; i++){            new Thread2Test(g2, Integer.toString(i));        }        System.out.println("--------11-------");        sys.list();    }}class Thread2Test extends Thread{    Thread2Test(ThreadGroup g, String name){        super(g, name);        start();    }}

结果:

-------1--------java.lang.ThreadGroup[name=main,maxpri=10]    Thread[main,5,main]    Thread[Monitor Ctrl-Break,5,main]--------2-------------java.lang.ThreadGroup[name=main,maxpri=9]    Thread[main,6,main]    Thread[Monitor Ctrl-Break,5,main]-------3--------java.lang.ThreadGroup[name=g1,maxpri=9]    Thread[A,9,g1]-------4--------java.lang.ThreadGroup[name=g1,maxpri=8]    Thread[A,9,g1]--------5-------java.lang.ThreadGroup[name=g1,maxpri=9]    Thread[A,9,g1]-------6--------java.lang.ThreadGroup[name=g1,maxpri=9]    Thread[A,9,g1]    Thread[B,9,g1]-------7--------java.lang.ThreadGroup[name=g1,maxpri=3]    Thread[A,9,g1]    Thread[B,9,g1]    Thread[C,3,g1]-------8--------java.lang.ThreadGroup[name=g1,maxpri=3]    Thread[A,9,g1]    Thread[B,9,g1]    Thread[C,2,g1]--------9-------java.lang.ThreadGroup[name=g2,maxpri=3]--------10-------java.lang.ThreadGroup[name=g2,maxpri=3]--------11-------java.lang.ThreadGroup[name=main,maxpri=9]    Thread[main,6,main]    Thread[Monitor Ctrl-Break,5,main]    java.lang.ThreadGroup[name=g1,maxpri=3]        Thread[C,2,g1]        java.lang.ThreadGroup[name=g2,maxpri=3]            Thread[0,3,g2]            Thread[1,3,g2]            Thread[2,3,g2]            Thread[3,3,g2]            Thread[4,3,g2]

如果觉得上面的程序太长,难看,可以先运行下面的代码,熟悉一下ThreadGroup的API。参考自thinking in java

class TestThread1 extends Thread{    private int i;    TestThread1(ThreadGroup g, String name){        super(g, name);    }    void f(){        i++;        System.out.println(getName() + " f()");    }}class TestThread2 extends TestThread1{    TestThread2(ThreadGroup g, String name){        super(g, name);        start();    }    public void run(){        ThreadGroup g = getThreadGroup().getParent().getParent();        g.list();//打印信息,从当前节点开始往树的叶子节点打印该组信息。        Thread[] gAll = new Thread[g.activeCount()];        g.enumerate(gAll);//复制g的信息到数组gAll中        for(int i = 0; i < gAll.length; i++){            gAll[i].setPriority(Thread.MIN_PRIORITY);            ((TestThread1)gAll[i]).f();        }        g.list();    }}class TestThread3 extends TestThread1{    TestThread3(ThreadGroup g, String name){        super(g, name);        start();    }    @Override    public void run() {        try {            sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

main方法

public static void main(String[] args) {        ThreadGroup x = new ThreadGroup("x");        ThreadGroup y = new ThreadGroup(x, "y");        ThreadGroup z = new ThreadGroup(y, "z");        Thread one = new TestThread1(x, "one");        Thread two = new TestThread2(z, "two");        Thread three = new TestThread3(x, "three");}

输出的结果:

java.lang.ThreadGroup[name=x,maxpri=10]    Thread[three,5,x]    java.lang.ThreadGroup[name=y,maxpri=10]        java.lang.ThreadGroup[name=z,maxpri=10]            Thread[two,5,z]three f()two f()java.lang.ThreadGroup[name=x,maxpri=10]    Thread[three,1,x]    java.lang.ThreadGroup[name=y,maxpri=10]        java.lang.ThreadGroup[name=z,maxpri=10]            Thread[two,1,z]

但是,我们并不推荐使用ThreadGroup这个类,因为它是不安全的,举一个栗子:

Thread[] gAll = new Thread[g.activeCount()];group.enumerate(gAll);

当我们要使用enumerate方法的时候,需要传入一个数组,这个数组的长度通过activeCount() 方法返回的一个“估计”的值来确定,所谓“估计”,就是说有可能在activeCount()返回的一瞬间这个group中新增加了一个线程。如果是这种情况,那么在运行enumerate方法的时候就会出现异常,因为gAll申请的数组空间不够。在列举线程组的 subgroups的时候也会出现同样的问题。那么我们可以使用线程池来代替。可以参考参考jdk提供了一个工具类Executors。[^Effective_Java_2nd_Edition]


  1. Thinking in java
    [^ deprecation]: https://docs.oracle.com/javase/6/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html
    [^Effective_Java_2nd_Edition]: Effective Java 2nd Ed. Item 73.http://www.ebooksbucket.com/uploads/itprogramming/java/Effective_Java_2nd_Edition.pdf ↩
1 1
原创粉丝点击