【JavaEE】synchronized监视锁 volatile关键字wait和notify

简介: 【JavaEE】synchronized监视锁 volatile关键字wait和notify

由于我们在使用多线程的过程中会出现线程安全的问题的。然后我们可以通过这几个方案来进行解决线程安全问题。

synchronized监视锁:

方案一:监视锁

synchronized关键字有以下几个特征:

1、互斥性

当程序进入到synchronized关键字修饰的代码块时,这个时候就被加锁了。当执行出该代码块时就解锁了。当我们的一个线程对该对象加锁后,另外一个线程执行到该对象时,就会阻塞等待。

我们可以简单的理解为:这里以上厕所为一个例子,当有人(一个线程)进入厕所(执行到监视的锁对象)进行解决的时候,就要进行锁门(也就是加锁),后面有人(另一个线程)也要来上厕所(执行到该对象)的时候就需要在门外等待(阻塞对待),直到里面的人出来了(解锁),再进去(再获取进行加锁)。

2、 可重入性

synchronized是可重入锁。可重入锁就是同一个线程对于同一个对象同时加锁两次或者两次以上。如果没有问题,没有产生bug,就是可重入锁。如果不能正常的运行,就是不可重入的锁了。

synchronized的用法:

1、synchronized修饰普通方法:

1. public synchronized void increase(){
2.         sum++;
3.     }

此处的锁对象是this

2、 synchronized修饰静态方法

1. public class SynchronizedDemo1 {
2. private int sum=0;
3. 
4. public synchronized static void fun(int a){
5.         a++;
6.     }
7. 
8. }

此处的锁对象是SynchronizedDemo1的类的对象。

3、synchronized修饰代码块

1. public  void increase(){
2. synchronized(this){
3.             sum++;
4.         }
5. 
6.     }

此时的锁对象依然是this。

注意:在使用synchronized的时候一定要注意锁的对象是谁,一般有this,类名.class(类对象)和普通对象变量。

volatile :

方案二:volatile关键字

首先我们需要明确:volatile是为了解决内存可见性问题用到的关键字,其中文意思就是易变的,这是在提醒编译器,遇到这个关键字就不要擅作主张了。

volatile的用法:

修饰变量。

比如:

volatile public int flag=0;

这里举个例子:还是前面总结内存可见性的例子:

1. package MySynchronized;
2. 
3. import java.util.Scanner;
4. 
5. class MyCount{
6. volatile public int flag=0;
7. }
8. 
9. public class SynchronizedDemo2 {
10. public static void main(String[] args) {
11. 
12.         MyCount myCount=new MyCount();
13. 
14.         Thread t1=new Thread(()->{
15. while(myCount.flag==0){
16. 
17.             }
18.             System.out.println("循环结束!");
19. 
20.         });
21. 
22.         Thread t2=new Thread(()->{
23.             Scanner scan=new Scanner(System.in);
24.             System.out.println("输入你要输入的数字:");
25. int result=scan.nextInt();
26.             myCount.flag=result;
27. 
28.         });
29.         t1.start();
30.         t2.start();
31.     }
32. }

如果我们不加上述的volatile,就会出现这个情况:

只有加上volatile才会正常的显示我们想要的结果:

volatile和synchronized的区别:

synchronized保证原子性,volatile不保证原子性,保证的是内存可见性

wait和notify:

方案三:wait和notify

首先我们要知道线程在执行的过程中是随机调度的,抢占式执行的,所以我们是不知道它们执行的顺序的。但是我们在实际的开发中有些时候我们希望各个线程按照一定的顺序来进行执行,此时我们就需要用到我们的wait和notify了。

wait要做的事情:使当前执行代码的线程阻塞等待,释放锁,等待唤醒后重新获取这个锁。

notify要做的事情:唤醒等待的线程

wait被唤醒的条件:

1、其他线程调用该对象的notify方法

2、wait等待时间超时

3、wait抛出InterruptedException异常(别的线程调用了该等待线程的interrupted方法)

这里我们举一个如何使用3个线程,按照顺序打印ABC的例子:

1. package MySynchronized;
2. 
3. public class SynchronizedDemo3 {
4. public static void main(String[] args) {
5. 
6.         Object loker1=new Object();
7.         Object loker2=new Object();
8.         Thread t1=new Thread(()->{
9.             System.out.println("A");
10. //A打印完进行通知t2
11. synchronized (loker1){
12.                 loker1.notify();
13.             }
14.         });
15. 
16.         Thread t2=new Thread(()->{
17. //B在A前面,所以让t2阻塞等待
18. synchronized (loker1){
19. try {
20.                     loker1.wait();
21.                 } catch (InterruptedException e) {
22. throw new RuntimeException(e);
23.                 }
24.             }
25.             System.out.println("B");
26. //打印B之后,进行通知
27. synchronized (loker2){
28.                 loker2.notify();
29.             }
30.         });
31. //为保证ABC的顺序,所以我们要保证C要在B的后面
32. //所以我们在打印C之前,要让它阻塞等待
33.         Thread t3=new Thread(()->{
34. //阻塞等待,等待t2线程的通知
35. synchronized (loker2){
36. try {
37.                     loker2.wait();
38.                 } catch (InterruptedException e) {
39. throw new RuntimeException(e);
40.                 }
41.             }
42.             System.out.println("C");
43.             System.out.println("打印正确!");
44.         });
45. //这里有个细节要注意:如果t1比t2先执行了,那么t1中的通知就失去了作用,也就是说t2还没有排队等待呢,t1已经通知了,这个时候通知就没有用了
46. //等到t2等待的时候,t1就没有等二次通知的机会了,所以要让t2先执行。
47.         t2.start();
48. try {
49.             Thread.sleep(100);
50.         } catch (InterruptedException e) {
51. throw new RuntimeException(e);
52.         }
53.         t1.start();
54.         t3.start();
55.     }
56. }

特别要注意:wait和notify的对象要要一致!

notifyAll方法:

与notify功能类似,都是通知同对象的线程。但是略有区别的是,notify是随机唤醒一个同对象所在的阻塞线程(假设有多个线程),notifyAll是唤醒所有的,剩下的进行所竞争。(风险大,不咋用,上面的wait和notify是重点!!!!

wait和sleep的区别(面试):

其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。

区别:1、wait需要搭配synchronized使用 sleep不需要

          2、wait是Object的方法sleep是Thread 的静态方法



相关文章
|
监控 安全
并发编程系列教程(06) - 多线程之间通讯(wait、notify、sleep、Lock锁、Condition)
并发编程系列教程(06) - 多线程之间通讯(wait、notify、sleep、Lock锁、Condition)
67 0
|
5月前
|
Java
Java中的内置锁synchronized关键字和wait()、notifyAll()方法
【6月更文挑战第17天】Java的synchronized和wait/notify实现顺序打印ALI:共享volatile变量`count`,三个线程分别检查`count`值,匹配时打印并减1,未匹配时等待。每个`print`方法加锁,确保互斥访问。代码示例展示了线程同步机制。考虑异常处理及实际场景的扩展需求。
86 3
|
3月前
|
安全 Java 调度
|
4月前
|
存储 SQL 安全
Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify以及锁分类
Java共享问题 、synchronized 线程安全分析、Monitor、wait/notify以及锁分类
45 0
|
6月前
|
存储 安全 Java
【JavaEE初阶】 volatile关键字 与 wait()方法和notify()方法详解
【JavaEE初阶】 volatile关键字 与 wait()方法和notify()方法详解
|
6月前
|
安全 Java API
synchronized关键字-监视器锁(monitor lock)
synchronized关键字-监视器锁(monitor lock)
|
6月前
|
设计模式 安全 编译器
线程学习(3)-volatile关键字,wait/notify的使用
线程学习(3)-volatile关键字,wait/notify的使用
54 0
|
缓存 Java 编译器
volatile,解决内存可见性引起的问题,wait和notify
volatile,解决内存可见性引起的问题,wait和notify
为什么 wait 方法要在 synchronized 中调用?
它们是在有 synchronized 标记的方法或 synchronized 块中调用的,因为 wait 和 nodify 需要监视对其调用的 Object。 大多数Java开发人员都知道对象类的 wait(),notify() 和 notifyAll() 方法必须在 Java 中的 synchronized 方法或 synchronized 块中调用, 但是我们想过多少次, 为什么在 Java 中 wait, notify 和 notifyAll 来自 synchronized 块或方法?
191 0
为什么 wait 方法要在 synchronized 中调用?
wait和notify 为什么要在synchronized代码块中
wait和notify 为什么要在synchronized代码块中
wait和notify 为什么要在synchronized代码块中