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线程。
先看代码的时序图:
详见下面代码。
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]
- 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 ↩
- 【Java多线程】多线程死锁
- Java 多线程
- java 多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA 多线程
- Java多线程
- java多线程
- JAVA 多线程
- Java 多线程
- Java 多线程
- java多线程
- Java 多线程
- Java多线程
- java 多线程
- scrapy中解决中文乱码问题
- poj2255Tree Recovery(二叉树,递归)
- Java的动态代理(dynamic proxy)
- 获取文档坐标和视口坐标的函数
- 进程等待wait,waitpid
- java多线程
- 限制textarea文本框字数的两种方法
- Codeforces Round #381 (Div. 2) A B C
- Python函数参数传递:传值还是传引用
- SparkSQLDemo初尝--SparkSession链接数据库
- MySQL 记录
- 一张白纸的开始,基础学习之命令符汇集笔记
- 【我的Android进阶之旅】Android使用getIdentifier()方法根据资源名来获取资源id
- 3. Longest Substring Without Repeating Characters, leetcode