黑马程序员-多线程部分(三.等待唤醒机制)

本文介绍了线程间同步和通信的基本概念,通过具体案例详细解释了如何使用synchronized关键字和Lock接口实现线程间的等待-通知机制,有效解决多线程环境下资源共享问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 ——- android培训java培训、期待与您交流! ———-

对于操作同一资源的不同行为,该如何解决?

思路:因为两个线程操作的是同一资源,为避免出现数据安全问题,必须实现同步,且设置的同步锁对象一致,由于是两个线程的两种行为,可以将资源类名.class作为同步锁传入。

例子一:一边输入姓名和性别,一边输出。
定义资源类,包括姓名,性别,两个属性。

class Resource
{
     String name;
     String sex;
}

输入类,实现Runnable接口,作用,轮流输入两个人的姓名和性别。

class Input implements Runnable
{   private Resource resource;
    public Input(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   int i=1;
        while(true)
        {
            if(i%2==0)
            {
                resource.name = "mack";
                resource.sex = "male";  
            }else
            {
                resource.name = "莉莉";
                resource.sex = "女女女女女女";
            }
            i++;
        }

    }
}

输出类:实现Runnable接口,作用,输出资源里的姓名和性别。

class Output implements Runnable
{   private Resource resource;
    public Output(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   while(true)
        {
            System.out.println(resource.name+"....."+resource.sex);
        }

    }
}

在主函数中实例化并调用两个线程

public class ThreadDemo
{
    public static void main(String args[])
    {
        Resource resource = new Resource();
        Input input = new Input(resource);
        Output out = new Output(resource);
        Thread t1 = new Thread(input);
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    }
}

在未执行同步操作之前,输出结果为:
这里写图片描述
发现出现了数据异常,并没有同步输出对应的名字和性别。因此需要使用同步。
同步之后代码块如下:

class Input implements Runnable
{   private Resource resource;
    public Input(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   int i=1;
        while(true)
        {
            synchronized(Resource.class)
            {
                if(i%2==0)
                {
                    resource.name = "mack";
                    resource.sex = "male";  
                }else
                {
                    resource.name = "莉莉";
                    resource.sex = "女女女女女女";
                }
                i++;
            }

        }

    }
}


class Output implements Runnable
{   private Resource resource;
    public Output(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   while(true)
        {
            synchronized(Resource.class)
            {
                System.out.println(resource.name+"....."+resource.sex);
            }
        }

    }
}

运行结果如下:
这里写图片描述

应用同步解决了数据混乱的问题。
但是输出方式却是连着输出,而不符合轮流输出的效果。此时需要在输入之后,让输入线程等待,让输出线程取出之后,唤醒输入线程,同时输出线程等待,如此循环,能到达轮流输出的效果。

这就是重点:

线程间的通信,等待唤醒机制

(一)利用Synchronized同步来实现

修改上述代码:
共享资源项:

class Resource
{
     String name;
     String sex;
     boolean flag = true;
}

输入:

class Input implements Runnable
{   private Resource resource;
    public Input(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   int i=1;
        while(true)
        {
            synchronized(Resource.class)
            {   if(resource.flag)
                {
                    try{Resource.class.wait();}catch(Exception e){}
                }
                if(i%2==0)
                {
                    resource.name = "mack";
                    resource.sex = "male";  
                }else
                {
                    resource.name = "莉莉";
                    resource.sex = "女女女女女女";
                }
                i++;
                resource.flag = true;
                Resource.class.notify();
            }

        }

    }
}

输出:

class Output implements Runnable
{   private Resource resource;
    public Output(Resource resource)
    {
        this.resource = resource;
    }

    public void run()
    {   while(true)
        {
            synchronized(Resource.class)
            {   
                if(!resource.flag)
                {
                    try{Resource.class.wait();}catch(Exception e){}
                }
                System.out.println(resource.name+"....."+resource.sex);
                resource.flag = false;
                Resource.class.notify();
            }
        }
    }
}

运行结果如下:
这里写图片描述

现在实现了轮流输出的效果。

上面讲述的是一个输入一个输出,假如两个生成者,两个消费者,看看结果如何?

例子二:两个生成者,两个消费者,开启四个线程。

class Resource
{
    private String name;
    private int count;
    boolean flag = true;
    public synchronized void set(String name)
    {   
        if(flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        this.name = name+"......."+count++;
        System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
        flag = true ;
        this.notify();
    }
    public synchronized void out()
    {   
        if(!flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
        flag = false ;
        this.notify();
    }
}

class Producer implements Runnable
{
    private Resource resource;
    public Producer(Resource resource)
    {
        this.resource = resource;
    }
    public void run()
    {
        while(true)
        {
            resource.set("张三");
        }

    }
}
class Consumer implements Runnable
{
    private Resource resource;
    public Consumer(Resource resource)
    {
        this.resource = resource;
    }
    public void run()
    {
        while(true)
        {
            resource.out();
        }

    }
}

public class ProducerConsume
{
    public static void main(String args[]) throws Exception
    {
        Resource resource = new Resource();
        Producer pro = new Producer(resource);
        Consumer con = new Consumer(resource);
        Thread t1 = new Thread(pro);
        Thread t2 = new Thread(pro);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

运行结果如下:
这里写图片描述
可以看出:出现了问题,当生产者生产出好几个,但是消费者都未消费掉,是因为当第一个生产者生产完,首先被唤醒的是第二个生产者,而它未经过判断标志位就开始生产,从而产生了这种情况。

修改办法:
1)把Resource类中同步函数部分中,把if换成while(避免唤醒后不用判断);
2)而用whlie可以实现循环判断,四个线程会同时出现等待状态,为避免这种情况,修改notify为notifyAll来实现。

class Resource
{
    private String name;
    private int count;
    boolean flag = true;
    public synchronized void set(String name)
    {   
        while(flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        this.name = name+"......."+count++;
        System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
        flag = true ;
        this.notifyAll();
    }
    public synchronized void out()
    {   
        while(!flag)
        {
            try{this.wait();}catch(Exception e){}
        }
        System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
        flag = false ;
        this.notifyAll();
    }
}

运行结果如下:
这里写图片描述

(二)JDK1.5之后Lock类

JDK1.5之后同步的新优点:
第一点:把隐性的synchronized同步锁变成了可见的Lock代替,上锁lock和解锁unlock。
第二点:把Object(可操作wait,notify,notifyAll)同步锁换成了Condition接口(await,signal,signalAll),此接口可以通过Lock类获得newCondition。
第三点:最大的优点,一个锁同时可以拥有许多个condition对象,可以有选择性的唤醒个别线程。
备注:必须注意同步最后需要解锁,此类最大的好处是只定义一个锁,就可以有好几个condition,避免了synchronized定义多个锁引起的同步嵌套,避免了死锁的问题。实现了只唤醒对方线程的优点。
例子:改写上述两个生产和两个消费过程的实例,

import java.util.concurrent.locks.*;

class Resource
{
    private String name;
    private int count;
    Lock lock = new ReentrantLock();
    Condition conditionPro = lock.newCondition();
    Condition conditionCon = lock.newCondition();
    boolean flag = true;
    public void set(String name) throws Exception
    {   
        lock.lock();
            try
            {   
                while(flag)
                conditionPro.await();
                this.name = name+"......."+count++;
                System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
                flag = true ;
                conditionCon.signal();
            }finally
            {
                lock.unlock();
            }


    }
    public void out() throws Exception
    {   
        lock.lock();
            try
            {
                while(!flag)
                    conditionCon.await();
                System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
                flag = false ;
                conditionPro.signal();
            }finally
            {
                lock.unlock();
            }

    }
}

运行结果如下:
这里写图片描述

完美的解决了问题。比起synchronized更直观,更清楚的让人理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值