Java多线程编程核心技术---Java多线程技能

2017-01-11
基本概念

进程是操作系统结构的基础,是一次程序的执行,是一个程序及其数据结构在处理机上顺序执行时所发生的活动,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的独立单位。线程可以理解成是在进程中独立运行的子任务。

继承Thread类实现多线程
public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println("MyThread...");
    }
    
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("运行结束..");
    }
}

运行结果如下

运行结束..
MyThread...

可见,使用多线程编程时,代码的运行结果与代码执行顺序或调用顺序是无关的。


下面的程序演示了线程的随机性

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                int time =  (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("run=" + Thread.currentThread().getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.setName("MyThread");
            thread.start();
            for (int i = 0; i < 10; i++) {
                int time =  (int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("main=" + Thread.currentThread().getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

某一次的执行结果如下:

run=MyThread
run=MyThread
main=main
run=MyThread
run=MyThread
run=MyThread
main=main
main=main
main=main
run=MyThread
main=main
run=MyThread
main=main
run=MyThread
main=main
run=MyThread
run=MyThread
main=main
main=main
main=main

Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程的run()方法。这个过程其实就是让系统安排一个时间来调用Thread类中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用thread.run()就是同步执行,此线程对象由main主线程来调用run()方法,也就是必须等待run()方法中的代码执行完毕后才可以执行后面的代码。

  • 注:执行start()方法的顺序不代表线程启动的顺序。测试代码如下:

    public class MyThread extends Thread {
    private int i;
    public MyThread(int i) {
        super();
        this.i = i;
    }
    
    @Override
    public void run() {
        System.out.println(i);
    }
    
    public static void main(String[] args) {
        MyThread t1 = new MyThread(1);
        MyThread t2 = new MyThread(2);
        MyThread t3 = new MyThread(3);
        MyThread t4 = new MyThread(4);
        MyThread t5 = new MyThread(5);
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
    }

    某一次的执行结果如下:

    2
    1
    5
    3
    4

    实现Runnable接口

    如果创建的线程类已经有一个父类了,就不能再集成Thread类,因为Java不支持多继承,这时就需要实现Runnable接口。

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("MyRunnable is running...");
    }
    
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("运行结束..");
    }
}

打印结果如下:

运行结束..
MyRunnable is running...

构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样可以将一个Thread对象中的run()方法交给其他的线程进行调用。


实例变量与线程安全
  • 不共享数据的情况
public class MyThread3 extends Thread {
    private int count = 5;
    public MyThread3(String name){
        super();
        this.setName(name);
    }
    
    @Override
    public void run() {
        super.run();
        while (count > 0) {
            count--;
            System.out.println("执行者:" + this.currentThread().getName() + ",count=" + count);
        }
    }
    
    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3("A");
        MyThread3 t2 = new MyThread3("B");
        MyThread3 t3 = new MyThread3("C");
        t1.start();
        t2.start();
        t3.start();
    }
}

某一次的执行结果如下:

执行者:B,count=4
执行者:B,count=3
执行者:B,count=2
执行者:C,count=4
执行者:A,count=4
执行者:A,count=3
执行者:C,count=3
执行者:C,count=2
执行者:B,count=1
执行者:B,count=0
执行者:C,count=1
执行者:A,count=2
执行者:A,count=1
执行者:A,count=0
执行者:C,count=0
  • 共享数据的情况
public class MyThread4 extends Thread {
    private int count = 5;

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("执行者:" + Thread.currentThread().getName() + ",count=" + count);
    }

    public static void main(String[] args) {
        MyThread4 myThread = new MyThread4();
        Thread t1 = new Thread(myThread, "A");
        Thread t2 = new Thread(myThread, "B");
        Thread t3 = new Thread(myThread, "C");
        Thread t4 = new Thread(myThread, "D");
        Thread t5 = new Thread(myThread, "E");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

某一次的运行结果如下:

执行者:A,count=3
执行者:D,count=1
执行者:E,count=2
执行者:B,count=3
执行者:C,count=0

由结果可以看出,虽然在main中t1-t5是依次调用start()方法的,但实际却不是依次执行的。在某些JVM中,i--分成如下三步:

  1. 取得原有的i值
  2. 计算i-1
  3. 对i进行赋值

在这三个步骤中,如果有多个线程同时访问,那么一定会出现非线程安全问题。通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。一个线程调用run()前,先判断run()方法有没有被上锁。如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run()方法的调用结束之后才可以执行run()方法。这样就实现了排队调用run()方法的目的,也就达到了按顺序对count变量减1的效果了。


i--与System.out.println()的异常

解决非线程安全问题使用的是synchronized关键字。首先看println()方法的源码:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

但是有可能出现另外一种异常。测试代码如下:

public class MyThread extends Thread {
    private int i = 5;
    @Override
    public void run() {
        System.out.println("i=" + (i--) + ", ThreadName" + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        Thread t1 = new Thread(thread);
        Thread t2 = new Thread(thread);
        Thread t3 = new Thread(thread);
        Thread t4 = new Thread(thread);
        Thread t5 = new Thread(thread);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

某一次执行的结果如下:

i=5, ThreadNameThread-1
i=4, ThreadNameThread-2
i=5, ThreadNameThread-3
i=3, ThreadNameThread-4
i=2, ThreadNameThread-5

由此可见,虽然println()方法在内部是同步的,但是i--操作确实在进入println()之前发生的,所以有可能发生非线程安全问题。


currentThread()方法

currentThread()方法可以返回代码段正在被哪个线程调用,测试代码如下:

public class CountOperate extends Thread {

    public CountOperate(){
        System.out.println("CountOperate begin...");
        System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());//main
        System.out.println("this.getName()=" + this.getName());//Thread-0
        System.out.println("CountOperate end...");
    }
    
    @Override
    public void run() {
        System.out.println("run begin...");
        System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());//test
        System.out.println("this.getName()=" + this.getName());//Thread-0
        System.out.println("run end...");
    }
    
    public static void main(String[] args) {
        CountOperate countOperate = new CountOperate();
        Thread thread = new Thread(countOperate);
        thread.setName("test");
        thread.start();
    }
}

执行结果如下:

CountOperate begin...
Thread.currentThread().getName()=main
this.getName()=Thread-0
CountOperate end...
run begin...
Thread.currentThread().getName()=test
this.getName()=Thread-0
run end...

isAlive()方法

isAlive()方法可以判断当前的线程是否处于活动状态。活动状态是指线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是"存活"的。

public class MyThread6 extends Thread {

    @Override
    public void run() {
        System.out.println("run=" + this.isAlive());
    }
    
    public static void main(String[] args) {
        MyThread6 thread = new MyThread6();
        System.out.println("begin=" + thread.isAlive());
        thread.start();
        System.out.println("end=" + thread.isAlive());
    }
}

执行结果如下:

begin=false
end=true
run=true

上面的结果中的end=true是因为thread没有执行完毕。如果做一下更改,则变为end=false

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("run=" + this.isAlive());
    }
    
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        System.out.println("begin=" + thread.isAlive());
        thread.start();
        Thread.sleep(1000);
        System.out.println("end=" + thread.isAlive());//执行到此处时,thread已结束
    }
}

结果如下:

begin=false
run=true
end=false

sleep()方法

sleep()方法可以在指定的毫秒数内让this.currentThread()返回的线程休眠(暂停执行)。

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            System.out.println("run threadName=" + this.currentThread().getName() + " begin");
            Thread.sleep(1000);
            System.out.println("run threadName=" + this.currentThread().getName() + " end");
        } catch (Exception e) {
            e.getStackTrace();
        }
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        System.out.println("begin time=" + System.currentTimeMillis());
        thread.run();//同步执行
        System.out.println("end time=" + System.currentTimeMillis());
    }
}

执行结果:

begin time=1463126357174
run threadName=main begin
run threadName=main end
end time=1463126358175

getId()方法

getId()方法可以去的线程的唯一标识。

public class MyThread8 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "-" + thread.getId());
    }
}

运行结果如下:

main-1

停止线程

停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前的操作。停止一个线程可以使用Thread.stop()方法,但最好不用这个方法,虽然这个方法可以停止一个正在运行的线程,但是这个方法是不安全的(unsafe),而且是已经作废的(depracated),在将来的Java版本中,这个方法将不可用或不被支持。

大多数停止一个线程的操作使用Thread.interrupt()方法,这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

Java中有三种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是run方法完成后线程终止。
  2. 使用stop方法强行终止线程,但是不推荐使用这个方法。因为stop和suspend及resume一样,都是作废过期的方法,使用它们将会产生不可预料的后果。
  3. 使用interrupt方法中断线程。

停止不了的线程

调用interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 500000; i++) {
            System.out.println("i = " + i);
        }
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

控制台输出如下:

.......
i = 499990
i = 499991
i = 499992
i = 499993
i = 499994
i = 499995
i = 499996
i = 499997
i = 499998
i = 499999

从运行结果来看,调用interrupt方法并没有停止线程。


判断线程是否是停止状态

Thread类里提供了两种方法

this.interrupted();//测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程
this.isInterrupted();//测试线程是否已经中断
public class MyThread {
    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());//true
        //线程的中断状态由interrupted()方法清除。
        System.out.println(Thread.interrupted());//false
    }
}
public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        System.out.println("run end");
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            System.err.println("是否停止0?" + thread.getName() + "-" + thread.isInterrupted());
            thread.interrupt();
            System.err.println("是否停止4?" + thread.getName() + "-" + thread.isInterrupted());
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("main end");
    }
}

以上测试代码打印结果如下:

run end
是否停止0?Thread-0-false
main end
是否停止4?Thread-0-true

能停止的线程---异常法

可以在线程中用for语句来判断线程是否被interrupt,如果线程被interrupt,则后面的代码不再运行。

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 50000; i++) {
            if (this.interrupted()) {
                System.out.println("已经是停止状态了,我要退出了...");
                break;
            }
            System.out.println("i=" + i);
        }
        System.out.println("我是for循环后面的代码...");
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("main end...");
    }
}

运行结果如下:

...
i=41707
i=41708
i=41709
i=41710
i=41711
i=41712
已经是停止状态了,我要退出了...
我是for循环后面的代码...
main end...

可见以上代码中只是跳出了for循环,但是for循环后面的代码会继续执行。如果不要继续执行for循环后面的代码,可以在for循环中抛出一个异常。测试代码如下:

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            for (int i = 0; i < 50000; i++) {
                if (this.interrupted()) {
                    System.out.println("已经是停止状态了,我要退出了...");
                    throw new InterruptedException();
                }
                System.out.println("i=" + i);
            }
            System.out.println("我是for循环后面的代码...");
        } catch (InterruptedException e) {
            System.out.println("捕捉到了InterruptedException异常...");
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("main end...");
    }
}

运行结果如下:

...
i=42816
i=42817
i=42818
i=42819
i=42820
i=42821
i=42822
i=42823
i=42824
已经是停止状态了,我要退出了...
捕捉到了InterruptedException异常...
main end...
java.lang.InterruptedException
    at com.umgsai.thread.MyThread13.run(MyThread13.java:12)

在沉睡中停止
public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            System.out.println("run begin...");
            Thread.sleep(20000);
            System.out.println("run end...");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止......" + this.isInterrupted());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("main end...");
    }
}

运行结果如下:

run begin...
main end...
在沉睡中被停止......false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.umgsai.thread.MyThread13.run(MyThread13.java:10)

如果在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态,使之变成false。再看一下代码:

package com.umgsai.thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            System.out.println("for begin");
            for (int i = 0; i < 100000; i++) {
                System.out.println("i = " + i);
            }
            System.out.println("for end");
            System.out.println("sleep begin...");
            Thread.sleep(20000);
            System.out.println("sleep end...");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止......" + this.isInterrupted());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        System.err.println("main begin...");
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("main end...");
    }
}

运行结果如下:

...
i = 99990
i = 99991
i = 99992
i = 99993
i = 99994
i = 99995
i = 99996
i = 99997
i = 99998
i = 99999
for end
sleep begin...
在沉睡中被停止......false
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.umgsai.thread.MyThread13.run(MyThread13.java:15)

能停止的线程---暴力停止
public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        try {
            System.out.println("for begin");
            for (int i = 0; i < 100000; i++) {
                System.out.println("i = " + i);
            }
            System.out.println("for end");
            System.out.println("sleep begin...");
            Thread.sleep(20000);
            System.out.println("sleep end...");
        } catch (Exception e) {
            System.out.println("被停止......" + this.isInterrupted());
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        System.err.println("main begin...");
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(10);
            thread.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.err.println("main end...");
    }
}

运行结果如下:

main begin...
for begin
i = 0
i = 1
i = 2
i = 3
i = 4
...
i = 746
i = 747
i = 748
i = 749
i = 750
i = 751
main end...

可见使用stop()方法停止线程是非常暴力的。


方法stop()与java.lang.ThreadDeath异常

调用stop()方法时会抛出java.lang.ThreadDeath异常,但是在通常的情况下,不需要显式捕捉此异常。

public class MyThread extends Thread { 

    @Override
    public void run() {
        try {
            this.stop();
        } catch (ThreadDeath e) {
            System.out.println("捕捉到异常...");
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

运行结果如下:

捕捉到异常...
java.lang.ThreadDeath
    at java.lang.Thread.stop(Unknown Source)
    at com.umgsai.thread.MyThread14.run(MyThread14.java:8)

释放锁的不良后果

使用stop()方法释放锁会给数据造成不一致的结果。测试代码如下:

public class User {

    private String username = "a";
    private String password = "aaa";
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
    synchronized public void printString(String username, String password){
        try {
            this.username = username;
            Thread.sleep(20000);
            this.password = password;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class UserThread extends Thread {

    private User user;
    public UserThread(User user){
        super();
        this.user = user;
    }
    
    @Override
    public void run() {
        user.printString("b", "bbb");
    }
    
    public static void main(String[] args) {
        try {
            User user = new User();
            UserThread thread = new UserThread(user);
            thread.start();
            Thread.sleep(1000);
            thread.stop();//stop()方法强行释放锁
            System.out.println(user.getUsername() + "-" + user.getPassword());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果如下:

b-aaa

stop()方法已经在JDK中被标明是deprecated,显然此方法在功能上有缺陷,不建议在程序中使用。


使用return停止线程

结合使用interrupt()和return也能实现停止线程的效果。

public class MyThread extends Thread {

    @Override
    public void run() {
        while (true) {
            if (this.isInterrupted()) {
                System.out.println("interrupted..");
                return;
            }
            System.out.println("timer=" + System.currentTimeMillis());
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

运行结果如下:

...
timer=1463197371831
timer=1463197371831
timer=1463197371831
timer=1463197371831
timer=1463197371831
timer=1463197371831
interrupted..

不过还是建议使用“抛异常”的方法来停止线程,因为在catch块中可以将异常向外抛,使线程停止的事件得以传播。


暂停线程

暂停线程意味着此线程还可以恢复执行。在Java多线程中,可以使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。

public class MyThread extends Thread {
    private long i = 0;

    public long getI() {
        return i;
    }

    public void setI(long i) {
        this.i = i;
    }

    @Override
    public void run() {
        while (true) {
            i++;
        }
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            System.out.println("Main thread started, time=" + System.currentTimeMillis() + ", i=" + thread.getI());//1463272236875 1799
            Thread.sleep(2000);
            
            thread.suspend();//暂停
            System.out.println("A suspended, time=" + System.currentTimeMillis() + ", i=" + thread.getI());//1463272238875  1120598443
            Thread.sleep(2000);
            System.out.println("A suspended, time=" + System.currentTimeMillis() + ", i=" + thread.getI());//1463272240876  1120598443
            
            thread.resume();//恢复执行
            System.out.println("A resumed, time=" + System.currentTimeMillis() + ", i=" + thread.getI());//1463272240876  1120627110
            Thread.sleep(2000);
            
            thread.suspend();//暂停
            System.out.println("B suspended, time=" + System.currentTimeMillis() + ", i=" + thread.getI());//1463272242876  2221648001
            Thread.sleep(2000);
            System.out.println("B suspended, time=" + System.currentTimeMillis() + ", i=" + thread.getI());//1463272244876   2221648001
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

Main thread started, time=1463272236875, i=1799
A suspended, time=1463272238875, i=1120598443
A suspended, time=1463272240876, i=1120598443
A resumed, time=1463272240876, i=1120627110
B suspended, time=1463272242876, i=2221648001
B suspended, time=1463272244876, i=2221648001

在使用suspend与resume方法时,如果使用不当,极易造成公共的同步对象的独占,使其他线程无法访问公共的同步线程。

public class MyThread extends Thread {

    synchronized public void print(){
        System.out.println("begin...");
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("a thread suspended forever...");
            Thread.currentThread().suspend();
        }
        System.out.println("end");
    }
    
    public static void main(String[] args) {
        try {
            final MyThread thread = new MyThread1);
            Thread t1 = new Thread(){
                @Override
                public void run() {
                    thread.print();
                };
            };
            t1.setName("a");
            t1.start();
            Thread.sleep(1000);
            Thread t2 = new Thread(){
                @Override
                public void run() {
                    System.out.println("t2启动了,但无法进入print方法..");
                    thread.print();
                }
            };
            t2.start();
//          t1.resume();//如果不恢复t1,则t2无法进入print方法
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

begin...
a thread suspended forever...
t2启动了,但无法进入print方法..

打印出以上结果之后,程序不停止运行,始终处于阻塞状态。只有恢复t1使其继续执行,t2才有机会拿到对象锁从而执行print方法。

还有一种独占锁的情况需要注意。

public class MyThread extends Thread {

    private long i = 0;
    @Override
    public void run() {
        System.out.println("thread started...");
        while (true) {
            i++;
        }
    }
    
    public static void main(String[] args) {
        try {
            System.out.println("main started...");
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.suspend();
            System.out.println("main end...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

main started...
thread started...
main end...

如果在main线程里暂停了thread,当main线程执行结束之后,thread就再也不会有机会恢复执行了。

对以上代码做如下修改:

package com.umgsai.thread;

public class MyThread extends Thread {

    private long i = 0;
    @Override
    public void run() {
        System.out.println("thread started...");
        while (true) {
            i++;
            System.out.println(i);
        }
    }
    
    public static void main(String[] args) {
        try {
            System.out.println("main started...");
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(10);
            thread.suspend();
            System.out.println("main end...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

main started...
thread started...
1
2
3
4
5
...
800
801
802
803
804
805

此后thread被暂停,但是mian线程也被阻塞,因为System.out.println内部是同步的,代码如下:

public void println(long x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

thread被暂停,是的main线程被阻塞。

suspend与resume方法的缺点---不同步
public class MyThread extends Thread {

    private String username = "a";
    private String password = "aaa";
    public void setValue(String username, String password) {
        this.username = username;
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("停止a线程");
            Thread.currentThread().suspend();
        }
        this.password = password;
    }
    
    public void print() {
        System.out.println(username + "-" + password);
    }
    
    public static void main(String[] args) throws InterruptedException {
        final MyThread thread = new MyThread();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                thread.setValue("b", "bbb");
            }
        };
        t1.setName("a");
        t1.start();
        Thread.sleep(500);
        
        Thread t2 = new Thread(){
            @Override
            public void run() {
                thread.print();
            }
        };
        t2.start();
    }
}

打印结果如下:

停止a线程
b-aaa

程序的运行结果出现值不同步的情况,所以在程序中使用suspend()方法时要格外注意。


yield()方法

yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。

package com.umgsai.thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 500000000; i++) {
            Thread.yield();
            count = count + i;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用时:" + (endTime - beginTime) + "ms");
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

以上代码打印结果如下:

用时:32176ms

注释掉Thread.yield()后,打印结果如下:

用时:1ms

(what?!!!才1ms感觉太假了有木有,机器CPU是i5-4590)


线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也即是CPU优先处理优先级较高的线程对象中的任务。JDK源码中相关代码如下:


/**
 * 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;

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

Java中线程的优先级分为1-10这10个等级。


线程优先级的继承特性

Java中线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A线程是一样的。

public class MyThread21 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread21 priority:" + this.getPriority());//6
    }
}
public class MyThread22 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread22 priority:" + this.getPriority());//6
        MyThread21 t21 = new MyThread21();
        t21.start();
    }
    
    public static void main(String[] args) {
        System.out.println("Main begin Mypriority:" + Thread.currentThread().getPriority());//5
        Thread.currentThread().setPriority(6);
        System.out.println("Main end Mypriority:" + Thread.currentThread().getPriority());//6
        MyThread22 t22 = new MyThread22();
        t22.start();
    }
}

打印结果如下:

Main begin Mypriority:5
Main end Mypriority:6
MyThread22 priority:6
MyThread21 priority:6

优先级具有规则性

setPriority()可以设置线程的优先级,操作系统会==尽量==给优先级高的线程分配资源。

public class MyThread1 extends Thread {
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        long result = 0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j< 50000; j++) {
                Random random = new Random();
                random.nextInt();
                result = result + j;
            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println("☆☆☆☆☆-" + (endTime - beginTime));
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        long result = 0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j< 50000; j++) {
                Random random = new Random();
                random.nextInt();
                result = result + j;
            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println("★★★★★-" + (endTime - beginTime));
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 30; i++) {
            MyThread1 t1 = new MyThread1();
            t1.setPriority(10);
            t1.start();
            
            MyThread2 t2 = new MyThread2();
            t2.setPriority(1);
            t2.start();
        }
    }
}

部分打印结果如下:

//....前面全部是线程1打印的结果
☆☆☆☆☆-136
★★★★★-1035
★★★★★-1077
☆☆☆☆☆-564
☆☆☆☆☆-564
☆☆☆☆☆-572
☆☆☆☆☆-359
☆☆☆☆☆-452
★★★★★-1013
☆☆☆☆☆-489
☆☆☆☆☆-505
★★★★★-1344
★★★★★-1087
★★★★★-1572
★★★★★-1574
★★★★★-1293
★★★★★-975
★★★★★-1285
★★★★★-1593
☆☆☆☆☆-865
★★★★★-1843
★★★★★-1198
//....后面全部是线程2打印的结果

可见操作系统并不保证优先级高的线程一定比优先级低的线程先执行,CPU==尽量==将资源分配给优先级高的线程。

在以上代码中,若将线程1的优先级设置成6,将线程2的优先级设置成5,则在打印结果中几乎看不出哪个线程的优先级比较高。线程的优先级和打印顺序无关,他们的关系具有随机性和不确定性。


守护线程

在Java线程中有两种线程,一种是用户线程,一种是守护线程。

守护线程是一种特殊的线程,当进程中不存在非守护线程时,守护线程会自动销毁。典型的守护线程是垃圾回收线程,当进程中没有非守护线程时,则垃圾回收线程也没有寻在的必要了。

public class MyThread extends Thread {
    private int i = 0;
    @Override
    public void run() {
        try {
            while (true) {
                i++;
                System.out.println("i=" + i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.setDaemon(true);
            thread.start();
            Thread.sleep(5000);
            System.out.println("exit");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

i=1
i=2
i=3
i=4
i=5
exit



顶部