java多线程实现线程同步

java多线程实现线程同步

Posted by dym on January 19, 2014

本文迁移自老博客,原始链接为 https://seven.blog.ustc.edu.cn/java%e5%a4%9a%e7%ba%bf%e7%a8%8b%e5%ae%9e%e7%8e%b0%e7%ba%bf%e7%a8%8b%e5%90%8c%e6%ad%a5/

由于Java支持多线程,具有并发功能,从而大大提高了计算机的处理能力。在各线程之间不存在共享资源的情况下,几个线程的执行顺序可以是随机的。但是,当两个或两个以上的线程需要共享同一资源时,线程之间的执行次序就需要协调,并且在某个线程占用这一资源时,其他线程只能等待。例如生产者和消费者的问题,只有当生产者生产出产品并将其放入货架时,消费者才能从货架上取走产品进行消费。当生产者没有生产出产品时消费者是没法消费的。同理,当生产者生产的产品堆满货架时应该暂停生产等待消费者消费。在程序设计中,可用两个线程分别代表这里的生产者和消费者,可将货架视为任一时刻只允许一个线程访问的临界资源。 在这个问题中,两个线程要共享货架这一临界资源,需要在某些时刻(货空/货满)协调它们的工作,即货空时消费者应等待,而货满时生产者应等待。为了不致于发生混乱,还可进一步规定:当生产者往货架上放货物时不允许消费者取货物,当消费者从货架上取货物时不允许生产者放货物。这种机制在操作系统中称为线程间的同步。在同步机制中,将那些访问临界资源的程序段称为临界区。 在Java系统中,临界区程序段是用关键字“synchronized”来标注,并通过一个称为监控器的系统软件来管理的。当执行被冠以synchronized的程序段即临界区程序时,监控器将这段程序(访问的临界资源)加锁,此时,称该线程占有临界资源,直到这段程序执行完,才释放锁。只有锁被释放后,其他线程才可以访问这些临界资源。用关键字synchronized定义临界区的语句形式是: synchronized  (expression)  statement   其中,expression代表类的名字,它是可选项;statement可以是一个方法,也可以是一个语句或一个语句块,最常见的是一个方法。下面通过一个例子来说明线程的同步问题。

public class thread
{
    public static void main(String [ ] args)
    {
        HoldInt h=new HoldInt( );  //h为监控器
        ProduceInt p=new ProduceInt(h);
        ConsumeInt c=new ConsumeInt(h);
        p.start( );
        c.start( );
    }
}
class HoldInt
{
    private int sharedInt;
    private boolean writeAble=true;  //writeAble==true表示生产者线程能生产新数据
    public synchronized void set(int val)  //临界区程序段,也称为同步方法
    {
        while(!writeAble)
        {
        //生产者线程不能生产新数据时进入等待
             try {wait( );}
             catch(InterruptedException e){}
        }
        //生产者被唤醒后继续执行下面的语句
        writeAble=false;
        sharedInt=val;
        notify( );
    }
    public synchronized int get( )  //同步方法
    {
        while(writeAble)
        {
        //消费者线程不能消费数据时进入等待状态
            try
            {
                wait( );
            }catch(InterruptedException e){}
        }
        //消费者被唤醒后继续执行下面的语句
        writeAble=true;
        notify( );
        return sharedInt;
    }
}
//ProduceInt 是生产者线程
class ProduceInt extends Thread {
    private HoldInt hi;
    public ProduceInt(HoldInt hiForm)
    {
        hi=hiForm;
    }
    public void run( )
    {
        for(int i=1;i<=4;i++)
        {
            hi.set(i);
            System.out.println("产生的新数据是: "+ i);
        }
    } 
}
//ConsumeInt 是消费者线程
class ConsumeInt extends Thread {
    private HoldInt hi;
    public ConsumeInt(HoldInt hiForm)
    {
        hi=hiForm;
    }
    public void run( )
    {
        for(int i=1;i<=4;i++) 
        {
            int val=hi.get( );
            System.out.println("读到的数据是: "+ val);
        }
    }
}

在这个程序中,共享数据sharedInt的方法set( )和get( )的头部的修饰符synchronized 使HoldInt的每个对象都有一把锁。当ProduceInt对象调用set( )方法时,HoldInt对象就被锁定。当set( )方法中的数据成员writeAble值为true时,set( )方法就可以向数据成员sharedInt中写入一个值,而get( )方法不能从sharedInt上读出值。如果set( )方法中的writeAble的值为false,则调用set( )方法中的wait( )方法,把调用set( )方法的ProduceInt对象放到HoldInt对象的等待队列中,并将HoldInt对象的锁打开,使该对象的其他synchronized方法可被调用。这个ProduceInt对象将一直在等待队列中等待,直到被唤醒使它进入就绪状态,等待分配CPU。当ProduceInt对象再次进入运行状态时,HoldInt对象就被隐含地锁定,而set( )方法将继续执行while循环中wait( )方法后面的语句。在本例中wait( )方法后面无其他语句,因此将进入下一次循环,判断while条件。ConsumeInt 对象调用get( )方法与ProduceInt对象调用set( )方法的情况类似,不再详述。 该程序的运行结果如下: 产生的新数据是:1 产生的新数据是:2 读到的数据是:1 产生的新数据是:3 读到的数据是:2 产生的新数据是:4 读到的数据是:3 读到的数据是:4