场景
Java中Thread类的常用API以及使用示例:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126596884
在上面的基础上,学习线程同步的相关概念。
数据不一致问题引入
模拟一个营业厅叫号机程序
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(); } }
多次运行上面程序,每次都会不一样。
可以通过输出的行数明显发现不一样,主要有三个问题:
某个号码被略过、某个号码被多次显示、号码超过最大值
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。
实现
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(); } }
此时程序运行多次,不会出现数据不一致的问题。
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,双击运行
新建连接-本地进程-上面的Mutex选中-连接-接受不安全的连接-线程-Thread-0到4就是上面的5个线程
当设置休眠10分钟的时候可以看到,Thread-0在sleep
其它的则是进入了BLOCKED状态。
5、使用jps和jstack打印线程堆栈信息
通过上面的jconsole可以获取到其pid,也可以直接在cmd中输入
jps
获取所有的pid之后,再输入
jstack pid号
打印进程的线程堆栈信息
看到与上面的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(); } }
运行之后的结果为
此时用jstack分析
可以得出,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(); } }
运行之后的效果
使用jstack分析之后
由此可知,用synchronized同步某个类的 不同静态方法争抢的也是同一个monitor的lock。
以上代码和示例参考《Java高并发编程详解》,建议阅读原书。
标签:Java,Thread,synchronized,Mointor,void,线程,static,public From: https://www.cnblogs.com/badaoliumangqizhi/p/16665076.html