一、什么情况下会产生线程安全问题?
同时满足以下两个条件时:
1,多个线程在操作共享的数据。
2,操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
例1:四个线程卖100张票
Thread-3....sale....100Thread-2....sale....99Thread-0....sale....97Thread-1....sale....98Thread-3....sale....96Thread-1....sale....94Thread-0....sale....94Thread-2....sale....95Thread-1....sale....93Thread-0....sale....92Thread-2....sale....92Thread-3....sale....92Thread-0....sale....91Thread-2....sale....89Thread-3....sale....90Thread-1....sale....91Thread-1....sale....88Thread-3....sale....86Thread-0....sale....88Thread-2....sale....87Thread-2....sale....84Thread-3....sale....84Thread-1....sale....85Thread-0....sale....83Thread-1....sale....82Thread-0....sale....80Thread-3....sale....79Thread-2....sale....81Thread-3....sale....78Thread-2....sale....75Thread-1....sale....76Thread-0....sale....77Thread-2....sale....74Thread-1....sale....71Thread-0....sale....73Thread-3....sale....72Thread-1....sale....70Thread-0....sale....68Thread-3....sale....69Thread-2....sale....67Thread-2....sale....66Thread-3....sale....64Thread-0....sale....63Thread-1....sale....65Thread-2....sale....62Thread-0....sale....62Thread-1....sale....60Thread-3....sale....61Thread-2....sale....59Thread-0....sale....57Thread-3....sale....58Thread-1....sale....59Thread-0....sale....56Thread-1....sale....56Thread-3....sale....55Thread-2....sale....56Thread-1....sale....54Thread-2....sale....54Thread-0....sale....54Thread-3....sale....53Thread-0....sale....52Thread-3....sale....52Thread-2....sale....50Thread-1....sale....51Thread-2....sale....49Thread-0....sale....49Thread-3....sale....48Thread-1....sale....48Thread-2....sale....46Thread-0....sale....44Thread-3....sale....45Thread-1....sale....47Thread-1....sale....43Thread-0....sale....42Thread-2....sale....42Thread-3....sale....41Thread-1....sale....40Thread-0....sale....39Thread-3....sale....39Thread-2....sale....40Thread-2....sale....38Thread-1....sale....37Thread-3....sale....35Thread-0....sale....36Thread-3....sale....34Thread-1....sale....33Thread-0....sale....32Thread-2....sale....31Thread-3....sale....30Thread-1....sale....29Thread-0....sale....29Thread-2....sale....28Thread-3....sale....27Thread-0....sale....25Thread-1....sale....26Thread-2....sale....24Thread-1....sale....23Thread-0....sale....23Thread-3....sale....22Thread-2....sale....21Thread-1....sale....20Thread-3....sale....20Thread-0....sale....20Thread-2....sale....19Thread-3....sale....16Thread-0....sale....17Thread-1....sale....18Thread-2....sale....15Thread-0....sale....13Thread-1....sale....12Thread-3....sale....14Thread-2....sale....11Thread-3....sale....10Thread-0....sale....8Thread-1....sale....9Thread-2....sale....7Thread-3....sale....6Thread-0....sale....5Thread-1....sale....4Thread-2....sale....3Thread-1....sale....2Thread-3....sale....2Thread-2....sale....1Thread-0....sale....2
运行结果
观察结果,我们发现会有多个线程卖到同一张票和卖到0号票的情况,这就是线程安全问题。
解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码 ;
}
这个对象一般称为同步锁。
同步的前提:同步中必须有多 个线程并使用同一个锁。
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
解决例1的线程安全问题代码:
二、同步锁是什么:
同步函数使用的锁是 this。
静态的同步函数使用的锁是该函数所属 字节码文件对象 ,可以用 getClass()方法获取,也可以用 当前类名.class 表示。
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
1 class Ticket implementsRunnable {2 private static int num = 100;3 boolean flag = true;4
5 public voidrun() {6 if(flag)7 while (true) {8 synchronized (Ticket.class)//(this.getClass())同步代码块
9 {10 if (num > 0) {11 try{12 Thread.sleep(10);13 } catch(InterruptedException e) {14 }15 System.out.println(Thread.currentThread().getName() + ".....obj...." + num--);16 }17 }18 }19 else
20 while (true)21 this.show();22 }23
24 public static synchronized void show()//同步函数
25 {26 if (num > 0) {27 try{28 Thread.sleep(10);29 } catch(InterruptedException e) {30 }31 System.out.println(Thread.currentThread().getName() + ".....function...." + num--);32 }33 }34 }35
36 classStaticSynFunctionLockDemo {37 public static voidmain(String[] args) {38 Ticket t = newTicket();39
40 Thread t1 = newThread(t);41 Thread t2 = newThread(t);42
43 t1.start();44 try{45 Thread.sleep(10);46 } catch(InterruptedException e) {47 }48 t.flag = false;49 t2.start();50 }51 }
三、死锁常见情况:
同步嵌套时,两个线程你拿了我的锁,我拿了你的锁,都不释放,造成死锁。
可以记一套死锁情况代码,面试可能用得到。
死锁情况:
四、单例设计模式中的线程安全问题
开发用饿汉式,没有线程安全问题。——饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
面试懒汉式,记住如何解决线程安全问题
下面是我自己加的
五、Lock锁的使用方法
1 class Windowtest implementsRunnable{2 private static int num=100;3 //1实例化ReentrantLock
4 private ReentrantLock lock =newReentrantLock();5 @Override6 public voidrun() {7 while (true){8 try{9 //2.调用锁定方法lock()
10 lock.lock();11
12 if (num > 0) {13 try{14 Thread.sleep(10);15 } catch(InterruptedException e) {16 e.printStackTrace();17 }18
19 System.out.println(Thread.currentThread().getName() + ":" +num);20 num--;21 } else{22 break;23 }24 } finally{25 //3.调用解锁方法unlock()
26 lock.unlock();27 }28 }29 }30 }31
32 public classWindow {33 public static voidmain(String[] args) {34 Windowtest windowtest1=newWindowtest();35 Thread t1 =newThread(windowtest1);36 Thread t2 =newThread(windowtest1);37 Thread t3 =newThread(windowtest1);38
39 t1.setName("窗口1");40 t2.setName("窗口2");41 t3.setName("窗口3");42 t1.start();43 t2.start();44 t3.start();45
46 }47 }
六、synchronized 与 Lock 的对比
1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
2. Lock只有代码块锁,synchronized有代码块锁和方法锁
3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)
优先使用顺序: Lock >同步代码块(已经进入了方法体,分配了相应资源) > 同步方法 (在方法体之外)