当前位置: 首页 > news >正文

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]张票\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     @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]张票\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     @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]张票\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     @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]张票\n",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】已经拿到了枪,准备去拿子弹!\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     //定义一个标志位,让其默认值为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 }

 

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

相关文章:

  • 三、Mac平台下集成 FFmpeg 框架
  • (汇总)os模块以及shutil模块对文件的操作
  • windows2008搭建FTP限制上传文件大小
  • 「转载」用户使用分享_开源PaaS Rainbond
  • 京东架构师-----解析Hystrix线程隔离技术
  • spring整合kaptcha验证码
  • 线程池参数的意义
  • 顶部BANNER
  • 管理使用Greenplum集群
  • 创建自己的网站博客--Hexo
  • SAP云平台里Global Account和Sub Account的关系
  • 无服务器化是企业 IT 架构的未来吗?
  • Fiddler证书安装(查看HTTPS)
  • 算法之广度优先搜索
  • 常用的集成学习方法
  • @angular/forms 源码解析之双向绑定
  • EOS是什么
  • export和import的用法总结
  • java中具有继承关系的类及其对象初始化顺序
  • Joomla 2.x, 3.x useful code cheatsheet
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Vim Clutch | 面向脚踏板编程……
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • windows下mongoDB的环境配置
  • 大数据与云计算学习:数据分析(二)
  • 订阅Forge Viewer所有的事件
  • 码农张的Bug人生 - 见面之礼
  • 使用 QuickBI 搭建酷炫可视化分析
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 一天一个设计模式之JS实现——适配器模式
  • 湖北分布式智能数据采集方法有哪些?
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (二)Linux——Linux常用指令
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (终章)[图像识别]13.OpenCV案例 自定义训练集分类器物体检测
  • (转)3D模板阴影原理
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)jQuery 基础
  • .gitignore
  • .net 怎么循环得到数组里的值_关于js数组
  • .net6Api后台+uniapp导出Excel
  • @Autowired注解的实现原理
  • @ConfigurationProperties注解对数据的自动封装
  • [ web基础篇 ] Burp Suite 爆破 Basic 认证密码
  • [ 蓝桥杯Web真题 ]-布局切换
  • [22]. 括号生成
  • [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略
  • [AI]文心一言爆火的同时,ChatGPT带来了这么多的开源项目你了解吗
  • [BZOJ5125]小Q的书架(决策单调性+分治DP+树状数组)
  • [C++]运行时,如何确保一个对象是只读的
  • [CareerCup] 2.1 Remove Duplicates from Unsorted List 移除无序链表中的重复项