10.4:线程互斥
在并发程序设计中已经被研究并得到解决.对多线程共享的资源或数据称为临界资源,而把每一个线程中访问临界资源的那一段代码称为临界代码.通过为临界带买段设置信号灯,就可以保证资源的完整性,从而安全地访问共享资源.
为了实现这种机制,Java语言提供了以下两方面的支持
为每个对象设置了一个”互斥锁”标记.该标记保证在任何时候,只能有一个线程有该互斥锁,其他线程如果需要获得互斥锁,必须等待当前拥有该锁的线程将其释放.该对象称为互斥对象.
为了配合使用对象的互斥锁,Java语言提供了保留字synchronized.其基本用法如下:
Synchronized(互斥对象){
临界代码
}
当一个线程执行到该行代码时,首先检测该互斥对象的互斥锁.如果该互斥锁没有被占用,则该线程将获得该互斥锁,并执行临界代码,直到执行完毕并释放互斥锁;
可以看出,任意一个对象都可以作为信息灯,从而解决上面的问题,首先定义一个互斥对象类,作为信号灯.由于该对象只作为信号量使用,所以并不需要为它定义其他方法.
public class AccountThread extends Thread
{
public static void main(String[] args)
{
Account account = new Account(100);
Semaphore semaphore = new Semaphore();
AccountThread at1 = new AccountThread(account, 1000, semaphore);
AccountThread at2 = new AccountThread(account, 0, semaphore);
at1.start();
at2.start();
}
Account account;
int delay;
Semaphore semaphore;
// 构造方法
public AccountThread(Account account, int delay, Semaphore semaphore)
{
this.account = account;
this.delay = delay;
this.semaphore = semaphore;
}
@Override
public void run()
{
synchronized (this.semaphore)
{
if (this.account.balance >= 100)
{
try
{
// 延迟
sleep(this.delay);
// 模拟取钱100
this.account.balance = this.account.balance - 100;
System.out.println("withdraw 100 successful!!!");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
else
{
System.out.println("withdraw failed!!!");
}}}}
// 定义一个类,利用其对象作为互斥信号灯
class Semaphore{}
10.5:线程同步
在实际应用中,多个线程之间不仅需要互斥机制来保证对共享数据的完整性,而且有时需要多个线程之间的相互合作,一个典型的应用称为 生产者-消费者模型
1:生产者负责产品,并将其保存到仓库中
2:消费者从仓库中获取产品
3:由于库房容量有限,因此只有当库房还有空间时,生产者才可以将产品存入库房,否则只能等待
4:只有库房中存在满足数量的产品时,消费者才可以取走产品否则只能等待
Java语言为互斥对象提供了两个方法:一个是wait(),一个是notify() 这两个方法必须同时使用
wait()方法的语义是:当一个线程执行了该方法后,则该线程进入堵塞状态,同时让出同步对象的互斥锁,并自动进入互斥对象的等待队列
Notify()方法的语义是:当一个线程执行了该方法后,则拥有该方法的互斥对象的等待队列中的第一个线程被唤醒,同时自动获得该互斥对象的互斥锁,并进入就绪状态等待调度
10.6 线程通信
线程之间的通信问题是指线程之间互相传递信息,这些信息包括数据,控制指令.线程至之间通过管道进行通信
管道的特点?
1:管道是单向的.一个线程充当发送者,另一个线程充当接受者.如果需要建立双向通信,可以通过建立多个管道解决
2:管道通信是面向连接的.因此在程序设计中,一方线程必须建立起对应的端点,由另一方线程来建立连接.
3:官道中的通信是严格按照发送的顺序进行传送的.
10.7 线程死锁
线程死锁是并发程序设计中可能遇到的问题之一.它是指程序运行过程中,多个线程竞争共享资源时可能出现的一种系统状态.
出现线程死锁的必须同时具备的4个条件:
1:互斥条件.即至少存在一个资源,不能被多个线程同时共享.
2:至少存在一个线程,他拥有一个资源,并等待获得另一个线程当前锁拥有的资源.
3:线程拥有的资源不能被强制剥夺,只能由线程释放.
4:线程对资源的请求形成了一个圆环.
10.8 线程池
因为创建和清除线程垃圾都会大量占用CPU等系统资源,所以可以使用线程池来解决资源浪费的问题.
线程池的思想是:在系统中开辟一块区域,其中存放一些待命的线程,这个区域称为线程池,如果需要执行任务,则从线程池中”取”一个待命的线程来执行指定的任务,等到任务结束可以再将所取的线程放回.这样就避免上面所说的问题.常用的线程池有 固定尺寸线程池和可变尺寸线程池,这种线程池中待命的数量是根据任务负载的需要动态变化的.
public class FixText implements Runnable
{
public static void main(String[] args)
{
// 创建尺寸为2的固定线程池
ExecutorService threadpool = Executors.newFixedThreadPool(2);
// 创建3个任务对象
FixText ft1 = new FixText("FT1");
FixText ft2 = new FixText("FT2");
FixText ft3 = new FixText("FT3");
// 启动三个任务执行
threadpool.execute(ft1);
threadpool.execute(ft2);
threadpool.execute(ft3);
threadpool.shutdown();
}
private String name;
public FixText(String name)
{
this.name = name;
}
public void run()
{
System.out.println(" --------" + this.name + "开始执行");
for (int i = 0; i < 50; i++)
{
System.out.print("[" + this.name + "]");
}
System.out.println(" --------" + this.name + "执行结束");
}}