1.多线程的执行顺序

先来看一个例子:

public class test {

	public static void main(String[] args) {
	    System.out.println(Thread.currentThread().getName());
	}
}

输出结果:

main

这个例子说明一个问题:一个Java程序至少会有1个线程在运行,就如上面的main()方法,它是由JVM创建的一个叫main的线程调用的。控制台输出的就是这个线程的名字。

接着再看下面这段代码:

public class MyThread extends Thread{

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println(Thread.currentThread().getName());
        System.out.println("运行结束!");
    }

    @Override
    public void run() {
        //super.run();
        System.out.println(Thread.currentThread().getName());

    }
}

输出结果:

main
运行结束!
Thread-0

或者是:

main
Thread-0
运行结束!

上段代码说明了两个问题:

  1. 上述代码有两个线程同时在运行:一个是JVM创建的main线程,该线程调用main函数,另一个是自己创建的MyThread线程类,名称是Thread-0,该线程调用run函数
  2. main()函数和run()函数中的打印结果顺序不确定。这是因为在使用多线程时,代码的运行结果与代码执行顺序或调用顺序是无关的。线程是一个子任务,上面代码有两个线程,意味着两个子任务各干各的事,互不干扰,一个线程正在调用main函数,另一个线程在这个时候去调用run函数,先打印哪个函数里面的东西都不一定。虽然main()这个函数比run()函数先调用,意思是时间起点早,但是具体到函数里面的语句调用顺序就说不清楚了。

再比如下面的例子:


public class Test3 {

    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3("thread1");
        MyThread3 t2 = new MyThread3("thread2");
        MyThread3 t3 = new MyThread3("thread3");
        MyThread3 t4 = new MyThread3("thread4");
        MyThread3 t5 = new MyThread3("thread5");
        System.out.println(Thread.currentThread().getName());
        t1.start();
        System.out.println("t1准备好:");
        t2.start();
        System.out.println("t2准备好");
        t3.start();
        System.out.println("t3准备好");
        t4.start();
        System.out.println("t4准备好");
        t5.start();
        System.out.println("t5准备好");

    }
}
public class MyThread3 extends Thread {

    private String name;
    public MyThread3(String name){
        super(name);
        this.name = name;
    }

    @Override
    public void run() {

        System.out.println("当前执行run方法的线程是:" + Thread.currentThread().getName());
        try {
            for(int j = 0; j < 5; j++){
                //int time = (int)(Math.random() * 1000);
                int time = 500;
                Thread.sleep(time);
                System.out.println("j=" + j +",run=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

main
t1准备好:
t2准备好
t3准备好
t4准备好
当前执行run方法的线程是:thread1
当前执行run方法的线程是:thread2
t5准备好
当前执行run方法的线程是:thread3
当前执行run方法的线程是:thread5
当前执行run方法的线程是:thread4
j=0, run=thread5
j=0, run=thread3
j=0, run=thread4
j=0, run=thread2
j=0, run=thread1
j=1, run=thread1
j=1, run=thread3
j=1, run=thread5
j=1, run=thread2
j=1, run=thread4
j=2, run=thread4
j=2, run=thread2
j=2, run=thread1
j=2, run=thread3
j=2, run=thread5
j=3, run=thread4
j=3, run=thread5
j=3, run=thread1
j=3, run=thread3
j=3, run=thread2
j=4, run=thread2
j=4, run=thread1
j=4, run=thread4
j=4, run=thread3
j=4, run=thread5

上述代码开了5个线程,每个线程的run函数被调用5次,从结果中可以看出,5个线程run函数的执行顺序是随机的。线程执行的顺序和代码的顺序无关。

2.关于run()方法的调用

源码中是这样说明run()方法的调用的:

If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns. 

Subclasses of Thread should override this method.

意思就是:如果这个线程是使用Runnable对象构造的,那么实际上是调用的Runnable对象的run方法;否则的话,方法不做任何处理。Thread类的子类应该重写该方法。

上面的内容可以进一步补充一下,因为Thread类自身继承了Runnable接口,因此也可以采用继承了Thread的一个类的对象作为参数传入,构造Thread实例,这种情况下,实际上调用的则是该传入的Thread对象的run方法。

run方法源码如下(源码中的target就是一个Runnable对象):

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

我们先进行测试一下:

public class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
        System.out.println("构造方法的打印:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("run方法的打印:" + Thread.currentThread().getName());
    }
}
public class Run2 {
    public static void main(String[] args) {
        
        MyThread myThread = new MyThread("myThread");
        myThread.start();
        //myThread.run();
    }
}

打印结果为:

构造方法的打印:main

run方法的打印:myThread

把Run2修改为如下:

public class Run2 {
    public static void main(String[] args) {
        
        MyThread myThread = new MyThread("myThread");
        //myThread.start();
        myThread.run();
    }
}

打印结果:

构造方法的打印:main
run方法的打印:main

从上面的例子我们可以看出:MyThread.java类的构造函数是被main线程调用的,启动start()后的隐式调用的run方法是被创建的名叫myThread线程调用的,而显式调用的run方法则是main线程调用的。

接着,我们用下面的例子进一步来说明run的调用问题:

public class MyThread_t4 extends Thread {

    private int count = 5;

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "计算.count=" + count);
    }
}
public class Run_t4 {
    public static void main(String[] args) {
//        MyThread_t4 myThread_t4 = new MyThread_t4();
//        Thread a = new Thread(myThread_t4,"A");
//        Thread b = new Thread(myThread_t4,"B");
//        Thread c = new Thread(myThread_t4,"C");
//        Thread d = new Thread(myThread_t4,"D");
//        Thread e = new Thread(myThread_t4,"E");
        MyThread_t4 a = new MyThread_t4();
        MyThread_t4 b = new MyThread_t4();
        MyThread_t4 c = new MyThread_t4();
        MyThread_t4 d = new MyThread_t4();
        MyThread_t4 e = new MyThread_t4();

        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

上面的代码会创建a,b,c,d,e四个线程,每个线程都有自己的run方法,每个线程都有各自的count变量,自己减少自己的count变量的值,所以输出结果为4.


输出结果:

由Thread-0计算.count=4
由Thread-3计算.count=4
由Thread-1计算.count=4
由Thread-2计算.count=4
由Thread-4计算.count=4

我们把测试代码修改为如下:

public class Run_t5 {
    public static void main(String[] args) {
        MyThread_t4 myThread_t4 = new MyThread_t4();
        Thread a = new Thread(myThread_t4,"A");
        Thread b = new Thread(myThread_t4,"B");
        Thread c = new Thread(myThread_t4,"C");
        Thread d = new Thread(myThread_t4,"D");
        Thread e = new Thread(myThread_t4,"E");
//        MyThread_t4 a = new MyThread_t4();
//        MyThread_t4 b = new MyThread_t4();
//        MyThread_t4 c = new MyThread_t4();
//        MyThread_t4 d = new MyThread_t4();
//        MyThread_t4 e = new MyThread_t4();

        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

输出结果如下:

由A计算.count=4
由C计算.count=2
由B计算.count=3
由E计算.count=0
由D计算.count=1

从该结果可以看出,产生了非线程安全问题。造成非线程安全问题的主要原因是因为多个线程同时访问了一个变量。为什么上面都是各自线程访问各自的count变量,为什么这里就是访问同一个count变量呢。原因是因为这里创建了a,b,c,d,e四个线程,但是这四个线程都是使用Thread对象myThread_t4作为参数构造的,那么调用的run函数实际上是myThread_t4对象的run函数(如果是使用Runnable对象构造的,那么实际上是调用的Runnable对象的run方法),因此不再是各自的四个run函数,而是一个run函数了,都去调用这个函数,当调用一个run函数的时候,对这个函数而言,内部就是同步操作,但是由于这里run函数内部count--这么一句,在某些JVM中,i--操作分3步,在3个步骤中,如果有多个线程同时访问,就会出现线程安全问题。解决的办法就是将run方法进行同步(加synchronized),当然,这里主要想说明的不是线程安全问题,而是run函数的调用问题,修改代码MyThread_t4如下:

public class MyThread_t4 extends Thread {

    private int count = 5;

    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "计算.count=" + count);
    }
}

当然如下把代码修改成下面这样(把count--替换成count=count-1),也不会出现线程安全问题(因为多个线程访问同一个run()函数,run函数内部是同步执行的):

public class MyThread_t4 extends Thread {

    private int count = 5;

    @Override
    public void run() {
        super.run();
        count=count-1;
        //count--;
        System.out.println("由" + this.currentThread().getName() + "计算.count=" + count);
    }
}



再运行测试函数Run_t5,

输出结果为:

由C计算.count=4
由D计算.count=3
由A计算.count=2
由B计算.count=1
由E计算.count=0