博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java基础-线程操作共享数据的安全问题
阅读量:5927 次
发布时间:2019-06-19

本文共 11641 字,大约阅读时间需要 38 分钟。

                    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     @Override13     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]张票\n",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     @Override15     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]张票\n",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     @Override12     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]张票\n",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     @Override18     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]张票\n",ticket--);32                 } catch (InterruptedException e) {33                     e.printStackTrace();34                 }finally {35                     //释放锁,调用Lock接口方法unlock36                     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     @Override21     public void run() {22         while(true) {23             if( i % 2 == 0 ) {24                 //先进去A同步,在进入B同步25                 synchronized(MyLock.lockA) {26                     System.out.printf("【%s】已经拿到了枪,准备去拿子弹!\n",Thread.currentThread().getName());27                     synchronized(MyLock.lockB) {28                         System.out.printf("【%s】成功拿到子弹!\n",Thread.currentThread().getName());29                     }30                 }31             }else {32                 //先进入B同步,在进入A同步33                 synchronized(MyLock.lockB) {34                     System.out.printf("【%s】已经拿到子弹,准备去拿枪!\n",Thread.currentThread().getName());35                     synchronized(MyLock.lockA) {36                         System.out.printf("【%s】成功拿到枪!\n",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     //定义一个标志位,让其默认值为false13     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 }

 

转载于:https://www.cnblogs.com/yinzhengjie/p/9005858.html

你可能感兴趣的文章
数值积分中的辛普森方法及其误差估计
查看>>
Web service (一) 原理和项目开发实战
查看>>
跑带宽度多少合适_跑步机选购跑带要多宽,你的身体早就告诉你了
查看>>
广平县北方计算机第一届PS设计大赛
查看>>
深入理解Java的接口和抽象类
查看>>
java与xml
查看>>
Javascript异步数据的同步处理方法
查看>>
快速排序——Java
查看>>
unity游戏与我
查看>>
187. Repeated DNA Sequences
查看>>
iis6 zencart1.39 伪静态规则
查看>>
SQL Server代理(3/12):代理警报和操作员
查看>>
基于事件驱动的DDD领域驱动设计框架分享(附源代码)
查看>>
Linux备份ifcfg-eth0文件导致的网络故障问题
查看>>
2018年尾总结——稳中成长
查看>>
行列式的乘法定理
查看>>
linux下内存释放问题
查看>>
让Java和JavaScript进行交互
查看>>
LINQ之路12:LINQ Operators之数据转换(Projecting)
查看>>
SQL Server:数据库角色
查看>>