详解一道多线程笔试题(考察wait())

原题大概是这么个意思。
一个线程A 执行setJob方法(设为方法1),一个线程B执行getJob方法(设为方法2),问下面这段代码有啥问题。

    Object lock = new Object();
    Object job;
 	public  void setJob(Object job){
        this.job = job;
        synchronized (lock){
            
            lock.notifyAll();
        }
    }
    public Object getJob(){
        Object job = this.job;
        if(job==null){
            synchronized (lock){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return null;
                }
            }

        }
        return job;
    }

**分析:**这里很明显是个整个等待通知机制,想要当A线程对job赋值后,B线程读取job。
写个测试代码看看:

public static void main(String[] args) throws InterruptedException {
        Thread t1,t2;

        for(int i=0;i<100;i++){
            test t = new test();
            t1 = new Thread(()->t.setJob(
            Thread.currentThread().getName()),
            String.valueOf(i));
            t2 = new Thread(()->{System.out.println(
            t.getJob());});
            t1.start();
            t2.start();
            t1.join();
            t2.join();
        }
    }

在这里插入图片描述
很明显不符合预期啊。为啥会出现这个问题呢?没按照设想的来啊。
有两种可能。

  • 线程A对job对象的写对线程B写不可见。
  • 线程B中的判断无用或者二次读值失败

1.
第一个问题很好解决,给job变量加一个volatile关键字即可,使用内存屏障(写读屏障),迫使读线程去主存获取job的引用,迫使写线程将值写入主存。

加了后会发现还是有null的情况。其实稍作思考就知道,问题不在这,测试代码里赋值操作的执行内容很少,就一个赋值操作不存在内部重排序,而且本身线程并不执行其他操作很快就会主动刷新到主存中,而且线程B也仅仅有一次获取。

不过这是个隐患,所以对于这种需要写读分离且在双方的执行操作都相对复杂的时候(比如一个线程多次读的过程中(比如循环),有写操作发生,而且需要及时感知),较长的变量最好还是加上volatile关键字。

2.
那就锁定到第二个问题上了,这个问题还要从wait方法说起,从wait状态中恢复后会从哪里开始执行呢?

事实上,它被唤醒后,并非重新进入方法(估计出题人的意思就是这个),该线程会重新尝试获取锁,然后从调用wait()方法的下一个指令开始执行(由线程独占的程序计数器实现)。

也就是说方法2恢复后不会再判断一遍if也不会执行一遍if判断也不会将值再读一遍,现在仍然是写入前读取的值,为null。

解决方式: 将wait()之后再添加一个赋值语句。(如果还有其他非写线程的唤醒操作,就再将if换成while)

引申:
如果想要实现多个AB线程之间的交替读写呢?
交替读写自然就需要线程之间相互通信。
只有一个A线程更新了值一个B线程才能去读取。
只有当一个B线程读完了才能去写值。

	Object lock = new Object();
    String s;
    volatile boolean flag =true;
    public  String setSrting(String s){
        synchronized (lock) {
            while (!flag) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            flag =false;
            this.s = s;
            String ss = this.s;
            lock.notifyAll();
            return ss;
        }
    }

    public String getString(){
           synchronized (lock){
               while (flag){
                   try {
                       lock.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
               flag = true;
               String ss = new String(s);
               lock.notifyAll();
               return ss;
           }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值