多线程安全(一)
2018-07-27 06:24:51来源:博客园 阅读 ()
首先感谢授课XXX老师。
1.什么是线程安全问题
当多个线程共享同一个全局变量,做写的操作时候,可能会受到其他线程的干扰,导致数据出现问题,这种现象就叫做线程安全问题。做读的时候不会产生线程安全问题。
什么安全:多个线程同时共享一个全局变量,做写操作的时候会发生线程安全。
多个线程共享同一个局部变量做写的操作时候,不会发生线程安全问题。
2.如何解决简单的线程安全问题(锁)
所谓简单就是不涉及分布式,集群等行列。
因为用以下几种解决方法完全得不到解决(实际上也就是两种)!!!
① 使用 synchronized 代码块
② 使用 synchronized 函数
③ 使用 静态同步代码块
抢夺cpu资源高的线程会首先拿到锁,然后进入逻辑中进行操作,其他线程只能进行等待,然后这个线程执行完毕后释放锁,这个时候其他线程会争夺这个锁,也就会产生资源争夺的问题,所以synchronized 的效率是很低的,然后抢夺cpu资源高的线程再次进入逻辑进行操作,以此类推。
案例
以抢车票为例,代码会尽量简洁。
100张火车票,两个售票窗口。同时出售火车票,在不解决安全问题的情况下看一看会出现什么问题。
1 package com.mydream.cn; 2 3 class Train extends Thread { 4 int count = 1; // 售票次数 5 @Override 6 public void run() { 7 while (count<=100) { 8 try { 9 Thread.currentThread().sleep(30);//为了让程序满足并发条件,让他进入睡眠状态。好让后面同时进行 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 sale(); 14 } 15 } 16 // 售票方法逻辑 17 public void sale() { 18 System.out.println(count); 19 count++; 20 } 21 } 22 public class TrainDemo { 23 public static void main(String[] args) { 24 Train t = new Train(); 25 Thread th2 = new Thread(t); 26 Thread th1 = new Thread(t); 27 th1.start(); 28 th2.start(); 29 } 30 }
运行结果是出现了101张票!并且在开始时候还出现了重复的票数,例如1,1,3,这种情况,并不是我们想象的1~100。
那么是什么原因呢???
思路分析:
首先定义了一个全局变量count然后开启两个线程但是是一个对象,也就是同时操作一个全局变量。在主函数main中运行他们,th1线程和th2线程因为有一个sleep函数会等待30毫秒,这样就有机会创造出代码并发的可能性。
分析一
1,1,3,这种不按顺序走的逻辑思维。首先两个线程都得到释放,那么就同时进入system.out打印方法中,然后就会打印出1 , 1 ,3这种不安顺序的代码。这种想起来应该很简单的。也就是说同时进入的时候因为都是>=100,第一次一起进入你打印 1 我也打印 1
分析二
第101张票出现的情况,按照上面思路,当运行到第100张的时候,第一个线程打印出了100,然后进行了+1操作,而在判断程序中第二个线程已经进入到sale()逻辑方法中了,此时他是100,因为地换一个线程的+1操作,导致他变成了101,然后因为判断是>=100所以后面的进不来了。才导致会打印出101的情况。
解决方法一
1 // 售票方法逻辑 2 public void sale() { 3 synchronized(this){ 4 // 再次判断逻辑,因为进入到这里可能都是100,再次进行判断即可 5 if (count<=100) { 6 System.out.println(count); 7 count++; 8 } 9 } 10 }
synchronized 代码块,在售票方法逻辑中加入代码块,并且写入判断逻辑,可能有人会说为什么还要在写入判断逻辑?我直接写入判断逻辑不加synchronized 不是一样么,请记住synchronized 是用来进行线程安全同步的,既然是同步那么就会安全,可是synchronized 已经进入了sale()方法中了,那么我要是同时都是100进入那也没毛病啊~~~~。所以在此加入逻辑就会防止101的出现。
synchronized 代码块中括号中的变量该放什么呢?答案是放什么都可以,只要线程中使用的是同一把锁就可以了。
例如:
1 private Object obj = new Object(); 2 3 // 售票方法逻辑 4 public void sale() { 5 synchronized(obj){ 6 // 再次判断逻辑,因为进入到这里可能都是100,再次进行判断即可 7 if (count<=100) { 8 System.out.println(count); 9 count++; 10 } 11 } 12 }
解决方法二
1 // 售票方法逻辑 2 public synchronized void sale() { 3 if (count<=100) { 4 System.out.println(count); 5 count++; 6 } 7 }
synchronized 函数,synchronized 原理就是使用this当锁,我们可以假设一种情况,也是两个线程,分别用不同的锁,这里不同的锁就是 synchronized 函数 和 synchronized 代码块用this当锁,如果发现是同步的,那么就证明了 synchronized 函数 是用this当锁的,相反就不是,在这里答案是是的。
解决方式三
static synchronized 函数 就是在synchronized 函数 前面 加上 static。如果一旦加入了static 那么 synchronized 代码块this锁 不会 和 它同步了。
static 关键字是比所有代码都先编译的,所以也就不会有this的说法,难道你能在static修饰的方法中调用this么?可能是不可以的了。
那么 这种锁 用的是什么呢?答案是 .class 文件 。如何测试呢?相同的方法,把 synchronized 代码块 锁换成 类名.class 测试是否同步。答案是同步的。
模拟 this 和 同步函数是否相同
1 class ThreadTrain2 implements Runnable { 2 // 总共有一百张火车 当一变量被static修饰的话存放在永久区,当class文件被加载的时候就会被初始化。 3 private static int train1Count = 100; 4 private Object oj = new Object(); 5 public boolean flag = true; 6 @Override 7 public void run() { 8 // 为了能够模拟程序一直在抢票的话。 where 9 if (flag) { 10 //执行同步代码块this锁 11 while (train1Count > 0) { 12 13 synchronized (this) { 14 if(train1Count>0){ 15 try { 16 Thread.sleep(50); 17 } catch (Exception e) { 18 // TODO: handle exception 19 } 20 System.out.println(Thread.currentThread().getName()+ ",出售第" + (100 - train1Count + 1) + "票"); 21 train1Count--; 22 } 23 } 24 25 } 26 } 27 else{ 28 // 同步函数 29 while (train1Count > 0) { 30 31 // 出售火車票 32 sale(); 33 } 34 } 35 36 } 37 38 public synchronized void sale() { 39 40 // 同步代码块 synchronized 包裹需要线程安全的问题。 41 // synchronized (oj) { 42 if (train1Count > 0) { 43 try { 44 Thread.sleep(50); 45 } catch (Exception e) { 46 // TODO: handle exception 47 } 48 System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - train1Count + 1) + "票"); 49 train1Count--; 50 } 51 // } 52 53 } 54 } 55 56 public class ThreadDemo2 { 57 58 public static void main(String[] args) throws InterruptedException { 59 ThreadTrain2 threadTrain2 = new ThreadTrain2(); 60 Thread t1 = new Thread(threadTrain2, "窗口①"); 61 Thread t2 = new Thread(threadTrain2, "窗口②"); 62 t1.start(); 63 Thread.sleep(40); 64 threadTrain2.flag=false; 65 t2.start(); 66 } 67 68 }
有多线程安全的解决那么就会出现一些问题,出现死锁状态,也就是说两个锁的状态是你等我解锁,我等你解锁。直接上代码
package com.itmayiedu; class ThreadTrain3 implements Runnable { // 总共有一百张火车 当一变量被static修饰的话存放在永久区,当class文件被加载的时候就会被初始化。 private static int train1Count = 100; private Object oj = new Object(); public boolean flag = true; @Override public void run() { // 为了能够模拟程序一直在抢票的话。 where if (flag) { // 执行同步代码块this锁 while (true) { synchronized (oj) { sale(); } } } else { // 同步函数 while (true) { // 出售火車票 sale(); } } } public synchronized void sale() { // 同步代码块 synchronized 包裹需要线程安全的问题。 synchronized (oj) { if (train1Count > 0) { try { Thread.sleep(50); } catch (Exception e) { // TODO: handle exception } System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - train1Count + 1) + "票"); train1Count--; } } } } public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException { ThreadTrain3 threadTrain3 = new ThreadTrain3(); Thread t1 = new Thread(threadTrain3, "窗口①"); Thread t2 = new Thread(threadTrain3, "窗口②"); t1.start(); Thread.sleep(40); threadTrain3.flag = false; t2.start(); } }
什么是死锁?同步中嵌套同步,导致锁无法释放
上面锁中用到了两个锁,一个是this一个是obj,分析运行程序,现进入true然后马上进入false
true 得到obj锁 进入sale() 得到 this 锁
false 得到this0锁 进入sale() 得到 obj 锁
然后就可能会出现 交叉想等待,这种想法要好好理解,并且重要! 抽象一点就是this被ojb锁掉了 另一个 ojb被this锁掉了 然后互相等待解锁,可是完全等不到,就会出现多线程死锁情况。
多线程有三大特性
原子性、可见性、有序性
什么是原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分,
什么是可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
什么是有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 最详细的java多线程教程来了 2020-06-08
- 系统化学习多线程(一) 2020-06-08
- Java跨平台原理(字节码文件、虚拟机) 以及Java安全性 2020-06-07
- 面试官:用了HTTPS安全了吗?用HTTPS会被抓包吗?我回答不上 2020-06-06
- 如何快速安全的插入千万条数据? 2020-06-03
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash