美文网首页
synchronized和monitor使用案例

synchronized和monitor使用案例

作者: nextliving | 来源:发表于2018-04-21 22:16 被阅读25次

    多线程编程是开发高并发应用的重点和难点,是许多互联网公司面试环节必不可少的部分.打算围绕多线程编程总结一些核心概念及它们之间的关系,本篇是synchronized和监视器(monitor)的使用.

    监视器

    监视器简介

    查阅Oracle官网文档Object部分,可以看到所有对象都有这么几个方法:

    • notify()

    • notifyAll()

    • wait()

    • wait(long timeout)

    • wait(long timeout, int nanos)

    选择notify()方法看一下官网说明:

    
    Wakes up a single thread that is waiting on this object's monitor
    
    

    也就是说每个对象都有一个监视器(monitor),这个监视器可以当锁用,实现多线程同步,但是需要配合synchronized使用.关于监视器的概念可以参考这篇文章监视器–JAVA同步基本概念

    wait()

    在一个线程(假设为A)中使用某个对象(假设为B)的wait()方法可以阻塞该线程(A),使该线程(A)等待获取该对象(B)的监视器.在其它线程(假设为C)中使用同一个对象(B)的notify()或者notifyAll()可以唤醒该线程(A),使该线程(A)重新获得该对象(B)的监视器.官网说明是:

    
    Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
    
    

    notify()

    用来随机唤醒一个等待监视器的线程.

    notifyAll()

    唤醒全部等待监视器的线程,但是只有一个线程可以获得监视器,其它线程仍然进入等待状态,等待下一次唤醒.官网说明是:

    
    Wakes up all threads that are waiting on this object's monitor.
    
    

    这里的this object指的是调用notifyAll()方法的object.

    synchronized

    简介

    synchronized是java中的关键字,提供了一种内置的同步机制.使用synchronized同步编译代码时会自动在开始时添加lock操作,在结束时添加unlock操作.这里的lock和unlock是java内存模型(java memory model)8种操作的2种.按照《深入理解java虚拟机》第373页的介绍,lock和unlock并未直接开放给用户使用,而是通过synchronized使用这2个操作.

    使用方式

    synchronized可以有2种使用方式:

    • 同步方法

    • 同步代码块.

    关于同步方法和同步代码块的区别参考 java中的synchronized(同步代码块和同步方法的区别)

    同步代码块

    写法是这样的:

    
    public void A() {
    
    //同步代码块
    
    synchronized(monitor) {
    
     //do something
    
     }
    
    }
    
    

    monitor是使用的锁对象.

    同步方法

    写法是这样的:

    
    public synchronized void A() {
    
     //do something
    
    }
    
    

    就是在方法上加上修饰词synchronized.使用的锁对象是this,这种写法等同于

    
    public void A() {
    
    //同步代码块
    
    synchronized(this) {
    
     //do something
    
     }
    
    }
    
    

    可重入

    先看一段代码:

    
    public class ReentrantDemo {
    
     int count = 0;
    
     public void A() {
    
     synchronized(this) {
    
     B();
    
     }
    
     }
    
     public void B() {
    
     synchronized(this) {
    
     C();
    
     }
    
     }
    
     public void C() {
    
     synchronized(this) {
    
     count++;
    
     }
    
     }
    
    }
    
    

    这段代码中,某个线程执行到方法A时已经获得了this对象的监视器(monitor),执行到B时可以再获取一次,到C时仍然可以获取,这就是可重入.

    synchronized和wait()

    wait()必须在synchronized中使用吗?

    wait方法必须在synchronized中使用,写法是这样的:

    
    public void A() {
    
    //同步代码块
    
    synchronized(monitor) {
    
     monitor.wait();
    
     }
    
    }
    
    

    或者

    
    public synchronized void A() {
    
     this.wait();
    
    }
    
    

    具体可以参考stackoverflow上的Why must wait() always be in synchronized block

    生产环境使用wait()需要注意什么?

    生产上可能需要放到while中:

    
    while (!condition) { 
    
    obj.wait(); 
    
    }
    
    

    这是因为生产环境可能存在没有接收到notify()/notifyAll()时就被唤醒的情况,这种唤醒过程一般被称为spurious wakeup.官网wait()给出的解释是:

    
    A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
    
     synchronized (obj) {
    
     while (<condition does not hold>)
    
     obj.wait(timeout);
    
     ... // Perform action appropriate to condition
    
     }
    
    

    实际上,不止synchonized会发生spurious wakeup,Reentrantlock/Condition等其它同步方式也会发生spurious wakeup.

    使用wait()会导致当前线程释放锁(monitor)吗?

    会.当前线程阻塞,同时释放锁(monitor).相比之下,yield()方法仅仅暂停线程,不会释放锁(monitor).

    案例

    案例简介

    有n个线程同时执行某个类中的add方法,该方法会将属性值count加1.

    案例代码

    主要有3个类:CountNumber.java、CountNumberTask.java、CountNumberTest.java.

    CountNumber.java代码如下:

    
    package com.ms.thread.synchronizedandwait;
    
    /**
    
     * 使用add()中嵌套realyAdd(),用于测试synchonized的可重入性.
    
     * @author iengchen
    
     * @since 2018-04-21
    
     */
    
    public class CountNumber {
    
    private int count = 0;
    
    public int add() {
    
    synchronized (this) {
    
    realyAdd();
    
    //System.out.println("count值是:"+this.count);
    
    }
    
    return this.count;
    
    }
    
    public void realyAdd() {
    
    synchronized (this) {
    
    this.count++;
    
    }
    
    }
    
    public int getCount() {
    
    return count;
    
    }
    
    public void setCount(int count) {
    
    this.count = count;
    
    }
    
    }
    
    

    CountNumberTask.java代码如下:

    
    package com.ms.thread.synchronizedandwait;
    
    /**
    
     * @author iengchen
    
     * @since 2018-04-21
    
     */
    
    public class CountNumberTask implements Runnable{
    
    private CountNumber cn;
    
    public CountNumberTask(CountNumber cNumber) {
    
    this.cn = cNumber;
    
    }
    
    @Override
    
    public void run() {
    
    //this.cn.add();
    
    System.out.println(Thread.currentThread().getName()+"加1成功,值是:"+this.cn.add());
    
    }
    
    }
    
    

    CountNumberTest.java代码如下:

    
    package com.ms.thread.synchronizedandwait;
    
    /**
    
     * 用于测试CountNumber和CountNumberTask
    
     * @author iengchen
    
     * @since 2018-04-21
    
     */
    
    public class CountNumberTest {
    
    //执行加1方法的线程数.打印出来的最大count值应该等于该值.否则说明同步有问题
    
    private static int addThreadNum = 10;
    
    public static void main(String[] args) {
    
    CountNumber cNumber = new CountNumber();
    
    for (int i=0; i<addThreadNum; i++) {
    
    new Thread(new CountNumberTask(cNumber), "task-"+i).start();
    
    }
    
    }
    
    }
    
    

    案例结果

    打印的结果如下:

    
    task-1加1成功,值是:1
    
    task-4加1成功,值是:5
    
    task-3加1成功,值是:4
    
    task-2加1成功,值是:2
    
    task-0加1成功,值是:3
    
    task-6加1成功,值是:7
    
    task-5加1成功,值是:6
    
    task-7加1成功,值是:8
    
    task-8加1成功,值是:9
    
    task-9加1成功,值是:10
    
    

    参考

    相关文章

      网友评论

          本文标题:synchronized和monitor使用案例

          本文链接:https://www.haomeiwen.com/subject/rhwflftx.html