还是之前卖票的例子:
class Test implements Runnable { private int num = 50; Object obj = new Object(); public void run () { while (true) { if (num >= 0) { try { Thread.sleep(20); } catch (Exception e) { // TODO: handle exception System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+">>"+num--); } } } } public class RUNNABLE { public static void main (String[] args) { Test t = new Test(); Thread a = new Thread(t); Thread b = new Thread(t); Thread c = new Thread(t); Thread d = new Thread(t); a.start(); b.start(); c.start(); d.start(); } }
在打印结果发现出现了 -1,-2,这与实际是不相符的,也许你会有疑问,不是num >= 0的时候才输出结果的吗,为什么会出现负数呢?
这就是多线程的安全问题。
解释一下:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
假如说输出结果是这样的:
Thread-2...0
Thread-0...-1
Thread-3...-2
可能造成这样的结果的原因是:
当num=0的时候0线程来了,睡了,3线程来了,也睡了,这个时候2线程sleep的时间到了,醒过来了,这时2线程执行程序,输出结果为0,之后0线程和3线程醒了过来,然后它们接着执行程序,不需要再判断num是否符合条件,因为在它们睡之前就已经判断过了,所以再输出结果的时候,num的值已经变为负数了。这就是会输出负数的原因。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与。
对于多线程的解决方式:同步代码块
格式:
synchronized (对象)
{
//需要被同步的语句
}
解决方案:
class Test implements Runnable { private int num = 50; Object obj = new Object(); public void run () { while (true) { synchronized (obj) { if (num >= 0) { try { Thread.sleep(20); } catch (Exception e) { // TODO: handle exception System.out.println(e.toString()); } System.out.println(Thread.currentThread().getName()+">>"+num--); } } } } } public class RUNNABLE { public static void main (String[] args) { Test t = new Test(); Thread a = new Thread(t); Thread b = new Thread(t); Thread c = new Thread(t); Thread d = new Thread(t); a.start(); b.start(); c.start(); d.start(); } }
哪些代码需要被同步就要看哪些语句在操作共享数据。
同步代码块的工作原理:
synchronized (obj)
{
//被同步的代码
}
在需要被同步的代码外加了synchronized(obj) 就相当于将需要被同步的代码关进了一个房间,obj就相当于这个房间的锁,一开始默认这个锁是打开的,当某一个线程需要执行被同步的代码的时候,需要先判断能不能进入到这个房间,也就是这个锁是不是打开的。如果锁是打开的,那么这个线程就会进入到房间里,从而执行被同步的代码,顺便把锁锁上了,锁上之后,别的线程如果也要运行被同步的代码的时候,也需要先判断能不能进去,但这个时候的锁是锁上的,也就是说,别的线程进不去。也就是说,只要有线程在执行被同步的代码的时候,别的线程是无法再执行的。当线程执行完了被同步的代码的时候,出去这个房间的时候,就将锁打开了,这个时候,其他的线程才有机会进入到这个房间执行代码。
同步的前提:
1.必须是两个及两个以上的线程
就一个线程就不需要锁了,所有的代码都是它来执行
2.必须是多个线程使用同一个锁
意思就是多个线程都进一个房间,如果每个线程进入各自对应的房间,锁与不锁又有什么区别
保证房间内只有一个线程
好处:解决安全性问题
弊端:线程在执行代码的时候需要判断锁,消耗了一定的资源。