Java中数据同步-synchronized关键字与Mointor的使用

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 Java中数据同步-synchronized关键字与Mointor的使用,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

场景

Java中Thread类的常用API以及使用示例:

Java中Thread类的常用API以及使用示例_霸道流氓气质的博客-CSDN博客

在上面的基础上,学习线程同步的相关概念。

数据不一致问题引入

模拟一个营业厅叫号机程序

package com.ruoyi.demo.threadsafe;

/**
 * 模拟营业大厅叫号程序,每次会不一样的发现:某个号码被略过、某个号码被多次显示、号码超过了最大值500
 */
public class TicketWindowRunable implements Runnable{

    private int index = 1;
    private final static int MAX = 500;

    @Override
    public void run() {
        while (index <= MAX)
        {
            System.out.println(Thread.currentThread()+"的号码:"+(index++));
        }
    }

    public static void main(String[] args) {
        final TicketWindowRunable task = new TicketWindowRunable();
        Thread windowThread1 = new Thread(task,"1号窗口");
        Thread windowThread2 = new Thread(task,"2号窗口");
        Thread windowThread3 = new Thread(task,"3号窗口");
        Thread windowThread4 = new Thread(task,"4号窗口");

        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
        windowThread4.start();
    }
}

多次运行上面程序,每次都会不一样。

Java中数据同步-synchronized关键字与Mointor的使用

可以通过输出的行数明显发现不一样,主要有三个问题:

某个号码被略过、某个号码被多次显示、号码超过最大值

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

1、出现上述不一样的原因是线程的执行是由CPU时间片轮询调度的,CPU调度器在将执行权交给各个线程时,多个线程对index变量(共享变量/资源)

同时操作引起的。

要解决这个问题需要使用synchronized关键字,其提供了一种排他机制,也就是在同一时间只能有一个线程执行某些操作。

2、将上面的叫号程序修改

package com.ruoyi.demo.threadsafe;

/**
 * 模拟营业大厅叫号程序
 */
public class TicketWindowRunableWithSync implements Runnable{

    private int index = 1;
    private final static int MAX = 500;
    private final static Object MUTEX = new Object();
    @Override
    public void run() {
        synchronized (MUTEX){
            while (index <= MAX)
            {
                System.out.println(Thread.currentThread()+"的号码:"+(index++));
            }
        }
    }

    public static void main(String[] args) {
        final TicketWindowRunableWithSync task = new TicketWindowRunableWithSync();
        Thread windowThread1 = new Thread(task,"1号窗口");
        Thread windowThread2 = new Thread(task,"2号窗口");
        Thread windowThread3 = new Thread(task,"3号窗口");
        Thread windowThread4 = new Thread(task,"4号窗口");

        windowThread1.start();
        windowThread2.start();
        windowThread3.start();
        windowThread4.start();
    }
}

Java中数据同步-synchronized关键字与Mointor的使用

此时程序运行多次,不会出现数据不一致的问题。

3、线程堆栈分析

synchronized关键字提供了一种互斥机制,在同一时刻,只能有一个线程访问同步资源。下面是一个简单示例

package com.ruoyi.demo.threadsafe;

import java.util.concurrent.TimeUnit;

public class Mutex {
    private final static Object MUTEX = new Object();
    public void accessResource(){
        synchronized (MUTEX){
            try {
                //TimeUnit.MINUTES.sleep(10);
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final Mutex mutex = new Mutex();
        for (int i = 0; i < 5 ; i++) {
            new Thread(mutex::accessResource).start();
        }
    }
}

上面定义了一个方法accessResource,使用同步代码块的方式对accessResource进行同步,同时定义5个线程调用accessResource方法。

由于同步代码块的互斥性,只能有一个线程获取了mutex monitor的锁,其他线程只能进入堵塞状态。

4、使用JConsole工具监控

找到jdk的目录下bin下jconsole.exe,双击运行

Java中数据同步-synchronized关键字与Mointor的使用

新建连接-本地进程-上面的Mutex选中-连接-接受不安全的连接-线程-Thread-0到4就是上面的5个线程

Java中数据同步-synchronized关键字与Mointor的使用

当设置休眠10分钟的时候可以看到,Thread-0在sleep

Java中数据同步-synchronized关键字与Mointor的使用

其它的则是进入了BLOCKED状态。

5、使用jps和jstack打印线程堆栈信息

通过上面的jconsole可以获取到其pid,也可以直接在cmd中输入

jps

获取所有的pid之后,再输入

jstack pid号

打印进程的线程堆栈信息

 

Java中数据同步-synchronized关键字与Mointor的使用

看到与上面的jconsole的效果一致。

6、规则与注意事项

规则:

每个对象都与一个monitor相关联,一个monitor的lock的锁只能被一个线程在同一时间获得。

释放对monitor的所有权的前提是你曾经获取到了所有权。

注意问题:

 ①与monitor关联的对象不能为空。

private final static Object MUTEX = null;

②synchronized作用域太大。

如果将其作用在run方法上,则丧失了并发的优势。

③不同的monitor企图锁相同的方法。

package com.ruoyi.demo.threadsafe;

public class ErrorMutexDemo implements Runnable {
    private final Object MUTEX = new Object();

    @Override
    public void run() {
        synchronized (MUTEX)
        {
           
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(ErrorMutexDemo::new).start();
        }
    }
}

上面的代码每个线程争抢的monitor关联引用都是彼此独立的,因此不能起到互斥的作用。

④多个锁的交叉导致死锁

7、This Monitor与Class Monitor介绍

ThisMonitor

两个方法method1和method2都被synchronized关键字修饰,启动两个线程分别访问method1和method2

package com.ruoyi.demo.threadsafe;

import java.util.concurrent.TimeUnit;

import static java.lang.Thread.currentThread;

public class ThisMonitor {
    public synchronized void method1(){
        System.out.println(currentThread().getName()+"enter to method1");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2(){
        System.out.println(currentThread().getName()+"enter to method2");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThisMonitor thisMonitor = new ThisMonitor();
        //注意这里调用了构造方法public Thread(Runnable target, String name)
        //Target——线程启动时调用其run方法的对象。如果为空,则调用此线程的run方法。
        new Thread(thisMonitor::method1,"T1").start();
        new Thread(thisMonitor::method2,"T2").start();
    }
}

运行之后的结果为

Java中数据同步-synchronized关键字与Mointor的使用

此时用jstack分析

Java中数据同步-synchronized关键字与Mointor的使用

可以得出,synchronized关键字同步类的不同实例方法,争抢的是同一个monitor的lock,而与之相关联的引用则是ThisMonitor的实例

引用。

Classmonitor

有两个类方法(静态方法)分别使用synchronized对其进行同步

package com.ruoyi.demo.threadsafe;

import java.util.concurrent.TimeUnit;

import static java.lang.Thread.currentThread;

public class ClassMonitor {
    public static synchronized void method1()
    {
        System.out.println(currentThread().getName()+"enter to method1");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void method2()
    {
        System.out.println(currentThread().getName()+"enter to method2");
        try {
            TimeUnit.MINUTES.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(ClassMonitor::method1,"T1").start();
        new Thread(ClassMonitor::method2,"T2").start();
    }
}

运行之后的效果

Java中数据同步-synchronized关键字与Mointor的使用

使用jstack分析之后

Java中数据同步-synchronized关键字与Mointor的使用

由此可知,用synchronized同步某个类的 不同静态方法争抢的也是同一个monitor的lock。

 以上代码和示例参考《Java高并发编程详解》,建议阅读原书。 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135908.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!