Java多线程同步
对于多线程的同步,大家就牢记并学会一个关键字synchronized就OK了,很简单。
首先,理解同步。当多个线程需要共享资源时,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronization)。由于多线程中的每个线程是单一的顺序控制流程,但它们共享一个进程的内存。这就产生一个问题:当多个线程同时操作一个进程内存中的对象时,产生不确定情况。为了协调多个线程,就形成一个约定:我用的时候你不要用,等着,当我用完,你再用。于是产生了多线程的同步。
同步的关键是管程(也叫信号量semaphore)的概念。管程是一个互斥独占锁定的对象,或称互斥体(mutex)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定,它必须进入管程,所有其他的试图进入已经锁定的管程必须挂起直到第一个线程退出管程,这些其他的线程被称为等待管程。一个拥有管程的线程如果愿意的话可以再次进入相同的管程。
以上是书面用语,打个比方,管程就是一把钥匙,而且是唯一的一把。线程好比一个人,把管程所对应的对象看成一个只能容纳一人的房间。OK,当一个”人“获得了“该房间”的“钥匙”后,进入“房间”内,其他”人“必须在“房间”外等候。只有当之前”那个人“走出房间,“钥匙”交给”下一个人“,”下一个人“才能进去。当然,如果上一个人不想交出钥匙,那他就完全可以不交,待在房间就行。
Java语言中同步很简单。因为所有对象都有它们与之对应的隐式管程。进入某一对象的管程,就是调用被synchronized关键字修饰的方法。当一个线程在一个同步方法内部,所有试图调用该方法的同实例的其他线程必须等待。为了退出管程,并放弃对对象的控制权给其他等待的线程,拥有管程的线程仅需从同步方法中返回。最经典例子:生产者与消费者问题,如下:
package 多线程同步;
/*
* 创建生产者类,实现Runnable接口
*/
public class Producer implements Runnable {
//创建生产商名字,生产仓库
private String producerName = null;
private StoreHouse storeHouse = null;
// 通过构造方法传入生产商名字,生产仓库
public Producer(String producerName,StoreHouse storeHouse){
this.producerName=producerName;
this.storeHouse=storeHouse;
}
// 设置生产商名字
public void setProducerName(String producerName){
this.producerName=producerName;
}
// 返回生产商名字
public String getProducerName(){
return producerName;
}
// 生产商品
public void produceProduct(){
int i=0;
while(true){
i++;
Product pro = new Product(i);//生产产品
storeHouse.push(pro); //将产品放入仓库
System.out.println(getProducerName()+"生产了"+pro);//打印生产信息
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
return;
}
}
}
public void run() {
produceProduct();
}
}
package 多线程同步;
/*
* 创建顾客类,实现Runnable接口
*/
public class Consumer implements Runnable {
private String consumerName = null;//创建顾客名
private StoreHouse storeHouse = null;//创建仓库
//通过构造方法传入顾客名,仓库
public Consumer(String consumerName,StoreHouse storeHouse){
this.consumerName=consumerName;
this.storeHouse=storeHouse;
}
//设置顾客名
public void setConsumerName(String consumerName){
this.consumerName=consumerName;
}
//返回顾客名
public String getConsumerName(){
return consumerName;
}
//消费商品
public void consumerProduct(){
while(true){
System.out.println(getConsumerName()+"消费了"+storeHouse.pop());//打印消费信息
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
return;
}
}
}
public void run() {
consumerProduct();
}
}
package 多线程同步;
/*
* 创建商品类
*/
public class Product {
private int productId = 0; //创建商品ID
// 通过构造方法传入商品ID
public Product (int productId){
this.productId=productId;
}
// 返回商品ID
public int getProductId(){
return productId;
}
// 将ID转换为字符串形式
public String toString(){
return Integer.toString(productId);
}
}
package 多线程同步;
/**
*
* 创建仓库类
*
*/
public class StoreHouse {
//创建两计数变量和设置仓库库存大小
private int base=0;
private int top=0;
private Product[] products=new Product[10];
// 入库方法
public synchronized void push(Product product) {
//判断库存是否已满
while(top==products.length){
notify();//库存已满,唤醒顾客(消费)线程
try {
System.out.println("仓库已满,正等待消费...");
wait();//使生产线程进入等待队列状态
} catch (InterruptedException e) {
System.out.println("stop push product because other reasons");
}
}
products[top] = product;//仓库未满,将产品入库,计数变量+1
top++;
}
// 出库方法
public synchronized Product pop(){
Product pro= null;
//判断仓库是否有商品
while(top==base){
notify();//仓库已空,唤醒生产线程
try {
System.out.println("仓库已空,正等待生产...");
wait();//使消费线程进入等待队列状态
} catch (InterruptedException e) {
System.out.println("stop pop product beacuse other reasons");
}
}
top--;
pro = products[top];//将产品出库
products[top]= null;
return pro;
}
}
package 多线程同步;
public class Synch {
public static void main(String[] args) {
StoreHouse storeHouse = new StoreHouse();
Producer producer = new Producer("生产者",storeHouse);
Consumer consumer = new Consumer("消费者",storeHouse);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
需要注意死锁问题:
如果生产的速度大于消费的速度就会导致供大于求,仓库很容易就满了,然而生产者又一直关着仓库不放,没有机会给消费者使用,消费者不消费生产者就无法生产,所以就造成了死锁。解决的方法是在两个同步互斥方法中使用wait()和notify()方法。
wait()是Object类的方法,它的作用是拥有互斥锁的线程放弃锁的使用权,进入wait池进 等待,那么互斥锁就有可能被其他线程获得以执行其他任务。Notify()也是Object类的方法它的作用是从wait池中唤醒一条正在等待的线程进入就绪状态,被唤醒的这条线程就很可能重新获得CPU和互斥锁来完成它的任务。OK,现在我们再回过头来看上一次博客关于线程的状态中的(4),(5),就不难理解了。
(注,本文资源来自清华大学出版社《高级程序语言(Java版)》,本文只是交流学习,无任何商业目的)