java 多线程学习

创建线程的2种方法:

1.实现Runable接口

 

package com.thread;

 

public class MyThread implements Runnable {

private String name;

public MyThread(String name)

{

this.name=name;

}

 

@Override

public void run() {

// TODO Auto-generated method stub

        for(int i=0;i<5;i++)

        {

       

        System.out.println(i+" my name is "+name+"  "+Thread.currentThread().getName());

        }

}

public static void main(String args[])

{

MyThread thread1=new MyThread("xj1");

MyThread thread2=new MyThread("xj2");

Thread th1=new Thread(thread1);

Thread th2=new Thread(thread2);

th1.start();

th2.start();

}

 

}

2.继承Thread类
package com.thread;

public class MyThread extends Thread {
private String name;
public MyThread(String name)
{
this.name=name;
}
public void run()
{
for(int i=0;i<5;i++)
{
System.out.println(i+" my name is "+name+"  "+Thread.currentThread().getName());
}
}

/**
* @param args
*/
public static void main(String[] args) {
Thread th1=new MyThread("xj1");
Thread th2=new MyThread("xj2");
th1.start();
th2.start();

}

}
-------------------------------------------------------------------------------------------
要理解线程调度的原理,以及线程执行过程,必须理解 线程栈 模型。
线程栈是指某时刻时内存中线程调度的栈信息, 当前调用的方法总是位于栈顶 。线程栈的内容是随着程序的运行动态变化的,因此研究线程栈必须选择一个运行的时刻(实际上指代码运行到什么地方)。
 
下面通过一个示例性的代码说明线程(调用)栈的变化过程。
 
java 多线程学习
 
这幅图描述在代码执行到两个不同时刻1、2时候,虚拟机线程调用栈示意图。
 
当程序执行到t.start();时候,程序多出一个分支(增加了一个调用栈B),这样,栈A、栈B并行执行
 
从这里就可以看出方法调用和线程启动的区别了。
--------------------------------------------------
线程状态
 
线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。用一个图来描述如下:
 
java 多线程学习
1、新状态:线程对象已经创建,还没有在其上调用start()方法。
 
2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
 
3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
 
4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
 
5、死亡态: 当线程的run()方法完成时就认为它死去 。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。即不可在start方法后在继续执行start方法。
 
阻止线程执行的方法:
1.sleep():
注意:
(1)、线程睡眠是帮助所有线程获得运行机会的最好方法。
(2)、 线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行
(3)、sleep()是静态方法,只能控制当前正在运行的线程。
2.线程让步yield():
yield()应该做的是让当前运行线程回到可运行状态,以 允许具有相同优先级的其他线程获得运行机会 。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下 ,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果
3.join()方法
Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。例如:
        Thread t = new MyThread();
        t.start();
        t.join();
t的run方法执行完成后主线程才会继续执行t.join()后的代码。
另外,join()方法还有带超时限制的重载版本。 例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。
 
线程的加入join()对线程栈导致的结果是线程栈发生了变化,当然这些变化都是瞬时的。下面给示意图:
 
java 多线程学习
举例如下:
package com.thread;

public class MyThread extends Thread {

public static void main(String[] args) {
MyThread2 th=new MyThread2();
th.start();
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("计算完成,total="+th.total);
}
}
package com.thread;

public class MyThread2 extends Thread{
int total;

public void run()
{
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this)
{
for(int i=0;i<10;i++)
{
total=total+1;
}
notifyAll();
System.out.println(Thread.currentThread().getName()+"线程total执行完成,total="+total);
}
}
}
运行结果如下:
Thread-0线程total执行完成,total=10
计算完成,total=10
如果不加th.join();则运行结果如下:
计算完成,total=0
Thread-0线程total执行完成,total=10

-------------------------------------------------------------------------
线程的优先级的概念:
线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
 
注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。
 
当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。
 
设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:
        Thread t = new MyThread();
        t.setPriority(8);
        t.start();
线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。
 
线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:
static int MAX_PRIORITY 
          线程可以具有的最高优先级。 
static int MIN_PRIORITY 
          线程可以具有的最低优先级。 
static int NORM_PRIORITY 
          分配给线程的默认优先级。
------------------------------------------------------------------------------------------------------------------
关于锁和同步,有一下几个要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放。
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。
如:
package com.thread;

public class MyThread implements Runnable {
private String name;
private  int num=100;
public MyThread(String name)
{
this.name=name;
}
public    int miner(int a)
{
System.out.println("num1="+this.num+"  "+Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.num=this.num-a;
System.out.println("num2="+this.num+"  "+Thread.currentThread().getName());
return this.num;
}
public void run()
{
for(int i=0;i<5;i++)
{
                        this.miner(10);
}
}

public static void main(String[] args) {
MyThread my=new MyThread("xj1");
                Thread th1=new Thread(my);
Thread th2=new Thread(my);
th1.start();
th2.start();
       }
}
打印结果如下:
num1=100  Thread-0
num1=100  Thread-1
num2=90  Thread-0
num1=80  Thread-0
num2=80  Thread-1
num1=80  Thread-1
num2=60  Thread-1
num1=60  Thread-1
num2=60  Thread-0
num1=60  Thread-0
num2=40  Thread-0
num1=40  Thread-0
num2=40  Thread-1
num1=40  Thread-1
num2=30  Thread-1
num1=20  Thread-1
num2=20  Thread-0
num1=20  Thread-0
num2=10  Thread-1
num2=0  Thread-0
如果在miner方法上 加上synchronized,则打印结果如下:
num1=100  Thread-0
num2=90  Thread-0
num1=90  Thread-0
num2=80  Thread-0
num1=80  Thread-1
num2=70  Thread-1
num1=70  Thread-1
num2=60  Thread-1
num1=60  Thread-1
num2=50  Thread-1
num1=50  Thread-1
num2=40  Thread-1
num1=40  Thread-0
num2=30  Thread-0
num1=30  Thread-0
num2=20  Thread-0
num1=20  Thread-0
num2=10  Thread-0
num1=10  Thread-1
num2=0  Thread-1
即,加上同步后,miner方法同一时刻只能被一个线程访问。
静态方法同步如下:
package com.thread;

public class MyThread implements Runnable {
private String name;
private static int num=100;
public MyThread(String name)
{
this.name=name;
}
public  static   int miner(int a)
{
System.out.println("num1="+MyThread.num+"  "+Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
MyThread.num=MyThread.num-a;
System.out.println("num2="+MyThread.num+"  "+Thread.currentThread().getName());
return MyThread.num;
}
public void run()
{
for(int i=0;i<5;i++)
{
this.miner(10);
}
}

public static void main(String[] args) {
MyThread my1=new MyThread("xj1");
MyThread my2=new MyThread("xj2");
Thread th1=new Thread(my1);
Thread th2=new Thread(my2);
th1.start();
th2.start();

}

}
打印结果如下:
num1=100  Thread-0
num1=100  Thread-1
num2=80  Thread-0
num1=80  Thread-0
num2=80  Thread-1
num1=80  Thread-1
num2=70  Thread-1
num1=60  Thread-1
num2=60  Thread-0
num1=60  Thread-0
num2=50  Thread-1
num1=50  Thread-1
num2=40  Thread-0
num1=40  Thread-0
num2=30  Thread-1
num1=20  Thread-1
num2=20  Thread-0
num1=20  Thread-0
num2=10  Thread-0
num2=0  Thread-1
如果在miner方法上加上synchronized,则运行结果如下:
num1=100  Thread-0
num2=90  Thread-0
num1=90  Thread-0
num2=80  Thread-0
num1=80  Thread-1
num2=70  Thread-1
num1=70  Thread-1
num2=60  Thread-1
num1=60  Thread-1
num2=50  Thread-1
num1=50  Thread-0
num2=40  Thread-0
num1=40  Thread-0
num2=30  Thread-0
num1=30  Thread-1
num2=20  Thread-1
num1=20  Thread-1
num2=10  Thread-1
num1=10  Thread-0
num2=0  Thread-0
即,加上同步后,miner方法同一时刻只能被一个线程访问。

线程同步小结
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
----------------------------------------------------------------
线程交互
 void notify() 
          唤醒在此对象监视器上等待的单个线程。 
 void notifyAll() 
          唤醒在此对象监视器上等待的所有线程。 
 void wait() 
          导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
 
当然,wait()还有另外两个重载方法:
 void wait(long timeout) 
          导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。 
 void wait(long timeout, int nanos) 
          导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
 
以上这些方法是帮助线程传递线程关心的时间状态。
 
关于等待/通知,要记住的关键点是:
必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作
实例如下:
package com.thread;

public class MyThread {

public static void main(String[] args) {
MyThread2 th=new MyThread2();
th.start();
//同步必须加,否则无法执行wait方法,会报java.lang.IllegalMonitorStateException错误
synchronized(th)
{
System.out.println(Thread.currentThread().getName()+"线程 等待。。。");
try {
th.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程被唤醒,b对象计算的总和是:" + th.total); 
}
}

}
package com.thread;

public class MyThread2 extends Thread{
int total;

public void run()
{
//此处让线程sleep是为了确保main主线程执行到wait方法时Thread-0线程还没执行完notify方法,应为如果notify在wait前执行完,则main线程将永远等待不会被在叫醒
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同步必须加,否则无法执行notify方法,会报java.lang.IllegalMonitorStateException错误
synchronized(this)
{
for(int i=0;i<10;i++)
{
total=total+1;
}
notify();
System.out.println(Thread.currentThread().getName()+"线程执行完成,total="+total);
}
}
}
执行main方法,运行结果如下:
main线程 等待。。。
Thread-0线程执行完成,total=10
main线程被唤醒,b对象计算的总和是:10
注意:
当在对象上调用wait()方法时,执行该代码的线程立即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。如果线程荣然在完成同步代码,则线程在移出之前不会放弃锁。因此,只要调用notify()并不意味着这时该锁变得可用。
notifyAll()用法实例:
package com.thread;

public class MyThread extends Thread {
private MyThread2 mth;
public MyThread(MyThread2 mth)
{
this.mth=mth;
}
public void run()
{
synchronized(mth)
{
System.out.println(Thread.currentThread().getName()+"线程等待total计算完成。。。。");
try {
mth.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程被叫醒,total="+mth.total);
}
}

public static void main(String[] args) {
MyThread2 th=new MyThread2();
new MyThread(th).start();
new MyThread(th).start();
new MyThread(th).start();
th.start();
}
}
package com.thread;

public class MyThread2 extends Thread{
int total;

public void run()
{
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this)
{
for(int i=0;i<10;i++)
{
total=total+1;
}
notifyAll();
System.out.println(Thread.currentThread().getName()+"线程total执行完成,total="+total);
}
}
}
--------------------------------------------------------------
向线程传递数据的三种方法:

一、通过构造方法传递数据

在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:

              
              
  1. package mythread;  
  2.  
  3. public class MyThread1 extends Thread  
  4. {  
  5.     private String name;  
  6.  
  7.     public MyThread1(String name)  
  8.     {  
  9.         this.name = name;  
  10.     }  
  11.     public void run()  
  12.     {  
  13.         System.out.println("hello " + name);  
  14.     }  
  15.     public static void main(String[] args)  
  16.     {  
  17.         Thread thread = new MyThread1("world");  
  18.         thread.start();          
  19.     }  
  20. }  

由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。

二、通过变量和方法传递数据

向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:

              
              
  1. package mythread;  
  2.  
  3. public class MyThread2 implements Runnable  
  4. {  
  5.     private String name;  
  6.  
  7.     public void setName(String name)  
  8.     {  
  9.         this.name = name;  
  10.     }  
  11.     public void run()  
  12.     {  
  13.         System.out.println("hello " + name);  
  14.     }  
  15.     public static void main(String[] args)  
  16.     {  
  17.         MyThread2 myThread = new MyThread2();  
  18.         myThread.setName("world");  
  19.         Thread thread = new Thread(myThread);  
  20.         thread.start();  
  21.     }  
  22. }  

三、通过回调函数传递数据

上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。

              
              
  1. package mythread;  
  2.  
  3. class Data  
  4. {  
  5.     public int value = 0;  
  6. }  
  7. class Work  
  8. {  
  9.     public void process(Data data, Integer numbers)  
  10.     {  
  11.         for (int n : numbers)  
  12.         {  
  13.             data.value += n;  
  14.         }  
  15.     }  
  16. }  
  17. public class MyThread3 extends Thread  
  18. {  
  19.     private Work work;  
  20.  
  21.     public MyThread3(Work work)  
  22.     {  
  23.         this.work = work;  
  24.     }  
  25.     public void run()  
  26.     {  
  27.         java.util.Random random = new java.util.Random();  
  28.         Data data = new Data();  
  29.         int n1 = random.nextInt(1000);  
  30.         int n2 = random.nextInt(2000);  
  31.         int n3 = random.nextInt(3000);  
  32.         work.process(data, n1, n2, n3);   // 使用回调函数  
  33.         System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" 
  34.                 + String.valueOf(n3) + "=" + data.value);  
  35.     }  
  36.     public static void main(String[] args)  
  37.     {  
  38.         Thread thread = new MyThread3(new Work());  
  39.         thread.start();  
  40.     }  
  41. }  

在上面代码中的process方法被称为回调函数。从本质上说,回调函数就是事件函数。在Windows API中常使用回调函数和调用API的程序之间进行数据交互。因此,调用回调函数的过程就是最原始的引发事件的过程。在这个例子中调用了process方法来获得数据也就相当于在run方法中引发了一个事件。

--------------------------------------------------------------

从线程返回数据的二种方法:

从线程中返回数据和向线程传递数据类似。也可以通过类成员以及回调函数来返回数据。但类成员在返回数据和传递数据时有一些区别,下面让我们来看看它们区别在哪。

一、通过类变量和方法返回数据

使用这种方法返回数据需要在调用start方法后才能通过类变量或方法得到数据。让我们先来看看例程2-13会得到什么结果。

              
              
  1. package mythread;  
  2.  
  3. public class MyThread extends Thread  
  4. {  
  5.     private String value1;  
  6.     private String value2;  
  7.  
  8.     public void run()  
  9.     {  
  10.         value1 = "通过成员变量返回数据";  
  11.         value2 = "通过成员方法返回数据";  
  12.     }  
  13.     public static void main(String[] args) throws Exception  
  14.     {  
  15.         MyThread thread = new MyThread();  
  16.         thread.start();  
  17.         System.out.println("value1:" + thread.value1);  
  18.         System.out.println("value2:" + thread.value2);  
  19.     }  
  20. }  

运行上面的代码有可能输出如下的结果:

value1:null
value2:null

从上面的运行结果看很不正常。在run方法中已经对value1和value2赋了值,而返回的却是null。发生这种情况的原因是调用start方法后就立刻输出了value1和value2的值,而这里run方法还没有执行到为value1和value2赋值的语句。要避免这种情况的发生,就需要等run方法执行完后才执行输出value1和value2的代码。因此,我们可以想到使用sleep方法将主线程进行延迟,如可以在thread.start()后加一行如下的语句:

sleep(1000);

这样做可以使主线程延迟1秒后再往下执行,但这样做有一个问题,就是我们怎么知道要延迟多长时间。在这个例子的run方法中只有两条赋值语句,而且只创建了一个线程,因此,延迟1秒已经足够,但如果run方法中的语句很复杂,这个时间就很难预测,因此,这种方法并不稳定。

我们的目的就是得到value1和value2的值,因此,只要判断value1和value2是否为null。如果它们都不为null时,就可以输出这两个值了。我们可以使用如下的代码来达到这个目的:

              
              
  1. while (thread.value1 == null || thread.value2 == null); 

使用上面的语句可以很稳定地避免这种情况发生,但这种方法太耗费系统资源。大家可以设想,如果run方法中的代码很复杂,value1和value2需要很长时间才能被赋值,这样while循环就必须一直执行下去,直到value1和value2都不为空为止。因此,我们可以对上面的语句做如下的改进:

              
              
  1. while (thread.value1 == null || thread.value2 == null)  
  2.     sleep(100); 

在while循环中第判断一次value1和value2的值后休眠100毫秒,然后再判断这两个值。这样所占用的系统资源会小一些。

上面的方法虽然可以很好地解决,但Java的线程模型为我们提供了更好的解决方案,这就是join方法。在前面已经讨论过,join的功能就是使用线程从异步执行变成同步执行。当线程变成同步执行后,就和从普通的方法中得到返回数据没有什么区别了。因此,可以使用如下的代码更有效地解决这个问题:

thread.start();
thread.join();

在thread.join()执行完后,线程thread的run方法已经退出了,也就是说线程thread已经结束了。因此,在thread.join()后面可以放心大胆地使用MyThread类的任何资源来得到返回数据。

二、通过回调函数返回数据

其实这种方法已经在《向线程传递数据的三种方法》中介绍了。在《向线程传递数据的三种方法》一文的例子中通过Work类的process方法向线程中传递了计算结果,但同时,也通过process方法从线程中得到了三个随机数。因此,这种方法既可以向线程中传递数据,也可以从线程中获得数据。

你可能感兴趣的:(java,多线程)