zoukankan      html  css  js  c++  java
  • Java基础-线程操作共享数据的安全问题

                        Java基础-线程操作共享数据的安全问题

                                            作者:尹正杰

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

     

    一.引发线程安全问题

      如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    1>.售票案例

      假设某人一次性买了20张关于周杰伦的演唱会,原计划是请全部门去看演唱会的,但是由于老板的临时任务来袭,被迫需要把这20张票放在交给三个人转卖出去,请你模拟这个买票的过程。我们可以用代码来实现一下:

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note;
     8 
     9 class Tickets implements Runnable{
    10     //定义出售的票源
    11     private int ticket = 20;
    12     @Override
    13     public void run() {
    14         while(true) {
    15             //对于票数大于0才可以出售
    16             if( ticket > 0 ) {
    17                 try {
    18                     Thread.sleep(10);
    19                     System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票
    ",ticket--);
    20                 } catch (InterruptedException e) {
    21                     e.printStackTrace();
    22                 }
    23             }
    24         }
    25     }
    26 }
    27 
    28 
    29 public class ThreadDemo {
    30     public static void main(String[] args) {
    31         //创建Runnable接口实现了对象
    32         Tickets t = new Tickets();
    33         //创建三个Thread类对象,传递Runnable接口实现类
    34         Thread t1 = new Thread(t,"窗口1");
    35         Thread t2 = new Thread(t,"窗口2");
    36         Thread t3 = new Thread(t,"窗口3");
    37         t1.start();
    38         t2.start();
    39         t3.start();
    40         
    41     }
    42 }
    43 

       运行我们写的程序之后,发现了两个问题,即(重复买票还有出现负数票的情况),如下:

    2>.分享出现线程安全问题的原因

    3>.同步代码块解决线程安全问题

      我们解决上面案例的思路就是:当一个线程进入数据操作的时候,无论是否休眠,其它线程只能等待。这就需要引入Java的一个关键字,同步锁的概念:

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note;
     8 
     9 class Tickets implements Runnable{
    10     //定义出售的票源
    11     private int ticket = 20;
    12     
    13     private Object obj = new Object();
    14     @Override
    15     public void run() {
    16         while(true) {
    17             //线程共享数据,保证安全,可以把一段代码变成一个原子性操作,也就是说当某个线程在执行该操作时,其它的线程进不来!
    18             synchronized (obj) {
    19                 //对于票数大于0才可以出售
    20                 if( ticket > 0 ) {
    21                     try {
    22                         Thread.sleep(20);
    23                         System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票
    ",ticket--);
    24                     } catch (InterruptedException e) {
    25                         e.printStackTrace();
    26                     }
    27                 }
    28             }
    29         }
    30     }
    31 }
    32 
    33 
    34 public class ThreadDemo {
    35     public static void main(String[] args) {
    36         //创建Runnable接口实现了对象
    37         Tickets t = new Tickets();
    38         //创建三个Thread类对象,传递Runnable接口实现类
    39         Thread t1 = new Thread(t,"窗口1");
    40         Thread t2 = new Thread(t,"窗口2");
    41         Thread t3 = new Thread(t,"窗口3");
    42         t1.start();
    43         t2.start();
    44         t3.start();
    45         
    46     }
    47 }

      执行结果如下:

    4>.同步代码块的执行原理

    二.同步方法

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 package cn.org.yinzhengjie.note;
     7 
     8 class Tickets implements Runnable{
     9     //定义出售的票源
    10     private int ticket = 20;
    11     @Override
    12     public void run() {
    13         while(true) {
    14             payTicket();
    15         }
    16     }
    17     //同步方法需要在方法上面用关键字synchronized声明,同步方法中也有对象锁,该锁就是本类对象引用(this).
    18     //如果同步方法有静态修饰,那么成员变量也需要用静态修饰,静态同步方法中的锁对象是:“类名.class”,即"Tickets.class"。
    19     public synchronized void payTicket() {
    20             //对于票数大于0才可以出售
    21             if( ticket > 0 ) {
    22                 try {
    23                     Thread.sleep(20);
    24                     System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票
    ",ticket--);
    25                 } catch (InterruptedException e) {
    26                     e.printStackTrace();
    27                 }
    28             }
    29     }
    30 }
    31 
    32 public class ThreadDemo {
    33     public static void main(String[] args) {
    34         //创建Runnable接口实现了对象
    35         Tickets t = new Tickets();
    36         //创建三个Thread类对象,传递Runnable接口实现类
    37         Thread t1 = new Thread(t,"窗口1");
    38         Thread t2 = new Thread(t,"窗口2");
    39         Thread t3 = new Thread(t,"窗口3");
    40         t1.start();
    41         t2.start();
    42         t3.start();
    43     }
    44 }

      代码执行结果如下:

    三.Lock接口改进售票案例

     

      我们用synchronized关键字实现同步锁方法,我们也知道非静态默认所就是本类对象引用(this).如果同步方法有静态修饰,那么成员变量也需要用静态修饰,静态同步方法中的锁对象是:“类名.class”。但是我们很难实现在程序看出来它是在哪上锁,又是在哪解锁。这个时候我们Java开发者在JDK1.5版本后退出了Lock接口,该接口就可以清晰的表示程序应该在哪个位置上上锁,又是在哪个位置上解锁。我们用实现Lock接口的子类ReentrantLock来进行模拟,代码如下:

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 package cn.org.yinzhengjie.note;
     7 
     8 import java.util.concurrent.locks.Lock;
     9 import java.util.concurrent.locks.ReentrantLock;
    10 
    11 class Tickets implements Runnable{
    12     //定义出售的票源
    13     private int ticket = 20;
    14     //在类的成员位置创建lock获取锁
    15     private Lock lock = new ReentrantLock();
    16     
    17     @Override
    18     public void run() {
    19         while(true) {
    20             payTicket();
    21         }
    22     }
    23     //用lock锁也可以进行锁操作,可以和synchronzied实现同样的效果,并且可以清晰的在程序中看出在哪个位置上锁和解锁。
    24     public  void payTicket() {
    25             //调用Lock接口方法获取锁
    26             lock.lock();
    27             //对于票数大于0才可以出售
    28             if( ticket > 0 ) {
    29                 try {
    30                     Thread.sleep(50);
    31                     System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票
    ",ticket--);
    32                 } catch (InterruptedException e) {
    33                     e.printStackTrace();
    34                 }finally {
    35                     //释放锁,调用Lock接口方法unlock
    36                     lock.unlock();
    37                 }
    38             }
    39     }
    40 }
    41 
    42 public class ThreadDemo {
    43     public static void main(String[] args) {
    44         //创建Runnable接口实现了对象
    45         Tickets t = new Tickets();
    46         //创建三个Thread类对象,传递Runnable接口实现类
    47         Thread t1 = new Thread(t,"窗口1");
    48         Thread t2 = new Thread(t,"窗口2");
    49         Thread t3 = new Thread(t,"窗口3");
    50         t1.start();
    51         t2.start();
    52         t3.start();
    53     }
    54 }

    四.线程的死锁问题

      线程的死锁前提是:必须是多线程出现同步嵌套。多线程场景下,多个线程互相等待对方释放锁的现象。

      在实际生活中,死锁就好比两个小孩子打架,两个人彼此扯住对方的头发,谁也不撒手,都等着对方先松手为止。再比如我们看电影,尤其是成龙的动作片,经常出现两个搭档,电影中两个搭档去执行任务,一个人拿到了 抢,成龙达到了子弹,然后两个人分别跑到了走廊的两侧,发现彼此都达到了对方想要的东西,他们无法完成开枪的操作。在代码里,就变现为同步的嵌套,即拿着一个锁的同时,想要获取另外一个锁,此时另外一个锁拿走了当前线程需要锁并且等待着当前锁释放锁,即两个线程彼此等待对方释放锁的情况。我们可以用代码来模仿一下

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note;
     8 
     9 class MyLock{
    10     //构造方法私有化
    11     private MyLock() {}
    12     //由于构造方法私有化,让用户只能通过"类名.静态方法"的方式调用(此处我们不考虑反射的情况!)
    13     public final static MyLock lockA = new MyLock();
    14     public final static MyLock lockB = new MyLock();
    15     
    16 }
    17 
    18 class Deadlock implements Runnable{
    19     private int i = 0;
    20     @Override
    21     public void run() {
    22         while(true) {
    23             if( i % 2 == 0 ) {
    24                 //先进去A同步,在进入B同步
    25                 synchronized(MyLock.lockA) {
    26                     System.out.printf("【%s】已经拿到了枪,准备去拿子弹!
    ",Thread.currentThread().getName());
    27                     synchronized(MyLock.lockB) {
    28                         System.out.printf("【%s】成功拿到子弹!
    ",Thread.currentThread().getName());
    29                     }
    30                 }
    31             }else {
    32                 //先进入B同步,在进入A同步
    33                 synchronized(MyLock.lockB) {
    34                     System.out.printf("【%s】已经拿到子弹,准备去拿枪!
    ",Thread.currentThread().getName());
    35                     synchronized(MyLock.lockA) {
    36                         System.out.printf("【%s】成功拿到枪!
    ",Thread.currentThread().getName());
    37                     }
    38                 }
    39             }
    40             i++;
    41         }
    42     }
    43 }
    44 
    45 
    46 
    47 public class DeadLockDemo {
    48     public static void main(String[] args) {
    49         
    50         Deadlock dead = new Deadlock();
    51         
    52         Thread t1 = new Thread(dead,"成龙");
    53         Thread t2 = new Thread(dead,"李连杰");
    54         
    55         t1.start();
    56         t2.start();
    57     }
    58 }

      以上代码执行结果如下:

    五.线程等待案例展示

     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note1;
     8 
     9 public class Resource {
    10     public String name;
    11     public String sex;
    12     //定义一个标志位,让其默认值为false
    13     public boolean flag = false;
    14 }
    Resource.java 文件内容
     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note1;
     8 
     9 //定义一个输入的线程,对资源对象Resource中成员变量赋值
    10 public class Input implements Runnable {
    11     //让用户在调用时手动传入,若传入的对象是同一个,那么多个线程就可以实现对同一个线程进行操作
    12     private Resource r ;
    13     public Input(Resource r) {
    14         this.r = r;
    15     }
    16     
    17     public void run() {
    18         while(true) {
    19             int i = 0;
    20             while(true) {
    21                 synchronized (r) {
    22                     //表示是true时,表示赋值完成,我们可以让线程进入休眠状态
    23                     if(r.flag) {
    24                         try {
    25                             //让检查进入等待状态,也就是不会执行其下面的代码!
    26                             r.wait();
    27                         } catch (InterruptedException e) {
    28                             e.printStackTrace();
    29                         }
    30                     }
    31                     //如果标志位的值为false,则说明Resource对象并没有赋值,我们需要做的是赋值操作!
    32                     if(i % 2 == 0) {
    33                         r.name = "尹正杰";
    34                         r.sex = "男";
    35                     }else {
    36                         r.name = "yinzhengjie";
    37                         r.sex = "man";
    38                     }
    39                     //以上操作完成了赋值,标记改为true!
    40                     r.flag = true;
    41                     //此时将Output线程唤醒,让对方知道赋值已经完成,可以来取值啦!
    42                     r.notify();
    43                 }
    44                 i++;
    45             }
    46         }
    47     }
    48 
    49 }
    Input.java 文件内容
     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note1;
     8 
     9 //定义输出线程,对资源对象Resource中成员变量,输出值。
    10 public class Output implements Runnable {
    11     //让用户在调用时手动传入,若传入的对象是同一个,那么多个线程就可以实现对同一个线程进行操作
    12     private Resource r ;
    13     public Output(Resource r) {
    14         this.r = r;
    15     }
    16 
    17     public void run() {
    18         while(true) {
    19             //注意,在选择锁的时候,若锁不相同,可能存在和我们期望的结果有偏差!
    20             synchronized (r) {
    21                 //判断标志位的值是否为false,如果是则说明其是等待状态,我们需要的就是去取值!
    22                 if(!r.flag) {
    23                     try {
    24                         r.wait();
    25                     } catch (InterruptedException e) {
    26                         e.printStackTrace();
    27                     }
    28                 }
    29                 System.out.println(r.name+"==="+r.sex);
    30                 //标记改为false,
    31                 r.flag = false;
    32                 //表示赋值完成,唤醒Input线程。
    33                 r.notify();
    34             }
    35         }
    36     }
    37 
    38 }
    Output.java 文件内容
     1 /*
     2 @author :yinzhengjie
     3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
     4 EMAIL:y1053419035@qq.com
     5 */
     6 
     7 package cn.org.yinzhengjie.note1;
     8 
     9 public class ThreadDemo {
    10     public static void main(String[] args) {
    11         Resource r = new Resource();
    12         
    13         Input in = new Input(r);
    14         Output out = new Output(r);
    15         
    16         Thread t1 = new Thread(in);
    17         Thread t2 = new Thread(out);
    18         
    19         t1.start();
    20         t2.start();
    21     }
    22 }
  • 相关阅读:
    Robot Framework自动化测试 ---视频与教程免费分享
    我读《2017软件测试行业调查报告》
    『性能测试』文章大汇总
    作为一个测试,应该怎样分工呢?
    软件测试流程进阶----软件测试总结心得
    JMeter 聚合报告之 90% Line 参数说明
    JMeter使用技巧
    自动化基础普及之selenium的解释
    Robot Framework自动化测试(四)--- 分层思想
    mysql的几种join 及 full join 问题
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/9005858.html
Copyright © 2011-2022 走看看