一、什么情况下会产生线程安全问题?
同时满足以下两个条件时:
1,多个线程在操作共享的数据。
2,操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
例1:四个线程卖100张票
public class TicketDemo implements Runnable { private int tickets = 100; public void run() { while (true) { if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "....sale:...." + tickets--); } } } public static void main(String[] args) { TicketDemo ticketDemo = new TicketDemo(); Thread t1 = new Thread(ticketDemo); Thread t2 = new Thread(ticketDemo); Thread t3 = new Thread(ticketDemo); Thread t4 = new Thread(ticketDemo); t1.start(); t2.start(); t3.start(); t4.start(); } }
Thread-3....sale....100 Thread-2....sale....99 Thread-0....sale....97 Thread-1....sale....98 Thread-3....sale....96 Thread-1....sale....94 Thread-0....sale....94 Thread-2....sale....95 Thread-1....sale....93 Thread-0....sale....92 Thread-2....sale....92 Thread-3....sale....92 Thread-0....sale....91 Thread-2....sale....89 Thread-3....sale....90 Thread-1....sale....91 Thread-1....sale....88 Thread-3....sale....86 Thread-0....sale....88 Thread-2....sale....87 Thread-2....sale....84 Thread-3....sale....84 Thread-1....sale....85 Thread-0....sale....83 Thread-1....sale....82 Thread-0....sale....80 Thread-3....sale....79 Thread-2....sale....81 Thread-3....sale....78 Thread-2....sale....75 Thread-1....sale....76 Thread-0....sale....77 Thread-2....sale....74 Thread-1....sale....71 Thread-0....sale....73 Thread-3....sale....72 Thread-1....sale....70 Thread-0....sale....68 Thread-3....sale....69 Thread-2....sale....67 Thread-2....sale....66 Thread-3....sale....64 Thread-0....sale....63 Thread-1....sale....65 Thread-2....sale....62 Thread-0....sale....62 Thread-1....sale....60 Thread-3....sale....61 Thread-2....sale....59 Thread-0....sale....57 Thread-3....sale....58 Thread-1....sale....59 Thread-0....sale....56 Thread-1....sale....56 Thread-3....sale....55 Thread-2....sale....56 Thread-1....sale....54 Thread-2....sale....54 Thread-0....sale....54 Thread-3....sale....53 Thread-0....sale....52 Thread-3....sale....52 Thread-2....sale....50 Thread-1....sale....51 Thread-2....sale....49 Thread-0....sale....49 Thread-3....sale....48 Thread-1....sale....48 Thread-2....sale....46 Thread-0....sale....44 Thread-3....sale....45 Thread-1....sale....47 Thread-1....sale....43 Thread-0....sale....42 Thread-2....sale....42 Thread-3....sale....41 Thread-1....sale....40 Thread-0....sale....39 Thread-3....sale....39 Thread-2....sale....40 Thread-2....sale....38 Thread-1....sale....37 Thread-3....sale....35 Thread-0....sale....36 Thread-3....sale....34 Thread-1....sale....33 Thread-0....sale....32 Thread-2....sale....31 Thread-3....sale....30 Thread-1....sale....29 Thread-0....sale....29 Thread-2....sale....28 Thread-3....sale....27 Thread-0....sale....25 Thread-1....sale....26 Thread-2....sale....24 Thread-1....sale....23 Thread-0....sale....23 Thread-3....sale....22 Thread-2....sale....21 Thread-1....sale....20 Thread-3....sale....20 Thread-0....sale....20 Thread-2....sale....19 Thread-3....sale....16 Thread-0....sale....17 Thread-1....sale....18 Thread-2....sale....15 Thread-0....sale....13 Thread-1....sale....12 Thread-3....sale....14 Thread-2....sale....11 Thread-3....sale....10 Thread-0....sale....8 Thread-1....sale....9 Thread-2....sale....7 Thread-3....sale....6 Thread-0....sale....5 Thread-1....sale....4 Thread-2....sale....3 Thread-1....sale....2 Thread-3....sale....2 Thread-2....sale....1 Thread-0....sale....2
观察结果,我们发现会有多个线程卖到同一张票和卖到0号票的情况,这就是线程安全问题。
解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码 ;
}
这个对象一般称为同步锁。
同步的前提:同步中必须有多 个线程并使用同一个锁。
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
解决例1的线程安全问题代码:
public class TicketDemo implements Runnable { private int tickets = 100; Object obj = new Object(); public void run() { while (true) { synchronized (obj) { if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName() + "....sale...." + tickets--); } } } } public static void main(String[] args) { TicketDemo ticketDemo = new TicketDemo(); Thread t1 = new Thread(ticketDemo); Thread t2 = new Thread(ticketDemo); Thread t3 = new Thread(ticketDemo); Thread t4 = new Thread(ticketDemo); t1.start(); t2.start(); t3.start(); t4.start(); } }
二、同步锁是什么:
同步函数使用的锁是 this。
静态的同步函数使用的锁是该函数所属 字节码文件对象 ,可以用 getClass()方法获取,也可以用 当前类名.class 表示。
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
1 class Ticket implements Runnable { 2 private static int num = 100; 3 boolean flag = true; 4 5 public void run() { 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 class StaticSynFunctionLockDemo { 37 public static void main(String[] args) { 38 Ticket t = new Ticket(); 39 40 Thread t1 = new Thread(t); 41 Thread t2 = new Thread(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 }
三、死锁常见情况:
同步嵌套时,两个线程你拿了我的锁,我拿了你的锁,都不释放,造成死锁。
可以记一套死锁情况代码,面试可能用得到。
死锁情况:
class Testa implements Runnable { private boolean flag; Testa(boolean flag) { this.flag = flag; } public void run() { if (flag) { while (true) synchronized (MyLock.locka) { System.out.println(Thread.currentThread().getName() + "..if locka...."); synchronized (MyLock.lockb) { System.out.println(Thread.currentThread().getName() + "..if lockb...."); } } } else { while (true) synchronized (MyLock.lockb) { System.out.println(Thread.currentThread().getName() + "..else lockb...."); synchronized (MyLock.locka) { System.out.println(Thread.currentThread().getName() + "..else locka...."); } } } } } class MyLock { public static final Object locka = new Object(); public static final Object lockb = new Object(); } class DeadLockTest { public static void main(String[] args) { Testa a = new Testa(true); Testa b = new Testa(false); Thread t1 = new Thread(a); Thread t2 = new Thread(b); t1.start(); t2.start(); } }
四、单例设计模式中的线程安全问题
//饿汉式 class Single { private static final Single s = new Single(); private Single(){} public static Single getInstance() { return s; } } //懒汉式 /* *加入同步是为了解决多线程安全问题。 * *加入双重判断不用每次都判断是否上锁,是为了解决效率问题。 **/ class Single { private static Single s = null; private Single(){} public static Single getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) // -->0 -->1 s = new Single(); } } return s; } }
开发用饿汉式,没有线程安全问题。——饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
面试懒汉式,记住如何解决线程安全问题。