主要内容
详细内容
概念
与进程的比较
(1)在操作系统中,所有运行中的任务通常对应一个进程,每个运行中程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程;
(2)线程包括三个特性:独立性;动态性以及并发性。
(3)线程也被称作轻量级进程(Lightweight Process),线程是进程的执行单元;线程是独立运行的并且线程的执行是抢占式的;
(4)一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行;
(5)一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但是至少包含一个线程。
多线程优缺点
(1)进程之间不可以共享内存,但线程之间易于共享内存;
(2)使用多线程来实现多任务并发比多进程的效率高;
(3)Java语言内置了多线程功能支持;
(4)线程太多会导致效率的降低,因为线程的执行依靠的是CPU的来回切换。
(5)多线程是指一个进程中有多个线程。
线程的创建和启动
继承Thread类
线程是程序中的执行线程,Java虚拟机允许应用程序并发的运行多个执行线程;Thread类的语法格式如下所示:
public class Thread extends Object
implements Runnable
通过继承Thread类创建并启动多线程的步骤有三步:
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,run方法格式如下所示:
public void run()
(2)创建Thread子类的实例,即创建了线程对象;
(3)调用线程对象的start()方法来启动该线程;start方法格式如下所示:
public void start()
示例代码如下所示:
public class FirstThread extends Thread
{
private int i;
// 重写run方法,run方法的方法体就是线程执行体
public void run()
{
for (; i < 100; i++)
{
//当线程类继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName()返回当前线程的对象;
//因此可以调用getName()直接返回当前线程的名字
System.out.println(getName() + "" + i);
}
}
public static void main(String[] args)
{
for (int i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20)
{
// 创建并启动第一个线程
new FirstThread().start();
// 创建并启动第二个线程
new FirstThread().start();
}
}
}
}
以上程序中使用了Thread类中重要的两个方法:
public static Thread currentThread()
返回对当前正在执行的线程对象的引用;
|
返回该线程的名称。
实现Runnable接口
Runnable接口应该有那些打算通过某一线程执行其实例的类来创建,类必须定义并实现run方法,Runnable接口语法格式如下所示:
public interface Runnable
通过实现Runnable接口创建并启动线程的步骤有三步,如下所示:
(1)定义Runnable接口的实现类,并重写该接口的run方法,该方法的方法体是线程的执行体;run方法的格式如下所示:
void run()
(2)创建Runnable实现类的实例,并以此实例创建Thread对象;
(3)调用线程对象的start方法来启动该线程;Java虚拟机调用线程的run方法;start方法的格式如下所示:
public void start()
示例代码如下所示:
public class SecondThread implements Runnable
{
private int i;
public void run()
{
for (; i < 100; i++)
{
//当线程类实现Runnable接口时,如果想要获取当前线程,只能使用Thread.currentThread()方法
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args)
{
for (int i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20)
{
SecondThread st = new SecondThread();
// 创建并启动第一个线程
new Thread(st , "新线程1").start();
new Thread(st , "新线程2").start();
}
}
}
}
使用Callable和Future创建
Callable接口返回接口并且可能抛出异常的任务,实现者定义了一个不带任何参数的call方法;Callable接口语法格式如下所示:
public interface Callable<V>
Future接口表示异步计算的结果;Future接口语法格式如下所示:
public interface Future<V>
FutureTask类表示可取消的异步计算,利用开始和取消计算的方法、查询计算是否实现的方法和获取计算结果的方法,此类提供了对Future接口的基本实现。FutureTask类语法格式如下所示:
public class FutureTask<V>
extends Object
implements RunnableFuture<V>
使用Callable和Future创建并启动有返回值的步骤有四步,如下所示:
(1)创建Callable接口的实现类,并实现call方法,该call方法将作为线程的执行体,且该call方法有返回值;call方法的格式如下所示:
V call() throws Exception
(2)创建Callable实现类的实例,使用FutureTask类来包装Calllable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程;
(4)调用FutureTask对象的get方法来获得子线程执行结束后的返回值;get方法的格式如下所示:
V get(long timeout, TimeUnit unit)
throws InterruptedException,ExecutionException,TimeoutException
示例代码如下所示:
public class ThirdThread implements Callable<Integer>
{
public static void main(String[] args)
{
// 创建Callable对象
ThirdThread tt = new ThirdThread();
// 使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>(tt);
for (int i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + " 的循环变量值为: " + i);
if (i == 20)
{
// 实质是以Callable对象来创建并启动线程
new Thread(task, "有返回值线程").start();
}
}
try
{
// 获取线程返回值
System.out.println("子线程的返回值: " + task.get());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
}
}
// 实现call方法,作为线程执行体
public Integer call() throws Exception
{
int i = 0;
for (; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + "的循环变量值为:" + i);
}
return i;
}
}
三者的比较
(1)通过这三种方式都可以实现多线程,而通过实现接口的两种方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常;
(2)使用实现Runnable、Callable接口的方式创建多线程时,可以除实现这两个接口,还可以继承其他类;
更好的体现面向对象思想,但是如果需要访问当前线程,则必须使用Thread.currentThread()方法;
(3)使用继承Thread类创建多线程时,不可以再继承其它父类;编写简单,如果需要访问当前线程,不需要使用Thread.currentThread()方法,而是直接使用this关键字便可获得当前线程。
线程的生命周期
当线程被创建并启动时,便表示是开始了线程的生命周期,它要经过新建、就绪、运行、阻塞以及死亡5种状态,具体的生命周期如下图所示:
新建和就绪状态
当程序使用new关键字创建了一个线程以后,该线程处于新建状态;当线程对象调用了start方法后,该线程就是处于就绪状态,Java虚拟机会为其创建方法调用站和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。
如果使用run方法,则run方法就会立即被执行,而且在run方法返回之前其他线程五福并发执行。
注意:start和run方法的区别?
(1)调用start方法可以启动线程,而run方法只是Thread类中的一个普通方法,调用run方法不能实现多线程;
(2)start方法:
a) Start方法用来启动线程,实现多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码;
b) 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行;
c) 一旦得到cpu的执行权,就开始执行run方法,这里run方法称为县城提,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止;
(3)Run方法:
a) Run方法只是Thread类的一个普通方法,如果直接调用run方法,程序中依然只有主线程这个线程,其程序执行路径还是只有一条,要鞥带run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。
运行和阻塞状态
当发生如下几种情况,线程将会进入阻塞状态:
(1)线程调用sleep方法主动放弃所占用的处理器资源;
(2)线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞;
(3)线程试图获得一个同步监测器,但该同步监测器正被其它线程所持有;
(4)线程在等待某个通知(notify);
(5)程序调用了线程的suspend方法将线程挂起了。
当前正在执行的线程被阻塞之后,其他线程便可以获得执行的机会了;被阻塞的进程会在合适的时候重新进入到就绪状态,注意是就绪状态而不是运行状态,也就是说被阻塞线程的阻塞解除后,必须等待线程调度器再次调度它。
与以上五种情况相对应,当发生如下五种特定的情况将可以解除上面的阻塞,让该线程重新进入到就绪状态:
(1)调用sleep方法的线程经过了指定时间;
(2)线程调用的阻塞式IO方法已经返回;
(3)线程成功地获得了失去取得同步监视器;
(4)线程正在等待某个通知时,其他线程发出了一个通知;
(5)处于关起状态的线程被调用了resume恢复方法。
线程死亡
线程会以如下三种方式来结束,结束后便处于死亡状态。
(1)run()方法执行完成,线程正常结束;
(2)线程抛出一个未捕获的Exception或者Error;
(3)直接调用该线程的stop方法来结束该线程——该方法容易导致死锁。
通过调用线程对象的isAlive方法,方法格式如下所示:
public final boolean isAlive()
可以判断线程是否处于活动状态,当线程处于就绪、运行、阻塞三种状态时,该方法将返回true,当线程处于新建、死亡两种状态时,该方法将返回false。
控制线程方法
join线程
Thread类提供了join方法实现了让一个线程等待另一个线程完成的方法,join方法一共有三种重载的形式:
public final void join() throws InterruptedException
等待被join的线程执行完成;
public final void join(long millis) throws InterruptedException
等待被join的线程的时间最长为millis毫秒,如果在millis毫秒内,被join的线程还没有执行结束则不再等待;
public final void join(long millis, int nanos) throws InterruptedException
等待被join的线程的时间最长为millis毫秒加上nanos微秒(千分之一微秒)。
示例代码如下所示:
public class JoinThread extends Thread
{
// 提供一个有参数的构造器,用于设置该线程的名字
public JoinThread(String name)
{
super(name);
}
// 重写run方法,定义线程执行体
public void run()
{
for (int i = 0; i < 100; i++)
{
System.out.println(getName() + "-->" + i);
}
}
public static void main(String[] args) throws InterruptedException
{
// 启动子线程
new JoinThread("新线程").start();
for (int i = 0; i < 100; i++)
{
if (i == 20)
{
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
// main线程调用了jt线程的join方法,main线程必须等jt执行结束才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
后台线程
当一个线程是在后台运行的,并且它的任务是为其他的线程提供服务,这种线程被称为“后台线程”,其特征是如果所有的前台线程都死亡,那么后台线程会自动死亡。
可以通过调用Thread对象的isDaemon方法来测试该线程是否为后台线程,其方法格式如下所示:
public final boolean isDaemon()
可以通过调用Thread对象的setDaemon()方法可将指定的线程设置为后台线程或取消;当正在运行的线程都是后台线程时,Java虚拟机退出。其方法格式如下所示:
public final void setDaemon(boolean on)
注意:当前台线程死亡后,JVM会通知后台线程死亡。将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说setDaemon(true)方发必须在start()方法之前调用。否则将会引发IllegalThreadStateException异常。
线程睡眠
如果需要将正在执行的线程暂停一段时间,并进入阻塞状态,则可以调用Thread对象的sleep方法,该方法有两种重载形式:
public static void sleep(long millis) throws InterruptedException
让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法收到系统计时器和线程调度器的精度和准确度的影响;
public static void sleep(long millis,int nanos)
throws InterruptedException
让当前正在执行的线程暂停millis毫秒加nanos毫秒,并进入阻塞状态,该方法收到系统计时器和线程调度器的精度和准确度的影响;
示例代码如下所示:
public class TestSleep
{
public static void main(String[] args) throws InterruptedException
{
for (int i = 0; i < 10; i++)
{
System.out.println("当前系统时间:" + new Date());
//调用sleep方法让当前线程暂停1s
Thread.sleep(1000);
}
}
}
注意:sleep和wait方法的区别?
(1)这两个方法来自不同的类,sleep方法来自Thread类,而wait方法来自Object类;
(2)Sleep是Thread类的静态类方法,水滴起哦用谁就去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉就要在b的代码块中调用sleep。
而wait()是Object类的非静态方法;
(3)Sleep释放资源不释放锁,而wait释放资源释放锁;
(4)使用范围:wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
线程让步
与sleep方法相似,它可以让当前执行的线程暂停,并执行其它线程,但他不会阻塞给线程。其方法格式如下所示:
public static void yield()
示例代码如下所示:
public class TestYield extends Thread
{
public TestYield()
{}
public TestYield(String name)
{
super(name);
}
//定义run方法作为线程执行体
public void run()
{
for (int i = 0; i < 30; i++)
{
System.out.println(getName() + "-->" + i);
//当i等于20时候,使用yield方法让当前线程让步
if(i == 20)
{
Thread.yield();
}
}
}
public static void main(String[] args)
{
//启动两条并发线程
TestYield ty1 = new TestYield("high");
ty1.start();
TestYield ty2 = new TestYield("low");
ty2.start();
}
}
注意:sleep方法和yield方法的区别如下所示:
(1)sleep方法暂停当前线程后,会给其他线程执行机会,不需要理会其他线程的优先级,但yield方法只会给优先级相同或者优先级更高的线程执行机会;
(2)Sleep方法将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield方法不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态;
(3)Sleep方法声明抛出了IterruptedException异常,因此调用sleep方法时需要捕捉该异常或者抛出该异常。而yield方法则没有声明抛出任何异常。
(4)Sleep方法比yield方法有更好的可移植性,通常不要依靠yield来控制并发线程的执行。
线程优先级
Thread提供了setPriority()和getPriority()来设置和返回指定线程的优先级,其中setPriority方法的参数可以是一个整数(0到10之间)也可以是Thread类的三个静态常量之一,这两个方法的格式以及三个静态常量如下所示:
public final void setPriority(int newPriority)
public final int getPriority()
public static final int MIN_PRIORITY 线程可以具有的最低优先级
public static final int NORM_PRIORITY 分配给线程的默认优先级
public static final int NORM_PRIORITY 线程可以具有的最高优先级
线程同步
线程安全问题
当多个线程使用同一个数据的时候,容易会出现线程安全问题。最经典的案例便是银行取钱问题,示例代码如下所示:
public class Account
{
//封装账户编号、账户余额两个属性
private String accountNo;
private double balance;
public Account(){}
//有参构造器
public Account(String accountNo, double balance)
{
super();
this.accountNo = accountNo;
this.balance = balance;
}
//重新些Account的hashCode和equals方法
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (obj !=null && obj.getClass() == Account.class)
{
Account target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
//accountNo和balance两个属性的setter和getter方法
......
}
public class DrawThread extends Thread
{
//模拟用户账户
private Account account;
//当前取钱线程所取得的签署
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run()
{
//当账户余额大于取钱数目
if (account.getBalance() >= drawAmount)
{
System.out.println(getName() + "成功取出: " + drawAmount + " 元!");
//修改余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("账户余额为:" + account.getBalance() + " 元!");
}
else
{
System.out.println(getName() + "账户余额不足!");
}
}
}
public class TestDraw
{
public static void main(String[] args)
{
//创建一个账户
Account account = new Account("itheima",10000);
new DrawThread("老方",account,8000).start();
new DrawThread("老毕",account,6000).start();
}
}
以上程序运行会出现如下情况:
由上图可以清晰的看出当多个线程共享同一个数据时,程序会运行出错!
synchronized
为了解决如上的问题,Java的多线程引入了同步监视器来解决这个问题,同步监视器分为同步代码块和同步方法两种。
同步的前提:
1)必须保证有两个以上线程;
2)必须是多个线程使用同一个锁,即多条语句在操作线程共享数据;
3)必须保证同步中只有一个线程在执行。
同步的好处和弊端:
1)好处:同步解决了多线程的安全问题;
2)弊端:多线程都是需要判断的,比较消耗资源。
(1)同步代码块
语法格式如下所示:
synchronized(obj)
{
//代码
}
任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。
对上例修改代码如下所示:
public void run()
{
// 使用Account作为同步监视器,任何线程进入下面同步代码块之前都得鲜活的对Account账户的锁定,
//这种做法符合:加锁-->修改完成-->释放锁的逻辑
synchronized (account)
{
// 当账户余额大于取钱数目
if (account.getBalance() >= drawAmount)
{
System.out.println(getName() + "成功取出: " + drawAmount + " 元!");
// 修改余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("账户余额为:" + account.getBalance() + " 元!");
}
else
{
System.out.println(getName() + "账户余额不足!");
}
}
}
(1)同步方法
使用synchronized关键字来修饰某一种方法,则该种方法称为同步方法。对于同步方法而言,无需显式指定同步监视器,同步方法的同步监视器是this,即该对象本身。
使用同步方法可以将某个类编程线程安全的类,线程安全的类具有以下特征:
1) 该类的对象可以被多个线程安全的访问;
2) 每个线程调用该对象的任意方法之后都将得到正确的结果;
3) 每个线程调用该对象的任意方法之后,该对象状态依然保持合理的状态。
对上例修改代码如下所示:
public class Account
{
// 封装账户编号、账户余额两个属性
private String accountNo;
private double balance;
public Account()
{
}
// 有参构造器
public Account(String accountNo, double balance)
{
super();
this.accountNo = accountNo;
this.balance = balance;
}
// balance属性的getter方法
public double getBalance()
{
return this.balance;
}
// 提供一个线程安全draw方法来完成取钱操作
public synchronized void draw(double drawAmount)
{
// 当账户余额大于取钱数目
if (balance >= drawAmount)
{
System.out.println(Thread.currentThread().getName() + "成功取出: " + drawAmount + " 元!");
try
{
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 修改余额
balance = balance - drawAmount;
System.out.println("账户余额为:" + balance + " 元!");
}
else
{
System.out.println(Thread.currentThread().getName() + "账户余额不足!");
}
}
// accountNo属性的setter和getter方法
......
// 重新Account的hashCode和equals方法
......
}
public class DrawThread extends Thread
{
//模拟用户账户
private Account account;
private double drawAmount;
public DrawThread(String name,Account account,double drawAmount)
{
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run()
{
//直接调用account对象draw方法执行取钱
account.draw(drawAmount);
}
}
程序运行结果如下所示:
在以下四种情况下线程会释放对同步监视器的锁定:
(1)当前线程的同步方法或者同步代码块执行结束,当前线程即释放同步监视器;
(2)当线程在同步代码块或者同步方法中遇到break、return终止了该代码块或者该方法的执行,当前线程将会释放同步监视器;
(3)当线程在同步代码块或者同步方法中出现未处理的Error或者Exception时,导致了该代码块或者该方法异常结束时后将会释放同步监视器;
(4)当线程执行同步代码块或者同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器;
在以下两种情况下线程不会释放同步监视器:
(1)线程执行同步代码块或者同步方法时,程序调用Thread.sleep()或者Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;
(2)线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。
同步锁
在JDK1.5之后,Java提供了另外一种线程同步机制:它通过显式定义同步锁对象来实现同步,同步锁应该使用Lock对象来充当。Lock接口的语法格式如下所示:
public interface Lock
Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源先获得Lock对象。在实现线程安全的控制当中,通常使用ReentrantLock(可重入锁),该类的语法格式如下所示:
public class ReentrantLock
extends Object
implements Lock, Serializable
使用Lock对象可以显示加锁和释放锁,通常使用Lock对象的格式如下所示:
class X
{
//定义锁对象
private final ReentrantLock lock = new ReentrantLock();
//定义需要保证线程安全的方法
public void m()
{
//加锁
lock.lock();
try
{
//需要保证线程安全的代码
}
catch (Exception e)
{
// 需要处理的异常
}
//使用finally块来保证释放锁
finally
{
lock.unlock();
}
}
}
将上述案例修改后代码如下所示:
public class Account
{
//定义所对象
private final ReentrantLock lock = new ReentrantLock();
// 封装账户编号、账户余额两个属性
......
// 有参构造器
......
// balance属性的getter方法
......
// 提供一个线程安全draw方法来完成取钱操作
public void draw(double drawAmount)
{
//对同步锁进行加锁
lock.lock();
try
{
// 当账户余额大于取钱数目
if (balance >= drawAmount)
{
System.out.println(Thread.currentThread().getName() + "成功取出: " + drawAmount + " 元!");
try
{
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 修改余额
balance = balance - drawAmount;
System.out.println("账户余额为:" + balance + " 元!");
}
else
{
System.out.println(Thread.currentThread().getName() + "账户余额不足!");
}
}
//使用finally块来保证释放锁
finally
{
lock.unlock();
}
}
// accountNo属性的setter和getter方法
......
// 重新Account的hashCode和equals方法
.......
}
线程通信
传统的线程通信
关于创建线程的三种方式请参阅2.2线程的创建与启动。
在定时器的应用中需要用到两个类,其中Timer类的语法格式如下所示:
public class Timer extends Object
Timer类代表着一种工具,线程用其安排在后台线程中执行的任务,可安排任务执行一次,或这定义重复执行。
在Timer类中关于schedule方法有三种重载方式:
public void schedule(TimerTask task,Date time) 表示安排在指定的时间指定指定的任务。如果此时间已经过去,则安排立即执行任务;
public void schedule(TimerTask task,long delay,long period)表示安排指定的任务从指定的延迟后开始进行重复的固定延迟执行;
public void schedule(TimerTask task,Date firstTime,long period)表示安排指定的任务在指定的时间开始进行重复的固定延迟执行。
而TimerTask类表示由Timer安排为一次执行或者重复执行的任务。TimerTask类的语法格式如下所示:
public abstract class TimerTask
extends Object
implements Runnable
在程序中需要创建类实现TimerTask接口,并且重写run方法,其中run方法格式如下所示:
public abstract void run():此计时器任务要执行的操作。
示例代码如下所示:
public class TraditionalTimerTest
{
private static int count = 0;
public static void main(String[] args)
{
class MyTimerTask extends TimerTask
{
@Override
public void run()
{
count = (count + 1) % 2;
System.out.println("bombing !");
new Timer().schedule(new MyTimerTask(), 2000 + 2000 * count);
}
}
new Timer().schedule(new MyTimerTask(), 2000);
while (true)
{
System.out.println(new Date().getSeconds());
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
读写锁
读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。只要没有Writer,读取锁可以由多个Reader线程同时保持。而且写入锁是独占的。读写锁的语法格式如下所示:
public interface ReadWriteLock
在该接口中有两个重要的方法,如下所示:
Lock readLock() 表示返回用于读取操作的锁;
Lock writeLock() 表示用于写入操作的锁。
示例代码如下所示:
public class ReadWriteLockTest
{
public static void main(String[] args)
{
final Queue3 q3 = new Queue3();
for (int i = 0; i < 3; i++)
{
new Thread()
{
public void run()
{
while (true)
{
q3.get();
}
}
}.start();
new Thread()
{
public void run()
{
while (true)
{
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue3
{
private Object data = null;// 共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
ReadWriteLock rwl = new ReentrantReadWriteLock();
public void get()
{
//给读取操作的锁加锁
rwl.readLock().lock();
try
{
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
//给读取操作的锁解除锁
rwl.readLock().unlock();
}
}
public void put(Object data)
{
//写锁
rwl.writeLock().lock();
try
{
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
Thread.sleep((long) (Math.random() * 1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
rwl.writeLock().unlock();
}
}
}
使用Condition控制
Condition将同步监视器方法分解成截然不同的对象,以便通过这些对象与Lock对象组合使用,为每个对象提供多个等待集。Condition接口的语法格式如下所示:
public interface Condition
Condition实例被绑定在一个Lock对象上。可以通过调用Lock对象的newCondition()方法即可。Condition类提供如下三个方法:
void await() throws InterruptedException
表示当前线程等待,知道其他线程调用该Condition的signal()方法或者signalAll()方法来唤醒该线程。
void signal()
表示在此Lock对象上等待的单个线程。
void signalAll()
表示唤醒在此Lock对象上等待的所有线程。
示例代码如下所示:
public class ConditionCommunication
{
public static void main(String[] args)
{
final Business business = new Business();
new Thread(new Runnable()
{
@Override
public void run()
{
for (int i = 0; i <= 50; i++)
{
business.sub(i);
}
}
}).start();
for (int i = 0; i <= 50; i++)
{
business.main(i);
}
}
static class Business
{
private boolean bShouldSub = true;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void sub(int i)
{
lock.lock();
try
{
while (!bShouldSub)
{
try
{
condition.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int j = 0; j <= 10; j++)
{
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
// 通知主线程
condition.signal();
}
finally
{
lock.unlock();
}
}
public void main(int i)
{
lock.lock();
try
{
while (bShouldSub)
{
try
{
condition.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
for (int j = 0; j <= 10; j++)
{
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
// 通知子线程
condition.signal();
}
finally
{
lock.unlock();
}
}
}
}
使用阻塞队列控制
BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素,如果该队列已经满了,则该线程被阻塞;当消费者线程试图同BlockingQueue中取出元素时候,如果该队列已经空了,则该线程被阻塞。BlockingQueue接口语法格式如下所示:
public interface BlockingQueue<E> extends Queue<E>
以下代码是基于典型的生产者-使用者场景的一个用力。注意,BlockingQueue可以安全地与多个生产者和多个使用者一起使用。
class Producer implements Runnable
{
private final BlockingQueue queue;
Producer(BlockingQueue q)
{
queue = q;
}
public void run() {
try {
while(true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}
class Consumer implements Runnable
{
private final BlockingQueue queue;
Consumer(BlockingQueue q)
{
queue = q;
}
public void run() {
try {
while(true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}
class Setup
{
void main()
{
BlockingQueue q = new SomeQueueImplementation();
Producer p = new Producer(q);
Consumer c1 = new Consumer(q);
Consumer c2 = new Consumer(q);
new Thread(p).start();
new Thread(c1).start();
new Thread(c2).start();
}
}
从以上的代码中可以看出该接口提供两个支持阻塞的方法:
void put(E e) throws InterruptedException
表示尝试把E元素放入BlockingQueue中,如果该队列的元素已满,则阻塞该线程;
E take() throws InterruptedException
表示尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。
以下代表表示用3个空间的队列来演示阻塞队列的功能和效果。
public class BlockingQueueTest
{
public static void main(String[] args)
{
final BlockingQueue queue = new ArrayBlockingQueue(3);
for (int i = 0; i < 2; i++)
{
new Thread()
{
public void run()
{
while (true)
{
try
{
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + "准备放数据!");
queue.put(1);
System.out.println(Thread.currentThread().getName() + "已经放了数据," + "队列目前有" + queue.size()
+ "个数据");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}.start();
}
new Thread()
{
public void run()
{
while (true)
{
try
{
// 将此处的睡眠时间分别改为100和1000,观察运行结果
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "准备取数据!");
queue.take();
System.out.println(Thread.currentThread().getName() + "已经取走数据," + "队列目前有" + queue.size()
+ "个数据");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}.start();
}
}
多线程的单例设计模式
保证某个类中只有一个对象。
(1)饿汉式:
class Single
{
//将构造函数私有化,不让别的类建立该类对象
private Single(){}
//自己建议一个对象
private static final Single s = new Single();
//提供一个公共访问方式
public static Single getInstance()
{
return s;
}
}
(2)懒汉式:
class Single
{
//将构造函数私有化,不让别的类建立该类对象
private Single(){}
//提供一个公共访问方式
public static Single getInstance()
{
if (s == null)
{
s = new Single();
}
return s;
}
}
(3)饿汉式和懒汉式之间的区别:
a) 懒汉式是类一加载进内存就创建好对象;而懒汉式则是在类加载进内存的时候,对象还没有存在,只是在调用了getInstance()方法时,对象才开始创建。
b) 懒汉式是延迟加载,如果多个线程同时操作懒汉式时就有可能出现线程安全问题,解决线程安全问题可以加同步来解决。但是加了同步之后,每一次都要比较锁,效率因此降低,所以可以加双重判断来提高程序的效率。
将上述懒汉式的getInstance函数改写成同步:
class Single
{
//将构造函数私有化,不让别的类建立该类对象
private Single(){}
//提供一个公共访问方式
public static Single getInstance()
{
if (s == null)
{
synchronized(Single.class)
{
if (s == null)
{
s = new Single();
}
}
}
return s;
}
}
线程池&线程相关类
Java5实现的线程池
当程序中需要创建大量生存期很短暂的线程是,应该使用线程池,这样可以很好的提高性能。除此之外,使用线程池可以有效的控制系统中并发线程的数量。在Java 5以后提供了Executors类产生线程池,Executors类的语法格式如下所示:
public class Executors extends Object
该工厂类包含了如下几个静态工厂方法来创建线程池:
public static ExecutorService newCachedThreadPool()
表示创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中;
public static ExecutorService newFixedThreadPool(int nThreads)
表示创建一个可重用的、具有固定线程数的线程池。
public static ExecutorService newSingleThreadExecutor()
表示创建一个只有单线程的线程池,相当于调用newFixedThreadPool(int nThreads)时nThreads的值为1;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建具有指定线程数的线程池,它可以在指定言辞后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也是保存在线程池内。
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
表示创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。
使用线程池来执行线程任务的步骤如下所示:
(1)调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
(2)创建Runnable实现类或者Callable实现类的实例,作为线程执行任务。
(3)调用ExecutorService对象的submit方法来提交Runnable实例或者Callable实例;
(4)当不想提交任何任务时,调用ExecutorService对象所代表的任务。
以下程序代表使用线程池来执行指定Runnable对象所代表的任务。
//实现Runnable接口来定义一个简单的线程类
class MyThread implements Runnable
{
public void run()
{
for (int i = 0; i < 100; i++)
{
System.out.println(Thread.currentThread().getName() + "的i值为:" + i);
}
}
}
public class ThreadPoolTest
{
public static void main(String[] args) throws Exception
{
// 创建一个具有固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(6);
// 想线程池中提交两个线程
pool.submit(new MyThread());
pool.submit(new MyThread());
// 关闭线程池
pool.shutdown();
}
}
以下代码实现固定大小的线程池和缓存线程池,实现步骤如下所示:
1)用3个大小的固定线程池去执行10个内部循环10次就结束的任务,打印出正在执行的线程名、任务序号以及任务内部的循环次数,这10个任务要用个子独立的Runnable对象,才能看到任务的序号;
2)该为缓存线程池,可以看到当前有多少个任务,就会分配多少个线程为之服务。
示例代码如下所示:
public class ThreadPoolTest
{
public static void main(String[] args)
{
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++)
{
final int task = i;
threadPool.execute(new Runnable()
{
@Override
public void run()
{
for (int j = 0; j < 10; j++)
{
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is looping of " + j + " for task of "
+ task);
}
}
});
}
System.out.println("all of 3 tasks have committed!");
Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable()
{
@Override
public void run()
{
System.out.println("bombing");
}
}, 6, 2, TimeUnit.SECONDS);
}
}
Java7实现ForkJoinPool
为了充分利用利用多CPU的性能优势,可以考虑把一个任务拆分成多个“小任务”,然后将多个“小任务”放到多个处理器核心上并行执行。Java 7中提供了ForkJoin类实现此功能,ForkJoinPool是ExecutorService类的实现类,具体语法格式如下所示:
public class ForkJoinPool
extends AbstractExecutorService
在ForkJoin中提供了如下两个常用的构造器:
public ForkJoinPool(int parallelism)
表示创建一个包含parallelism个并行线程的ForkJoinPool。
public ForkJoinPool()
表示以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建ForkJoinPool。
创建了ForkJoinPool实例之后,就可以调用ForkJoinPool的submit(ForkJoinTask<T> task)或者invoke(ForkJoinTask<T> task)方法来执行指定的任务。其中ForkJoinTask代表一个可以并行、合并的任务。
上图显示了ForkJoinPool、ForkJoinTask等类的类图。
public class ForkJoinPoolTest
{
public static void main(String[] args) throws InterruptedException
{
ForkJoinPool pool = new ForkJoinPool();
// 提交可分解的PrintTask任务
pool.submit(new PrintTask(0, 100));
pool.awaitTermination(2, TimeUnit.SECONDS);
// 关闭线程池
pool.shutdown();
}
}
class PrintTask extends RecursiveAction
{
// 每个小任务最多打印50个数
private static final int THRESHOLD = 50;
private int start;
private int end;
// 打印从start到end的任务
public PrintTask(int start, int end)
{
this.start = start;
this.end = end;
}
@Override
protected void compute()
{
// 当end与start之间的差小于THRESHOLD,开始 打印
if (end - start < THRESHOLD)
{
for (int i = start; i < end; i++)
{
System.out.println(Thread.currentThread().getName() + " 的i值为: " + i);
}
}
else
{
// 当end与start之间的差大于THRESHOLD;即要打印的数超过50个时,将大任务分解成两个小任务
int middle = (start + end) / 2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
// 并行执行两个小任务
left.fork();
right.fork();
}
}
}
在本人计算机上运行结果如下所示:
上述程序定义的任务是一个没有返回值的打印任务,如果大任务有返回值的任务,可以让任务继承RecursiveTask<T>,其中个泛型参数T代表了该任务的返回值类型。
//继承RecursiveTask来实现可分解的任务
class CalTask extends RecursiveTask<Integer>
{
//每个“小任务”最多只累加20个数
private static final int THRESHOLD = 20;
private int arr[];
private int start;
private int end;
//累加从start到end的数组元素
public CalTask(int []arr,int start,int end)
{
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute()
{
int sum = 0;
//当end 与start之间的差小于THRESHOLD时,开始进行实际累加
if(end - start < THRESHOLD)
{
for(int i = start;i<end;i++)
{
sum += arr[i];
}
return sum;
}
else
{
// 当end与start之间的差大于THRESHOLD;即要打印的数超过20个时,将大任务分解成两个小任务
int middle = (start + end) / 2;
CalTask left = new CalTask(arr, start, middle);
CalTask right = new CalTask(arr, middle, end);
//并行执行两个小任务
left.fork();
right.fork();
//把两个小任务类家的结果合并起来
return left.join() + right.join();
}
}
}
public class Sum
{
public static void main(String[] args) throws InterruptedException, ExecutionException
{
int[] arr = new int[100];
Random rand = new Random();
int total = 0;
//初始化100个数字的元素
for (int i = 0; i < arr.length; i++)
{
int tmp = rand.nextInt(20);
//对数组元素赋值,并讲数组元素的值添加到total总和中
total += (arr[i] = tmp);
}
System.out.println("total = " + total);
ForkJoinPool pool = new ForkJoinPool();
//提交可分解的CalTask任务
Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));
System.out.println("future get = " + future.get());
//关闭线程池
pool.shutdown();
}
}
以上程序运行时结果如下所示:
ThreadLocal类
线程范围内共享数据的示意图如下所示:
以下代码可以解释线程范围内共享数据的概念。
public class ThreadScopeShareData
{
//全局的静态变量
private static int data = 0;
private static Map<Thread,Integer> threadData = new HashMap<Thread,Integer>();
public static void main(String[] args)
{
for (int i = 0; i < 2; i++)
{
new Thread(new Runnable()
{
@Override
public void run()
{
//为data赋值
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " has put data :" + data);
//将data数据放入线程共享中
threadData.put(Thread.currentThread(), data);
//A、B模块获取共享数据
new A().get();
new B().get();
}
}).start();
}
}
/**
* A模块
* */
static class A
{
public void get()
{
int data = threadData.get(Thread.currentThread());
System.out.println("A from " + Thread.currentThread().getName() + " has put data :" + data);
}
}
/**
* B模块
* */
static class B
{
public void get()
{
int data = threadData.get(Thread.currentThread());
System.out.println("B from " + Thread.currentThread().getName() + " has put data :" + data);
}
}
}
运行结果如下所示:
public class ThreadLocal<T>
extends Object
ThreadLocal类是用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。ThreadLocal类一共提供了以下三种方法:
public T get()表示返回此线程局部变量中当前线程副本中的值。
public void set(T value)表示删除此线程局部变量中当前线程的值。
public void set(T value)表示设置此线程局部变量中当前线程副本中的值。
ThreadLocal类的应用举例:
public class ThreadLocalTest
{
private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
public static void main(String[] args)
{
for (int i = 0; i < 2; i++)
{
new Thread(new Runnable()
{
@Override
public void run()
{
int data = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " has put data :" + data);
x.set(data);
MyThreadScopeData.getThreadInstance().setName("name" + data);
MyThreadScopeData.getThreadInstance().setAge(data);
//A、B模块获取共享数据
new A().get();
new B().get();
}
}).start();
}
}
/**
* A模块
* */
static class A
{
public void get()
{
int data = x.get();
System.out.println("A from " + Thread.currentThread().getName() + " has put data :" + data);
MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
System.out.println("A from " + Thread.currentThread().getName() + " get MyData :" + myData.getName() + ","
+ myData.getAge());
}
}
/**
* B模块
* */
static class B
{
public void get()
{
int data = x.get();
System.out.println("B from " + Thread.currentThread().getName() + " has put data :" + data);
MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
System.out.println("B from " + Thread.currentThread().getName() + " get MyData :" + myData.getName() + ","
+ myData.getAge());
}
}
}
class MyThreadScopeData
{
private MyThreadScopeData()
{
}
public static MyThreadScopeData getThreadInstance()
{
MyThreadScopeData instance = map.get();
if (instance == null)
{
instance = new MyThreadScopeData();
map.set(instance);
}
return instance;
}
private static MyThreadScopeData instance = null;
private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
private String name;
private int age;
//name属性的getter和setter
......
//age属性的getter和setter
......
}
运行结果如下所示:
总结:一个ThreadLocal代表一个变来那个,因此其中只能放一个数据。如果有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。但如果有一百个变量需要线程共享,则先定义一个对象来封装这一百个对象,然后在ThreadLocal中存储这个对象。
Semaphore实现信号灯
public class Semaphore
extends Object
implements Serializable
Semaphore类代表一个计数信号量。通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问。
class Pool
{
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException
{
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x)
{
if (markAsUnused(x)) available.release();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem()
{
for (int i = 0; i < MAX_AVAILABLE; ++i)
{
if (!used[i])
{
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item)
{
for (int i = 0; i < MAX_AVAILABLE; ++i)
{
if (item == items[i])
{
if (used[i])
{
used[i] = false;
return true;
}
else return false;
}
}
return false;
}
}
Semaphore类有两个构造函数,格式如下所示:
public Semaphore(int permits)
表示创建具有给定的许可数和非公平设置的Semaphore。
public Semaphore(int permits,boolean fair)
表示创建具有给定的许可数和给定的公平设置的Semaphore。
该类有两个重要的方法,如下所示:
public void acquire(int permits) throws InterruptedException
表示从此信号量获取给定数目的认可,在提供这些许可前一直将线程阻塞,或者线程已经被中断。获取给定数目的许可之后立即返回,将可用的许可数减去给定的量。
public void release(int permits)
表示释放给定数目的许可,将其返回到信号量。
以下程序表示创建5个线程,使用Semaphore来控制可以访问的资源的线程个数为3个。
public class SemaphoreTest//信号灯测试
{
public static void main(String[] args)
{
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);
for (int i = 0; i < 5; i++)
{
Runnable runnable = new Runnable()
{
public void run()
{
try
{
sp.acquire();
}
catch (InterruptedException e1)
{
e1.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "进入,当前已有"
+ (3 - sp.availablePermits()) + "个并发");
try
{
Thread.sleep((long) (Math.random() * 10000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "即将离开");
sp.release();
// 下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println("线程" + Thread.currentThread().getName() + "已离开,当前已有"
+ (3 - sp.availablePermits()) + "个并发");
}
};
service.execute(runnable);
}
}
}
运行的结果如下所示:
同步集合
Java5中提供了如下一些同步集合类:部分类如下图所示:
其余请查阅文档。
其它同步工具类
(1)CyclicBarrier
public class CyclicBarrier
extends Object
表示大家彼此等待,大家集合好后才开始出发。分散活动后又在指定的地点集合碰面。
以下程序表示用三个线程来实现以上示例:
public class CyclicBarrierTest
{
public static void main(String[] args)
{
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3);
for (int i = 0; i < 3; i++)
{
Runnable runnable = new Runnable()
{
public void run()
{
try
{
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点1,当前已有"
+ (cb.getNumberWaiting() + 1) + "个已经到达,"
+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候"));
cb.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点2,当前已有"
+ (cb.getNumberWaiting() + 1) + "个已经到达,"
+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候"));
cb.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将到达集合地点3,当前已有"
+ (cb.getNumberWaiting() + 1) + "个已经到达,"
+ (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等候"));
cb.await();
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}
运行结果如下所示:
(2)CountDownLatch
public class CountDownLatch extends Object
犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。
以下代表实现了如下功能:实现一个人(也可以是多个人)等待其他所有人都来通知他,可以实现一个人通知多个人的效果。具体代码如下所示:
public class CountdownLatchTest
{
public static void main(String[] args)
{
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(3);
for (int i = 0; i < 3; i++)
{
Runnable runnable = new Runnable()
{
public void run()
{
try
{
System.out.println("线程" + Thread.currentThread().getName() + "正准备接受命令");
cdOrder.await();
System.out.println("线程" + Thread.currentThread().getName() + "已接受命令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果");
cdAnswer.countDown();
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try
{
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令");
cdOrder.countDown();
System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果");
cdAnswer.await();
System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果");
}
catch (Exception e)
{
e.printStackTrace();
}
service.shutdown();
}
}
运行结果如下所示:
(3)Exchange
public class Exchanger<V>extends Object
表示用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
public class ExchangerTest
{
public static void main(String[] args)
{
ExecutorService service = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
service.execute(new Runnable()
{
public void run()
{
try
{
String data1 = "zxx";
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去");
Thread.sleep((long) (Math.random() * 10000));
String data2 = (String) exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
service.execute(new Runnable()
{
public void run()
{
try
{
String data1 = "lhm";
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去");
Thread.sleep((long) (Math.random() * 10000));
String data2 = (String) exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
运行结果如下所示: