一、线程的同步
- 对共享对象的访问必须同步,叫做条件变量.
- Java语言允许通过监视器(有的参考书称其为管程)使用条件变量实现线程同步.
- 监视器阻止两个线程同时访问同一个条件变量.它的如同锁一样作用在数据上.
- 线程1进入withdrawal方法时,获得监视器(加锁);当线程1的方法执行完毕返回时,释放监视器(开锁),线程2的withdrawal方能进入.
二、多线程问题—资源协调
- 用synchronized来标识的区域或方法即为监视器监视的部分。
一个类或一个对象有一个监视器,如果一个程序内有两个方法使用synchronized标志,则他们在一个监视器管理之下.
般情况下,只在方法的层次上使用关键区
class Account
{
static int balance=1000;
static int expense=0;
public synchronized void withdrawl(int amount)
{ if (amount<=balance)
{ balance-=amount;
expense+=amount;}
else
{ System.out.println(“bounced: “+amount);}
}
}
三、死锁问题
- 如果你的持有一个锁并试图获取另一个锁时,就有死锁的危险.
- 死锁可以通过以下方法来避免
-决定获取锁的次序
-始终遵照这个次序
-按照相反的次序释放锁
四、生产者和消费者问题
- 生产者和消费者模型是典型的线程同步问题,下面我们通过这个模型来说明线程同步的处理方法
使用某种资源的线程称为消费者,产生或释放这个资源的线程称为生产者。生产者生成10个整数(0~9),存储到一个共享对象中,并把它们打印出来。每生成一个数就随机休眠0~100毫秒,然后重复这个过程。一旦这10个数可以从共享对象中得到,消费者将尽可能快地消费这10个数,即把它们取出后打印出来。
(1)生产者程序
public class Producer extends Thread {
private Share shared;
private int number;
public Producer(Share s, int number) {
shared=s;
this.number=number;
}
public void run( ) {
for (int i=0; i<10; i++) {
shared.put(i);
System.out.println(″生产者″+this.number+″输出的数据为: ″+i);
try {
sleep((int)(Math.random( ) * 1000));
} catch (InterruptedException e) {}
}
}
}
(2) 消费者程序
public class Consumer extends Thread {
private Share shared;
private int number;
public Consumer(Share s, int number) {
shared=s;
this.number=number;
}
public void run( ) {
int value = 0;
for (int i=0; i<10; i++) {
value=shared.get( );
System.out.println(″消费者″+this.number+″得到的数据为 : ″+value);
}
}
}
(3) 共享资源对象
public class Share {
private int contents;
public int get( ){
return contents;
}
public void put(int value){
contents=value;
}
}
(4) 主程序
public class PCTest {
public static void main(String[] args) {
Share s=new Share( );
Producer p=new Producer(s,1);
Consumer c=new Consumer(s,1);
p.start( );
c.start( );
}
}
运行结果如下图所示
- 我们分析一下可能发生的情况:一种情况是生产者比消费者速度快,那么在消费者还没有取出上一个数据之前,生产者又存入了新数据,于是,消费者很可能会跳过上一个数据。另一种情况则相反,当消费者比生产者速度快,消费者可能两次取出同一个数据。
- 这两种情况不是我们所希望的。我们希望生产者存入一个数,消费者取出的就是这个数。为了避免上述情况发生,就必须锁定生产者线程,当它向共享对象中存储数据时禁止消费者线程从中取出数据,反之也一样。将共享对象Share中的put和get分别定义为同步化方法就可达到这个目的。
public class Share {
private int contents;
private boolean available=false;
public synchronized int get( ) {
while (available==false) {
try {
wait( );
} catch (InterruptedException e) { }
}
available=false;
notifyAll( );
return contents;
}
public synchronized void put(int value) {
while (available==true) {
try {
wait( );
} catch (InterruptedException e) { }
}
contents=value;
available=true;
notifyAll( );
}
}
- 修改后的Share仍利用put和get方法来写入和读取数据,但增加了wait和notifyAll功能。wait使线程进入短暂休眠,收到notifyAll的通知后会马上醒来。当消费者线程调用共享对象的get方法时,如果生产者没有写入数据,available变量就会保持为假,线程进入循环并调用wait方法等待。一旦生产者写入了新数据,available的值就会改变,同时生产者还会向消费者发出通知,唤醒消费者线程退出循环。此时,消费者线程将做两个非常重要的工作,一是把available变量改为假,二是通知生产者线程。最后,返回contents,它包含最新写入的数据。
当生产者线程第一次调用共享对象的put方法时,available变量为假,线程将跳过循环并将第一个数据写入contents变量,然后将available变量改为真值,调用notifyAll方法通知消费者线程可以取数据了。再次调用put方法时,如果消费者没有取走数据,available变量就会保持为真,线程将进入循环并调用wait方法等待。一旦消费者取走上一个数据,available的值就会改变,线程也会被唤醒并退出循环,继续后面的工作。采用这样的处理方式,就可以保证消费者一直等到生产者写入一个新数据后再把它取出,而生产者则一直等到消费者取走上一个数据后再写入新数据。
五、多线程问题
对多线程程序本身来说,它会对系统产生以下影响:
1.线程需要占用内存。
2.线程过多,会消耗大量CPU时间来跟踪线程。
3.必须考虑多线程同时访问共享资源的问题,如果没有协调好,就会产生令人意想不到的问题,例如可怕的死锁和资源竞争。
4.因为同一个任务的所有线程都共享相同的地址空间,并共享任务的全局变量,所以程序也必须考虑多线程同时访问全局变量的问题。