黑马程序员_多线程安全问题
2015-06-08 20:18
573 查看
------- android培训、java培训、期待与您交流! ----------
多线程安全问题
|--如果一个线程进来把所有的语句都执行完,然后下一个线程在进来把程序执行完,那么就没有问题了。
|--java对于多线程的安全问题提供了专业的解决方式;就是同步代码块;
|--如何判断哪些代码需要同步:就要看哪些代码在操作共享数据;-->tick就是共享数据;
现在就是4个线程共同操作一个共享数据,并且没有出现0和负数的情况;
|--同步经典例子:火车上卫生间
|--对象如同锁,持有锁的线程可以在同步中执行;没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁;
|--必须是多个线程使用同一个锁;
弊端:多个线程需要判断锁,较为消耗资源;
问题的描述:当储户在存钱的时候,第一个储户出了100元,但是没有立即执行输出语句,而是卧倒了,这是又来了一个储户,来存钱,存了100后,变成了200元,这时就执行了输出语句,打印了200元,而没有打印100;这就是出现了安全问题;
如何找问题:
|--明确哪些代码是多线程运行代码;
|--明确共享数据;
|--明确多线程运行代码中,哪些语句是操作的是共享数据;
|--同步代码:把同步放在代码中;
|--同步函数:把同步作为修饰符放在函数上;
|--同步函数用的是哪一个锁呢?
|--函数需要被对象调用,那么函数都有一个所属对象应用,就是this,所以同步函数使用的锁是this;
验证:
|--当同步代码块里面的锁的对象是obj的时候,同步函数里面的锁是this的时候,运行的结果是线程0和线程1交替运行,而且还出现了不同步的现象;
|--当同步代码块里面的锁的对象是this的时候,同步函数里面的锁是this的时候,运行的结果只有线程1运行;
|--由此可见,同步函数里面的锁是this锁;
由此可见,同步函数用的是this锁;
|--静态静内存后,内存中没有本类对象,但是一定有该类所属的字节码文件对象;
|--格式是:类名.class;该对象的类型是Class;
当在静态同步函数,发现其的对象不是this锁了,而是该类所属的字节码文件对象,类名.class;
|--在同步函数里面用的锁是this;
|--在静态同步函数上用的锁是该类所属的字节码文件对象,格式:类名.class;
|--想要解决多线程的安全问题,就要先明确同步的两个前提:是不是有多个线程,是不是用的是同一把锁;
多线程安全问题
第一部分
1、多线程安全问题的描述
在卖票程序中多线程安全问题的描述:当4个线程在分别执行程序的时候,0线程进入程序进行判断tick>0;刚判断完条件,这时候0线程就卧倒下了,卧倒就是0线程具备执行的资格但是执行权被其他的线程给强走了,换句话说就是cpu切换到其他人那边去了;这是1线程就获得了执行权,1线程判断条件tick>0,是满足条件的,然后依次类推都是有可能出现这些情况,也就是说4个线程都已经卧倒了,但是这是cpu这是切换到了0线程身上了,这是0线程就直接向下执行了,这时0线程输出的票是1号票,但是这时1线程输出的是0号票,2线程输出的是-1号票,3线程输出的是-2号票,按照人们生活常理是没有0号票还负数的票的;-->这时程序就有可能会出现安全隐患;2、用代码体现多线程安全问题
//实现Runnable class Ticket implements Runnable { private int tick =100; // Object obj = new Object(); public void run() { while(true) { // synchronized(obj) // { if(tick>0) { // sleep方法抛出了异常 try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+"sale:"+tick--); // } } } } } class TicketDemo2 { public static void main(String[] args) { Ticket t = new Ticket(); // 创建线程;需要传一个Ticket对象;要指定run方法所属对象; // Thread(Runnable target) Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); // 分别要开启线程; t1.start(); t2.start(); t3.start(); t4.start(); } }
3、代码运行的结果
4、问题的描述
多线程最恐慌的就是安全问题,通过分析,发现出现了0和-1号票,因此就是多线程的运行出现了安全问题,5、问题的原因
|--当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。|--如果一个线程进来把所有的语句都执行完,然后下一个线程在进来把程序执行完,那么就没有问题了。
6、解决办法
|--对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行;换句话说就是0线程在程序里面执行,1线程即使拿到了执行权也不让1线程执行;那么这样就靠谱了;|--java对于多线程的安全问题提供了专业的解决方式;就是同步代码块;
|--如何判断哪些代码需要同步:就要看哪些代码在操作共享数据;-->tick就是共享数据;
//同步代码块 synchronized(对象) { 需要被同步的代码 }
7、代码体现
//实现Runnable class Ticket implements Runnable { private int tick =500; // 需要放一个对象; Object obj = new Object(); public void run() { while(true) { // 同步代码块 synchronized(obj) { if(tick>0) { // sleep方法抛出了异常 try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } } } class TicketDemo2 { public static void main(String[] args) { Ticket t = new Ticket(); // 创建线程;需要传一个Ticket对象;要指定run方法所属对象; // Thread(Runnable target) Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); // 分别要开启线程; t1.start(); t2.start(); t3.start(); t4.start(); /* Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket(); t1.start(); t2.start(); t3.start(); t4.start(); */ } }
8、运行结果
现在就是4个线程共同操作一个共享数据,并且没有出现0和负数的情况;
9、同步原理
|--四个线程,这时有两个标志位,一个是0,一个是1,当0线程获取到cpu执行权时,判断标志位是1,进入同步代码快中,0线程进入以后,就把标志位恩恩1变成了0;把锁给关上了,然后0线程就判断if语句,满足条件,就开始读取,这时读取到了sleep语句,这是0线程就处于卧倒状态,这时1线程就进入懂啊了语句中,1线程就判断标志位是0,-->但是1线程是进不来的,这时0线程进醒了,就继续执行语句,出了同步,这是0线程就又做了一件事,就是把标志位的0置成了1;这时3线程抢到了执行权,这时3线程就把标志位改成了0;synchronized(obj)-->这个是一个锁,只有拿到了这个锁才可以进入到程序中执行,否则就一直等;|--同步经典例子:火车上卫生间
|--对象如同锁,持有锁的线程可以在同步中执行;没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁;
10、同步前提
|--必须要有两个或者两个以上的线程;|--必须是多个线程使用同一个锁;
11、同步好处和弊端
好处:解决了多线程的安全问题;弊端:多个线程需要判断锁,较为消耗资源;
第二部分
1、同步函数的描述
需求:银行有个金库,有两个储户分别往里面存钱,每次存100,分别存3次;问题的描述:当储户在存钱的时候,第一个储户出了100元,但是没有立即执行输出语句,而是卧倒了,这是又来了一个储户,来存钱,存了100后,变成了200元,这时就执行了输出语句,打印了200元,而没有打印100;这就是出现了安全问题;
如何找问题:
|--明确哪些代码是多线程运行代码;
|--明确共享数据;
|--明确多线程运行代码中,哪些语句是操作的是共享数据;
2、同步函数
同步有两种表现形式:|--同步代码:把同步放在代码中;
|--同步函数:把同步作为修饰符放在函数上;
3、同步函数的锁是this
|--如果卖票的程序在函数上使用同步的话,那么是不OK的,这样输出的是只有0线程一直在运行,把锁就一直锁起来了,其他的线程就没有运行;|--同步函数用的是哪一个锁呢?
|--函数需要被对象调用,那么函数都有一个所属对象应用,就是this,所以同步函数使用的锁是this;
验证:
|--当同步代码块里面的锁的对象是obj的时候,同步函数里面的锁是this的时候,运行的结果是线程0和线程1交替运行,而且还出现了不同步的现象;
|--当同步代码块里面的锁的对象是this的时候,同步函数里面的锁是this的时候,运行的结果只有线程1运行;
|--由此可见,同步函数里面的锁是this锁;
4、代码体现
//卖票的程序 //线程0是同步代码块,线程1是同步函数; class Ticket implements Runnable { private int tick =100; Object obj = new Object(); // boolean型的标记 boolean flag = true; public void run() { // 是true的话 if(flag) { while(true) { // 同步代码块 synchronized(this) { if(tick>0) { try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } // 同步代码快 System.out.println(Thread.currentThread().getName()+"--code--"+tick--); } } } } // 如果是false的话 else while(true) show(); } // 同步函数 public synchronized void show()//这个锁是this; { if(tick>0) { try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } // 同步函数 System.out.println(Thread.currentThread().getName()+"--show--"+tick--); } } } class ThisLockDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); // Thread t3 = new Thread(t); // Thread t4 = new Thread(t); t1.start(); // 让主线程停10毫秒 try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } // 把flag改为false,并开启t2 t.flag =false; t2.start(); // t3.start(); // t4.start(); } }
5、运行结果
由此可见,同步函数用的是this锁;
第三部分
1、同步静态函数描述
|--通过验证法发现同步静态函数出现了不同步的现象;也就是说静态函数用的锁不是this锁;其实静态方法中是没有this的,因为静态中是没有对象的;|--静态静内存后,内存中没有本类对象,但是一定有该类所属的字节码文件对象;
|--格式是:类名.class;该对象的类型是Class;
2、通过代码展现
class Ticket implements Runnable { private static int tick =100; Object obj = new Object(); boolean flag = true; public void run() { if(flag) while(true) { // 该类所属的字节码文件对象; synchronized(Ticket.class) { if(tick>0) { try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+"--code--"+tick--); } } } else while(true) show(); } public static synchronized void show() { if(tick>0) { try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+"--show--"+tick--); } } } class StaticMethodDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try { Thread.sleep(10); } catch (Exception e) { System.out.println(e.toString()); } t.flag =false; t2.start(); } }
3、运行结果
当在静态同步函数,发现其的对象不是this锁了,而是该类所属的字节码文件对象,类名.class;
第四部分
我的总结
|--在同步代码块里面用的锁是任意对象,|--在同步函数里面用的锁是this;
|--在静态同步函数上用的锁是该类所属的字节码文件对象,格式:类名.class;
|--想要解决多线程的安全问题,就要先明确同步的两个前提:是不是有多个线程,是不是用的是同一把锁;
相关文章推荐
- 黑马程序员——Java基础---I/O流(中[字节流])
- 黑马程序员---IO流-File类,递归
- 黑马程序员_Java基础_动态代理
- 黑马程序员----Java对象序列化
- 黑马程序员_Java_反射机制总结
- 恐惧会让你成为一个更糟糕的程序员
- 黑马程序员--while,do……while和for循环的区别
- 设计模式简述
- 转:Java面试题集(1-50)
- 黑马程序员——集合框架
- 转:115个Java面试题和答案——终极列表(上)
- 程序员每天-7
- 黑马程序员——Java基础---Date/ Calender
- 黑马程序员——Java基础---I/O流(上[异常])
- 程序员面试宝典 实现正确匹配(即数组中某值与B中某值相等) P93
- IT十年职业发展规划
- 黑马程序员java学习笔记——IO流
- 黑马程序员—C重点--基本运算
- (PHP开发工程师)我的面试之旅
- Android 面试之横竖屏切换的Activity生命周期