• Java多线程(二)同步与等待唤醒


     1:数据安全问题

          1.1:什么情况下会出现数据安全问题?

                多个线程对同一个资源进行操作,并且操作资源的语句有多条。那么这个时候这些语句因为cpu的随机性,有可能被多个线程分开执行。导致数据安全问题。

                 例子:有3个人分别是你爸、你妈妈、你姐,去你的一个账户汇钱给你,每一个只能存3次,一次只能存100元。每存一次,请打显示出账户里的余额。代码体现:

     1 public class SaveMoneyDemo1 {
     2     
     3     public static void main(String[] args) {
     4         SaveDemo1 s = new SaveDemo1();
     5         Thread t1 = new Thread(s);
     6         t1.setName("老爸");
     7         Thread t2 = new Thread(s);
     8         t2.setName("老妈");
     9         Thread t3 = new Thread(s);
    10         t3.setName("姐姐");
    11         t1.start();
    12         t2.start();
    13         t3.start();
    14     }
    15 
    16 }
    17 
    18 class SaveDemo1 implements Runnable{
    19     private int sum = 0;
    20     //要执行的代码块放在run方法里面。
    21     public void run() {
    22         //每个人能存三次,就是循环三遍
    23         for(int i=0; i<3; i++){
    24             sum+=100;
    25             System.out.println(Thread.currentThread().getName()+"给你汇了100,目前账号共有 "+sum+" 元");
    26                     
    27         }
    28     }
    29 }
    30 /**
    31  * 执行结果:
    32         老妈给你汇了100,目前账号共有 200 元
    33         老妈给你汇了100,目前账号共有 400 元
    34         老妈给你汇了100,目前账号共有 500 元
    35         姐姐给你汇了100,目前账号共有 300 元
    36         姐姐给你汇了100,目前账号共有 600 元
    37         姐姐给你汇了100,目前账号共有 700 元
    38         老爸给你汇了100,目前账号共有 200 元
    39         老爸给你汇了100,目前账号共有 800 元
    40         老爸给你汇了100,目前账号共有 900 元
    41  * 
    42  */
    没有同步的汇款代码

    运行结果好喜感。为什么会出现这种情况?分析:这三人存款是不需按照顺序和次数的,反正帮你存够三次就行了,所以用多线程更为合理。可是,打个比方:当你妈妈在存钱的时候钱是存进去了,在没来得及显示余额的时候你爸正好也把钱存了进去,这时候总金额连同你妈和你爸的加在一起了!所以显示出的金额会发生这样的情况。那么如何解决类似的情况呢?这就要限定一个人存一次就先把金额加上,不能让多人存完之后再一起加,如果这样那金额的显示就乱套了。这时候就要使用同步机制了。

          1.2:解决方案: 同步机制

                 1.2.1:同步代码块。

                        synchronized(锁){//锁可以为任意对象。但是需要保证多个线程用的是同一把锁。                                    

                                        对同一个资源的操作语句。

                                       }

                 1.2.2:同步方法的锁:

                         2.1:同步方法-----this

                         2.2:静态同步方法-----字节码文件对象。类名.class

     1 public class SaveMoneyDemo2 {
     2     
     3     public static void main(String[] args) {
     4         SaveDemo2 s = new SaveDemo2();
     5         Thread t1 = new Thread(s);
     6         t1.setName("老爸");
     7         Thread t2 = new Thread(s);
     8         t2.setName("老妈");
     9         Thread t3 = new Thread(s);
    10         t3.setName("姐姐");
    11         t1.start();
    12         t2.start();
    13         t3.start();
    14     }
    15 
    16 }
    17 
    18 class SaveDemo2 implements Runnable{
    19     private int sum = 0;
    20     //要执行的代码块放在run方法里面。
    21     public void run() {
    22         //每个人能存三次,就是循环三遍
    23         synchronized(this){
    24             for(int i=0; i<3; i++){
    25                 sum+=100;
    26                 System.out.println(Thread.currentThread().getName()+"给你汇了100,目前账号共有 "+sum+" 元");
    27             }
    28         }
    29     }
    30 }
    使用同步的数据安全的汇款

           1.3:如果加了同步,还出现数据安全问题,如何排查?

                  1.3.1:是否为同一把锁

                  1.3.2:访问资源的多条语句是否在同步中。

            1.4:关于同步的拙劣理解:一件事先一口气做完!不让别人插手。(好像太牵强了)

    2:死锁问题——互不释放资源(互相等待资源)

      2.1  需求:用程序来描述以下情况:一手交钱一手交货。商家与顾客两人都是很小气的人,顾客买商家的东西,商家收顾客的前,顾客说:先给我货我再给你钱;商家说:先给我钱我再给你货。最好的结局就是各自得到各自的东西。

          2.2  分析:对于商家和客户来说和他们俩有关的不是钱就是货,而限制这两人的也就是钱和货。这样一来钱和货就可以看做是程序中的两个锁了。造成死锁的原因:同步代码嵌套!在平时开发时应避免同步嵌套!

     1 public class DeadLockDemo1 {
     2     public static void main(String[] args) {
     3         Thread t1 = new Customer1();
     4         Thread t2 = new Seller1();
     5         t1.start();
     6         t2.start();
     7     }
     8 }
     9 
    10 class Customer1 extends Thread{
    11     public static Object money = new Object();
    12     @Override
    13     public void run() {
    14         //客户有钱
    15         synchronized(money){
    16             System.out.println("客户等商家给货");
    17             //客户等货
    18             synchronized (Seller1.goods) {
    19                 System.out.println("客户给商家钱");
    20             }
    21         }
    22     }
    23 }
    24 
    25 class Seller1 extends Thread{
    26     public static Object goods = new Object();
    27     @Override
    28     public void run() {
    29         //商家有货
    30         synchronized (goods) {
    31             System.out.println("商家等客户给钱");
    32             //商家等钱
    33             synchronized (Customer1.money) {
    34                 System.out.println("商家给客户货");
    35             }
    36         }
    37     }
    38 }
    39 
    40 /**
    41  * 如果想结果暴露地更明显,可以使用sleep()方法
    42  * 运行死锁的结果:
    43  *     客户等商家给货
    44  *    商家等客户给钱
    45  * 
    46  */
    死锁示例代码

    3:等待唤醒机制。

         前面多个线程案例中,每个线程执行的操作是一样的。如果线程所执行的操作不一样呢?比如一个线程负责生产产品,另外一个线程负责消费产品。

         3.1:创建2个线程,2个线程的动作是不一样。比如说:一个生产者和一个消费者。

               需求:生产者生产一个产品。消费者消费一个产品。这就涉及到了等待唤醒机制。当生产者生产一个产品后进入等待模式等待消费者来消费这个产品,当消费者消费了这个产品,发现没有产品了,消费者等待,叫生产者生产产品。生产者生产了产品则通知消费者。这就涉及到等待唤醒机制。

          3.2:等待唤醒机制。

                等待唤醒机制必须是在同步中进行,因为等待和唤醒都是要通过锁来操作的,查看API就是的,wait()和notify()是属于Object的方法,任何对象都是可以作为锁的。

                 wait:让当前线程等待。在哪里等待的就在哪里醒过来。

                 notify() :唤醒其中一个等待的线程。

                 notifyAll():唤醒所有等待的线程

                 wait和sleep的区别:

                       1:sleep会拥有锁,而wait会释放锁。

                       2:sleep睡眠的时间是固定的,而wait等待的时间是不固定的。

                       3:sleep可以放在同步中,也可以不放在同步中。wait方法必须放在同步中。

           3.3:一个生产者和一个消费者的代码实现

     1 /*
     2  * 生产者生产一个产品。
     3  * 消费者消费一个产品。
     4  * 生产者可以生产多个产品,但是一次只能生产一个产品。消费了才能生产。
     5  * 消费者可以消费多个产品。但是一次只能消费一个 产品。生产有了产品才能消费。
     6  */
     7 public class ProCusDemo1 {
     8     public static Object lock = new Object();//创建一个对象作为锁
     9     public static int num = 0;//产品数
    10     public static void main(String[] args) {
    11         Pro pro = new Pro();
    12         Cus cus = new Cus();
    13         pro.start();
    14         cus.start();
    15     }
    16     
    17 }
    18 
    19 class Pro extends Thread {
    20     @Override
    21     public void run() {
    22         //不断生产,使用循环
    23         while(true){
    24             try {
    25                 Thread.sleep(100);
    26             } catch (InterruptedException e1) {
    27                 // TODO Auto-generated catch block
    28                 e1.printStackTrace();
    29             }
    30 //            System.out.println("111");
    31             //操作同一数据——产品数(num),使用同步代码块,也可以是等待唤醒机制必须在同步在同步中进行
    32             synchronized (ProCusDemo1.lock) {
    33                 //当有一个产品了,生产者就不用生产了
    34                 if(ProCusDemo1.num == 1){
    35                     try {
    36                         //不用生产的体现就是等待
    37                         ProCusDemo1.lock.wait();
    38 //                        System.out.println("222");
    39                     } catch (InterruptedException e) {
    40                         e.printStackTrace();
    41                     }
    42                 }
    43                 ProCusDemo1.num ++;//生产了一个产品
    44                 System.out.println("生产者生产了一个产品,现有:"+ProCusDemo1.num+" 个");
    45                 //当生产了一个产品之后就可以唤醒消费者消费了
    46                 ProCusDemo1.lock.notify();
    47             }
    48         }
    49     }
    50 }
    51 
    52 class Cus extends Thread {
    53     @Override
    54     public void run() {
    55         while(true){
    56 //            System.out.println("333");
    57             try {
    58                 Thread.sleep(100);
    59             } catch (InterruptedException e1) {
    60                 // TODO Auto-generated catch block
    61                 e1.printStackTrace();
    62             }
    63             //多个线程操作同一数据使用同步
    64             synchronized (ProCusDemo1.lock) {
    65                 if(ProCusDemo1.num == 0){
    66                     try {
    67                         ProCusDemo1.lock.wait();
    68 //                        System.out.println("444");
    69                     } catch (InterruptedException e) {
    70                         e.printStackTrace();
    71                     }
    72                 }
    73                 ProCusDemo1.num--;
    74                 System.out.println("消费者消费了一个产品,现有:"+ProCusDemo1.num+" 个");
    75                 ProCusDemo1.lock.notify();
    76             }
    77         }
    78         
    79     }
    80 }
    等待唤醒机制——生产消费
  • 相关阅读:
    换上 SansForgetica-Regular 字体,增加记忆能力
    Windows和Linux查看端口占用
    安卓打开远程调试(免root)
    debian系统解决包依赖问题的神器aptitude
    C# WinForm 实现窗体淡入淡出
    [图文教程]VS2017搭建opencv & C++ 开发环境
    C# 调用Tesseract实现OCR
    数据库工具链接阿里云MySQL数据库
    【转载】如何选择MySQL存储引擎
    java Long、Integer 、Double、Boolean类型 不能直接比较
  • 原文地址:https://www.cnblogs.com/xinge1993/p/4733081.html
Copyright © 2020-2023  润新知