1、想要让对象具有多线程功能,只要继承java.lang.Thread类或是实现java.lang.Runnable接口。
2、继承Thread
首先继承java.lang.Thread类,并重新定义run()方法,之后可以实例化自定义的Thread类,接着使用start()方法。
但是这种方法有缺陷!
如果使用继承的方法来定义线程类,就意味着定义的类是一个Thread类型,且由于在Java中
一次只能继承一个类,如果一个类已经要继承某个类,那么就不能继承Thread类!
下面这个范例,将定义一个文字模式下的密码屏蔽线程,这个线程会不断地重复返回上一个字符位置并显示屏蔽字符;
我们可以在截取使用者密码输入前启动这个线程,仿真密码屏蔽功能,以避免使用者输入的密码被窥视。
3、实现Runnable接口
可以实现java.lang.Runnable接口来定义具有线程功能的类,Runnable接口中定义了一个
run()方法要实现,在实例化一个Thread对象时,可以传入一个实现Runnable接口的对象作为变量。
Thread对象会调用Runnable对象的run方法,进而执行其中所定义的流程。
下面范例改写密码屏蔽程序:
4、Daemon线程
设计一个程序,除了主线程之外,还运用了一个线程在背景中进行相关运算工作。程序可能这样:
运行main()的线程执行到最后一个语句了,工作该结束了,但程序并没有终止,因为还有一个线程在执行。
System.exit()虽然可以停止它,但这是强迫程序结束,并且这个方法不是随时可以使用。
“Daemon”线程的作用是在程序的运行期间于后台提供一种“常规”服务,但它并不属于程序的一个基本部分。
因此,一旦所有非Daemon线程完成,程序也会中止运行。相反,假若有任何非Daemon线程仍在运行
(比如还有一个正在运行main()的线程),则程序的运行不会中止。
修改程序如下:
可以使用setDaemon()方法来设定一个线程是否为Daemon线程;
使用isDaemon()方法可以判断一个线程是否为Daemon线程。
Java默认所有从Daemon线程产生的线程也是Daemon线程,因为基本上由一个背景服务线程
衍生出来的线程,也应该是为了在背景服务而产生的,所以在产生它的线程终止时,它也应该一并跟着终止。
5、线程有优先权,由1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),默认是5(Thread.NORM_PRIORITY)。
可以使用Thread的SetPriority()方法来设定优先权,设定必须在1到10之间。
绝大多数操作系统都支持Timeslicing,即为每个线程分配一段CPU;
在不支持Timeslicing的操作系统中,如果想要让目前线程礼让一下其他线程,
让它们获得CPU时间,可以调用线程执行的yield()方法。
6、线程的4个主要周期状态是创建线程、可执行(Runnable)、非可执行和终止(Dead)。
当实例化一个Thread并执行Start()之后,线程进入Runnable状态并开始执行run()方法;
通过调用sleep(),wait()等方法线程由Runnable状态转为Not Runnable状态;
当run()方法执行完毕后,线程进入Dead状态。
7、有几种状况会让线程进入Not Runnable状态(或是blocked状态)
1)调用sleep()方法
2)调用wait()方法
3)等待输入/输出完成
以下几种情况可以让线程回到Runnable状态
1)调用notify()
2)调用notifyAll()
3)调用interrupt()
4)输入/输出结束
最后线程执行结束,进入Dead状态,可以调用isAlive()方法检测线程是否存活。
8、当使用Thread.sleep()让线程暂停执行进入Not Runnable状态时,可以使用interrupt()让它
离开Not Runnable状态,且会丢出java.lang.InterruptedException异常对象。
范例如下:
9、如果要暂停线程,但暂停时间未知,使用sleep()不是个好办法;
可以使用wait(),然后让别的线程用notify()或notifyAll()来通知Not Runnable的线程回到Runnable状态。
10、线程的加入(join)
当线程使用join()加入至另一个线程时,另一个线程会等待这个被加入的线程工作完毕,然后再继续未完成的任务;
例如:A线程正在运行,然后插入B线程,并且B先执行完毕后,A在继续。
join()表示将线程加入成为另一个线程的流程之一。
范例如下:
有时候加入的线程不能处理太久,可以在join()方法上指定时间,如join(5000);
11、线程的停止
查询API会发现Thread的stop()方法已经被标示为deprecated,不建议使用stop()停止一个线程的运行。
【@deprecated】
这是Java 1.1的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不
必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该
方法时会收到编译器的警告。
如果要停止一个线程,最好自行实现,即执行完run()方法。
1)如果线程的run()方法执行的是一个重复执行的循环,可以提供一个标记(flag)来控制循环;
2)如果线程因为执行sleep()或是wait()而进入Not Runnable状态,而我们想要停止它,则可以
使用interrupt(),而程序会丢出InterruptedException异常,使得线程离开run()方法。
范例如下:
如果程序因为输入/输出的装置等待而停滞(进入Not Runnable状态),基本上必须等待输入/输出动作完成才能离开。
无法使用interrupt()方法来使得线程离开run()方法,要提供替代的方法,基本上也是引发一个异常,
而这个异常如何引发,要看所使用的输入/输出而定。
例如使用readLine()在等待网络上的一个信息,此时线程进入Not Runnable直到读到一个信息,
让它离开run()的方法就是使用close()关闭它的流,这时会引发一个IOException异常而使得线程离开run()方法。例如:
12、ThreadGroup
在Java中每个线程都属于某个线程组(Thread Group)管理的一员,可以使用下面的指令取得当前进程所属的线程组名称:
Thread.currentThread().getThreadGroup().getName();
每一个线程产生的时候,都会被归入某个线程组,这视该线程是在哪个组中产生而定。
如果没有指定,则归入产生该子线程的线程组中;也可以自行指定线程组,线程一旦归入某个组,就无法更换组。
java.lang.ThreadGroup类可以统一管理整个组中的线程。
13、同步化
只要继承Thread类或是实现Runnable接口,就可以让对象具有多线程的功能;
但如果多个线程共享某个数据,数据的同步就要特别注意!
范例如下,设计了PersonalInfo类:
测试程序如下:
虽然传给setNameAndID()的变量没有问题,在某个时间点时,thread1设定了Justin、J.L给name和id,
在进行if测试的前一刻,thread2可能此时刚好调用setNameAndID("Shang Hwang", "S.H")。
在name被设定为Shang Hwang时,checkNameAndIDEqual()开始执行,此时name等于Shang Hwang,而id还是J.L,
所以checkNameAndIDEqual()就会返回false,结果就显示错误信息。
14、使用synchronized可以进行同步化操作。
它有下面几种用法:
1)一种使用方式是用于方法上,让方法的范围内都成为被同步化区域。例如:
被标示为synchronized的方法就成为被同步化区域的一员,当线程执行某个对象的被同步化方法时,
线程会在对象上得到一个锁定,锁定所有同样被标示为synchronized的区域,不让其他的线程执行这些区域。
2)也可以用于限定某个程序区块为被同步化区域。例如:
3)也可以标示某个对象要求同步化。
例如在多线程存取同一个ArrayList对象时,由于ArrayList并没有实现数据存取时的同步化,所以当它
使用于多线程环境时,必须注意多个线程存取同一个ArrayList时,有可能发生两个以上的线程将数据
存入ArrayList的同一个位置,造成数据的相互覆盖。
为了确保数据存入时的正确性,可以在存取ArrayList对象时要求同步化。例如:
//arrayList参考至一个ArrayList的一个实例
synchronized(arrayList)
{
arrayList.add(new SomeClass());
}
15、wait()和notify()
wait()、notify()和notifyAll()是由Object类所提供的方法,因为java中所有的对象的最顶层
都继承自Object,所以在定义自己的类时会继承这些方法,它们都被声明为final,所以无法重新定义它们。
通过这3个方法可以要求线程进入等待,或是通知线程回到Runnable状态。
一定要搞清楚对象的方法和调用它的线程之间的关系!!! 如下:
必须在被同步化的方法或区块中调用wait()方法,当对象的wait()方法被调用时,当前的线程
会被放入对象的等待集合(Wait Set)中,线程会解除对对象的锁定,其他的线程可以竞争被同步化区块。
被放在等待集中的线程将不参与线程的排队,wait()可以指定等待的时间,如果指定时间,则时间到之后
线程会再度加入排队,如果指定时间为0或不指定,则线程会持续等待,知道被中断,或是被告知参与排队。
当对象的notify()被调用时,它会从当前对象的等待集中通知一个线程加入排队,被通知的线程是随机的,
被通知的线程会与其他正在执行的线程共同竞争对对象的锁定。
如果调用notifyAll(),则所有在等待集中的线程都会被通知加入排队,这些线程会与其他正在执行的线程共同竞争对对象的锁定。
简单地说,当线程调用wait()方法时,表示它要先让出对象的被同步区使用权并等待通知,或是等待
一段指定的时间,直到被通知或时间到时再与其他线程竞争,如果可以执行时就从等待点开始执行!
说明wait()、notify()和notifyAll()最常见的例子,就是生产者和消费者。
范例如下:生产者每次生产一个int整数交给店员,而消费者从店员处取走整数,店员一次只能持有一个整数。
【Clerk.java】
【Consumer.java】
【ProductTest.java】
2、继承Thread
首先继承java.lang.Thread类,并重新定义run()方法,之后可以实例化自定义的Thread类,接着使用start()方法。
但是这种方法有缺陷!
如果使用继承的方法来定义线程类,就意味着定义的类是一个Thread类型,且由于在Java中
一次只能继承一个类,如果一个类已经要继承某个类,那么就不能继承Thread类!
下面这个范例,将定义一个文字模式下的密码屏蔽线程,这个线程会不断地重复返回上一个字符位置并显示屏蔽字符;
我们可以在截取使用者密码输入前启动这个线程,仿真密码屏蔽功能,以避免使用者输入的密码被窥视。
public class EraserThread extends Thread
{
private boolean active;
private String mask;
public EraserThread()
{
this('*');
}
public EraserThread(char maskChar)
{
active = true;
mask = "\010" + maskChar;
}
//停止线程时设定为false
public void setActive(boolean active)
{
this.active = active;
}
public boolean isActive()
{
return active;
}
//重新定义run()方法
public void run()
{
while(isActive())
{
System.out.print(mask);
try
{
//暂停目前的线程50ms
//如果用户没有输入的话,这个线程会一直刷新当前字符为'#'
Thread.currentThread().sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
在文字模式下输出\010表示返回一个字符位置
import java.util.Scanner;
public class EraserThreadDemo
{
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
while(true)
{
System.out.print("输入名称:");
String name = scanner.next();
System.out.print("输入密码:");
//启动EraserThread线程
EraserThread eraserThread = new EraserThread('#');
eraserThread.start();
String password = scanner.next();
eraserThread.setActive(false);
if("Justice".equals(name) && "123456".equals(password))
{
System.out.println("欢迎Justice!");
break;
}
else
{
System.out.printf("%s, 名称或密码错误,请重新输入!%n", name);
}
}
}
}
3、实现Runnable接口
可以实现java.lang.Runnable接口来定义具有线程功能的类,Runnable接口中定义了一个
run()方法要实现,在实例化一个Thread对象时,可以传入一个实现Runnable接口的对象作为变量。
Thread对象会调用Runnable对象的run方法,进而执行其中所定义的流程。
下面范例改写密码屏蔽程序:
public class Eraser implements Runnable
{
private boolean active;
private String mask;
public Eraser()
{
this('*');
}
public Eraser(char maskChar)
{
active = true;
mask = "\010" + maskChar;
}
//停止线程时设定为false
public void setActive(boolean active)
{
this.active = active;
}
public boolean isActive()
{
return active;
}
//重新定义run()方法
public void run()
{
while(isActive())
{
System.out.print(mask);
try
{
//暂停目前的线程50ms
//如果用户没有输入的话,这个线程会一直刷新当前字符为'#'
Thread.currentThread().sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
import java.util.Scanner;
public class EraserDemo
{
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
while(true)
{
System.out.print("输入名称:");
String name = scanner.next();
System.out.print("输入密码:");
//EraserThread实现Runnable接口
Eraser eraser = new Eraser('#');
//传入实现Runnable接口的对象,启动Eraser线程
Thread eraserThread = new Thread(eraser);
eraserThread.start();
String password = scanner.next();
eraser.setActive(false);
if("Justice".equals(name) && "123456".equals(password))
{
System.out.println("欢迎Justice!");
break;
}
else
{
System.out.printf("%s, 名称或密码错误,请重新输入!%n", name);
}
}
}
}
基本上建议以实现Runnable的方式让对象具有线程功能,保留日后修改的弹性。
4、Daemon线程
设计一个程序,除了主线程之外,还运用了一个线程在背景中进行相关运算工作。程序可能这样:
public class SimpleThread
{
public static void main(String[] args)
{
Thread thread = new Thread(new Runnable()
{
public void run()
{
while(true)
{
System.out.print("T");
}
}
});
thread.start();
//主线程继续其他工作...
//现在主线程执行到这里,工作应该结束了
}
}
运行main()的线程执行到最后一个语句了,工作该结束了,但程序并没有终止,因为还有一个线程在执行。
System.exit()虽然可以停止它,但这是强迫程序结束,并且这个方法不是随时可以使用。
“Daemon”线程的作用是在程序的运行期间于后台提供一种“常规”服务,但它并不属于程序的一个基本部分。
因此,一旦所有非Daemon线程完成,程序也会中止运行。相反,假若有任何非Daemon线程仍在运行
(比如还有一个正在运行main()的线程),则程序的运行不会中止。
修改程序如下:
public class DaemonThread
{
public static void main(String[] args)
{
//匿名类写法!!!
Thread thread = new Thread(new Runnable()
{
public void run()
{
while(true)
{
System.out.print("T");
}
}
});
//设定为Daemon线程
thread.setDaemon(true);
thread.start();
}
}
可以使用setDaemon()方法来设定一个线程是否为Daemon线程;
使用isDaemon()方法可以判断一个线程是否为Daemon线程。
Java默认所有从Daemon线程产生的线程也是Daemon线程,因为基本上由一个背景服务线程
衍生出来的线程,也应该是为了在背景服务而产生的,所以在产生它的线程终止时,它也应该一并跟着终止。
5、线程有优先权,由1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),默认是5(Thread.NORM_PRIORITY)。
可以使用Thread的SetPriority()方法来设定优先权,设定必须在1到10之间。
绝大多数操作系统都支持Timeslicing,即为每个线程分配一段CPU;
在不支持Timeslicing的操作系统中,如果想要让目前线程礼让一下其他线程,
让它们获得CPU时间,可以调用线程执行的yield()方法。
6、线程的4个主要周期状态是创建线程、可执行(Runnable)、非可执行和终止(Dead)。
当实例化一个Thread并执行Start()之后,线程进入Runnable状态并开始执行run()方法;
通过调用sleep(),wait()等方法线程由Runnable状态转为Not Runnable状态;
当run()方法执行完毕后,线程进入Dead状态。
7、有几种状况会让线程进入Not Runnable状态(或是blocked状态)
1)调用sleep()方法
2)调用wait()方法
3)等待输入/输出完成
以下几种情况可以让线程回到Runnable状态
1)调用notify()
2)调用notifyAll()
3)调用interrupt()
4)输入/输出结束
最后线程执行结束,进入Dead状态,可以调用isAlive()方法检测线程是否存活。
8、当使用Thread.sleep()让线程暂停执行进入Not Runnable状态时,可以使用interrupt()让它
离开Not Runnable状态,且会丢出java.lang.InterruptedException异常对象。
范例如下:
public class InterruptDemo
{
public static void main(String[] args)
{
Thread thread = new Thread(new Runnable()
{
public void run()
{
try
{
//暂停99999ms
Thread.sleep(99999);
}
catch (InterruptedException e)
{
System.out.println("I'm interrupted!");
}
}
});
thread.start();
thread.interrupt(); //立即中断它
}
}
9、如果要暂停线程,但暂停时间未知,使用sleep()不是个好办法;
可以使用wait(),然后让别的线程用notify()或notifyAll()来通知Not Runnable的线程回到Runnable状态。
10、线程的加入(join)
当线程使用join()加入至另一个线程时,另一个线程会等待这个被加入的线程工作完毕,然后再继续未完成的任务;
例如:A线程正在运行,然后插入B线程,并且B先执行完毕后,A在继续。
join()表示将线程加入成为另一个线程的流程之一。
范例如下:
public class ThreadA
{
public static void main(String[] args)
{
//当前线程为A线程
System.out.println("线程A执行");
//声明B线程
Thread threadB = new Thread(new Runnable()
{
public void run()
{
try
{
System.out.println("线程B开始...");
for(int i = 0; i < 5; i++)
{
Thread.sleep(1000);
System.out.println("线程B执行...");
}
System.out.println("线程B即将结束...");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
});
//线程B执行后才加入到A
threadB.start();
try
{
//threadB 加入 threadA
threadB.join();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("Thread A 执行");
}
}
有时候加入的线程不能处理太久,可以在join()方法上指定时间,如join(5000);
11、线程的停止
查询API会发现Thread的stop()方法已经被标示为deprecated,不建议使用stop()停止一个线程的运行。
【@deprecated】
这是Java 1.1的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不
必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该
方法时会收到编译器的警告。
如果要停止一个线程,最好自行实现,即执行完run()方法。
1)如果线程的run()方法执行的是一个重复执行的循环,可以提供一个标记(flag)来控制循环;
2)如果线程因为执行sleep()或是wait()而进入Not Runnable状态,而我们想要停止它,则可以
使用interrupt(),而程序会丢出InterruptedException异常,使得线程离开run()方法。
范例如下:
public class SomeThread implements Runnable
{
public void run()
{
System.out.println("sleep...至 not runnable状态");
try
{
Thread.sleep(9999);
}
catch (InterruptedException e)
{
System.out.println("I'm interrupted...");
}
}
public static void main(String[] args)
{
Thread thread = new Thread(new SomeThread());
thread.start();
thread.interrupt();
}
}
如果程序因为输入/输出的装置等待而停滞(进入Not Runnable状态),基本上必须等待输入/输出动作完成才能离开。
无法使用interrupt()方法来使得线程离开run()方法,要提供替代的方法,基本上也是引发一个异常,
而这个异常如何引发,要看所使用的输入/输出而定。
例如使用readLine()在等待网络上的一个信息,此时线程进入Not Runnable直到读到一个信息,
让它离开run()的方法就是使用close()关闭它的流,这时会引发一个IOException异常而使得线程离开run()方法。例如:
public class Client implements Runnable
{
private Socket skt;
//...
public void terminate()
{
skt.close();
}
public void run()
{
//...
try
{
BufferedReader buf = new BufferedReader(new InputStreamReader(skt.getInputStream()));
//读取客户端信息
//执行readLine()会进入Not Runnable状态直到读到客户端信息
while((userMessage = buf.readLine()) != null)
{
//...
}
}
catch(IOException e)
{
System.out.println("线程被终止...");
}
}
}
12、ThreadGroup
在Java中每个线程都属于某个线程组(Thread Group)管理的一员,可以使用下面的指令取得当前进程所属的线程组名称:
Thread.currentThread().getThreadGroup().getName();
每一个线程产生的时候,都会被归入某个线程组,这视该线程是在哪个组中产生而定。
如果没有指定,则归入产生该子线程的线程组中;也可以自行指定线程组,线程一旦归入某个组,就无法更换组。
java.lang.ThreadGroup类可以统一管理整个组中的线程。
13、同步化
只要继承Thread类或是实现Runnable接口,就可以让对象具有多线程的功能;
但如果多个线程共享某个数据,数据的同步就要特别注意!
范例如下,设计了PersonalInfo类:
public class PersonalInfo
{
private String name;
private String id;
private int count;
public PersonalInfo()
{
name = "nobody";
id = "N/A";
}
public void setNameAndID(String name, String id)
{
this.name = name;
this.id = id;
if(!checkNameAndIDEqual())
{
System.out.println(count + ") illegal name or ID...");
}
count++;
}
private boolean checkNameAndIDEqual()
{
return (name.charAt(0) == id.charAt(0)) ? true : false;
}
}
测试程序如下:
public class PersonalInfoTest
{
public static void main(String[] args)
{
final PersonalInfo person = new PersonalInfo();
//假设会有两个线程可能更新person对象
Thread thread1 = new Thread(new Runnable()
{
public void run()
{
while(true)
{
person.setNameAndID("Justin Lin", "J.L");
}
}
});
Thread thread2 = new Thread(new Runnable()
{
public void run()
{
while(true)
{
person.setNameAndID("Shang Hwang", "S.H");
}
}
});
System.out.println("开始测试...");
thread1.start();
thread2.start();
}
}
虽然传给setNameAndID()的变量没有问题,在某个时间点时,thread1设定了Justin、J.L给name和id,
在进行if测试的前一刻,thread2可能此时刚好调用setNameAndID("Shang Hwang", "S.H")。
在name被设定为Shang Hwang时,checkNameAndIDEqual()开始执行,此时name等于Shang Hwang,而id还是J.L,
所以checkNameAndIDEqual()就会返回false,结果就显示错误信息。
14、使用synchronized可以进行同步化操作。
它有下面几种用法:
1)一种使用方式是用于方法上,让方法的范围内都成为被同步化区域。例如:
public synchronized void setNameAndID(String name, String id)
{
this.name = name;
this.id = id;
if(!checkNameAndIDEqual())
{
System.out.println(count + ") illegal name or ID...");
}
count++;
}
被标示为synchronized的方法就成为被同步化区域的一员,当线程执行某个对象的被同步化方法时,
线程会在对象上得到一个锁定,锁定所有同样被标示为synchronized的区域,不让其他的线程执行这些区域。
2)也可以用于限定某个程序区块为被同步化区域。例如:
public void setNameAndID(String name, String id)
{
synchronized(this)
{
this.name = name;
this.id = id;
if(!checkNameAndIDEqual())
{
System.out.println(count + ") illegal name or ID...");
}
count++;
}
}
3)也可以标示某个对象要求同步化。
例如在多线程存取同一个ArrayList对象时,由于ArrayList并没有实现数据存取时的同步化,所以当它
使用于多线程环境时,必须注意多个线程存取同一个ArrayList时,有可能发生两个以上的线程将数据
存入ArrayList的同一个位置,造成数据的相互覆盖。
为了确保数据存入时的正确性,可以在存取ArrayList对象时要求同步化。例如:
//arrayList参考至一个ArrayList的一个实例
synchronized(arrayList)
{
arrayList.add(new SomeClass());
}
15、wait()和notify()
wait()、notify()和notifyAll()是由Object类所提供的方法,因为java中所有的对象的最顶层
都继承自Object,所以在定义自己的类时会继承这些方法,它们都被声明为final,所以无法重新定义它们。
通过这3个方法可以要求线程进入等待,或是通知线程回到Runnable状态。
一定要搞清楚对象的方法和调用它的线程之间的关系!!! 如下:
必须在被同步化的方法或区块中调用wait()方法,当对象的wait()方法被调用时,当前的线程
会被放入对象的等待集合(Wait Set)中,线程会解除对对象的锁定,其他的线程可以竞争被同步化区块。
被放在等待集中的线程将不参与线程的排队,wait()可以指定等待的时间,如果指定时间,则时间到之后
线程会再度加入排队,如果指定时间为0或不指定,则线程会持续等待,知道被中断,或是被告知参与排队。
当对象的notify()被调用时,它会从当前对象的等待集中通知一个线程加入排队,被通知的线程是随机的,
被通知的线程会与其他正在执行的线程共同竞争对对象的锁定。
如果调用notifyAll(),则所有在等待集中的线程都会被通知加入排队,这些线程会与其他正在执行的线程共同竞争对对象的锁定。
简单地说,当线程调用wait()方法时,表示它要先让出对象的被同步区使用权并等待通知,或是等待
一段指定的时间,直到被通知或时间到时再与其他线程竞争,如果可以执行时就从等待点开始执行!
说明wait()、notify()和notifyAll()最常见的例子,就是生产者和消费者。
范例如下:生产者每次生产一个int整数交给店员,而消费者从店员处取走整数,店员一次只能持有一个整数。
【Clerk.java】
public class Producer implements Runnable
{
private Clerk clerk;
public Producer(Clerk clerk)
{
this.clerk = clerk;
}
public void run()
{
System.out.println("生产者开始生产整数...");
//生产1到10的整数
for(int product = 1; product <= 10; product++)
{
try
{
//暂停随机时间
Thread.sleep((int)Math.random() * 3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//将产品交个店员
clerk.setProduct(product);
}
}
}
【Producer.java】
public class Producer implements Runnable
{
private Clerk clerk;
public Producer(Clerk clerk)
{
this.clerk = clerk;
}
public void run()
{
System.out.println("生产者开始生产整数...");
//生产1到10的整数
for(int product = 1; product <= 10; product++)
{
try
{
//暂停随机时间
Thread.sleep((int)Math.random() * 3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//将产品交个店员
clerk.setProduct(product);
}
}
}
【Consumer.java】
public class Consumer implements Runnable
{
private Clerk clerk;
public Consumer(Clerk clerk)
{
this.clerk = clerk;
}
public void run()
{
System.out.println("消费者开始消耗整数...");
//消耗10个整数
for(int i = 1; i <= 10; i++)
{
try
{
//等待随机时间
Thread.sleep((int)Math.random() * 3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
//从店员处取走整数
clerk.getProduct();
}
}
}
【ProductTest.java】
public class ProductTest
{
public static void main(String[] args)
{
Clerk clerk = new Clerk();
//生产者线程
Thread producerThread = new Thread(new Producer(clerk));
//消费者线程
Thread consumerThread = new Thread(new Consumer(clerk));
producerThread.start();
consumerThread.start();
}
}