java多线程——多线程的安全问题

模拟火车票售票程序:

 1 /**
 2  * 售票案例:售票的动作被多个线程同时执行
 3  */
 4 class Ticket implements Runnable{
 5     //描述票的数量
 6     private int tickets=100;
 7     //售票,线程任务中通常都有循环结构
 8     @Override
 9     public void run() {
10         while(true) {
11             if(tickets>0) {//此处可能线程1,2,3,4都进入,那么就可能出现输出tickets为负值的错误数据
12                 try {
13                     Thread.sleep(200);
14                 } catch (InterruptedException e) {
15                     //待处理
16                 }
17                 System.out.println(Thread.currentThread().getName()+"------"+tickets--);
18             }    
19         }
20     }
21 }
22 
23 public class TicketSellDemo {
24     public static void main(String[] args) {
25         Ticket t=new Ticket();
26         
27         Thread t1=new Thread(t);
28         Thread t2=new Thread(t);
29         Thread t3=new Thread(t);
30         Thread t4=new Thread(t);
31         
32         t1.start();
33         t2.start();
34         t3.start();
35         t4.start();
36     }
37 }

1、线程安全问题出现的原因:

(1)多个线程操作共享的数据;

(2)线程任务操作共享数据的代码有多条(多个运算)。

2、解决思路:

只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,在执行过程中,不要让其他线程参与运算。那么如何在代码中体现呢?java中解决此问题:

(1)通过代码块完成,这个代码块叫做同步代码块,使用关键字synchronized

java多线程——多线程的安全问题-风君雪科技博客java多线程——多线程的安全问题-风君雪科技博客

 1 /**
 2  * 售票案例:售票的动作被多个线程同时执行
 3  */
 4 class Ticket implements Runnable{
 5     //描述票的数量
 6     private int tickets=20;
 7     //售票,线程任务中通常有循环结构
 8     private Object obj=new Object();//用来处理安全问题
 9     @Override
10     public void run() {
11         while(true) {
12             synchronized(obj)
13             {
14                 if(tickets>0) {
15                     try { Thread.sleep(200);} catch (InterruptedException e) {/*待处理*/}
16                     System.out.println(Thread.currentThread().getName()+"------"+tickets--);
17                 }    
18             }
19         }
20     }
21 }
22 
23 public class TicketSellDemo {
24     public static void main(String[] args) {
25         Ticket t=new Ticket();
26         
27         Thread t1=new Thread(t);
28         Thread t2=new Thread(t);
29         Thread t3=new Thread(t);
30         Thread t4=new Thread(t);
31         
32         t1.start();
33         t2.start();
34         t3.start();
35         t4.start();
36     }
37 }

View Code

同步的前提:必须保证多个线程中使用的是同一个锁。例如:若将上述代码中synchronized(obj)改为synchronized(new Object()),安全问题仍然不能解决。

(2)使用同步函数(方法)

同步函数使用的锁是this,静态同步函数使用的锁是字节码文件对象,类名.class.

java多线程——多线程的安全问题-风君雪科技博客java多线程——多线程的安全问题-风君雪科技博客

 1 /**
 2  * 售票案例:售票的动作被多个线程同时执行
 3  */
 4 class Ticket implements Runnable{
 5     //描述票的数量
 6     private int tickets=20;
 7     //售票,线程任务中通常有循环结构
 8     @Override
 9     public void run() {
10         //method1();
11         while(true) {
12             sale();
13         }
14     }
15     
16     public synchronized void sale() {
17             if(tickets>0) {
18             //    try { Thread.sleep(200);} catch (InterruptedException e) {/*待处理*/}
19                 System.out.println(Thread.currentThread().getName()+"------"+tickets--);
20             }    
21     }
22 }
23 
24 public class TicketSellDemo {
25     public static void main(String[] args) {
26         Ticket t=new Ticket();
27         
28         Thread t1=new Thread(t);
29         Thread t2=new Thread(t);
30         Thread t3=new Thread(t);
31         Thread t4=new Thread(t);
32         
33         t1.start();
34         t2.start();
35         t3.start();
36         t4.start();
37     }
38 }

View Code

同步函数和同步代码块有什么区别?

同步函数使用的锁是固定的this,同步代码块使用的锁可以是任意对象。当线程任务只需要一个同步时完全可以使用同步函数。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用同步代码块。同步代码块较为常用。

3、单例懒汉式的并发访问

java多线程——多线程的安全问题-风君雪科技博客java多线程——多线程的安全问题-风君雪科技博客

 1 //饿汉式,多线程并发没有问题。
 2 /*
 3   public class Single {
 4         private static final Single instance=new Single();
 5         private Single() {};
 6         public static Single getInstance() {
 7             return instance;
 8         }
 9     }
10 */
11 
12 //懒汉式:存在线程安全问题,需要加上同步机制synchronized
13 /*
14 public class Single {
15     private static Single instance=null;
16     private Single() {}
17     public static synchronized Single getInstance() {
18         if(instance==null) {
19             instance=new Single();
20         }
21         return instance;
22     }    
23 }
24 */
25 //但是,同步的出现降低了效率.可以通过双重判断,解决效率问题,减少判断次数
26 public class Single {
27     private static Single instance=null;
28     private Single() {}
29     public static Single getInstance() {
30         if(instance==null) {
31             synchronized(Single.class){
32                 if(instance==null) {
33                     instance=new Single();
34                 }
35             }
36         }
37         return instance;
38     }    
39 }

Single

4、死锁

同步的另一个弊端:死锁

情况之一:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步,这时容易引发死锁。

java多线程——多线程的安全问题-风君雪科技博客java多线程——多线程的安全问题-风君雪科技博客

 1 class Test implements Runnable{
 2     private boolean flag;
 3     Test(boolean flag){
 4         this.flag=flag;
 5     }
 6     
 7     public void run() {
 8         if(flag) {
 9             while(true) {//加了循环一定会锁住
10                 synchronized(Mylock.lockA) {
11                     System.out.println(Thread.currentThread().getName()+"....if...lockA");
12                     synchronized(Mylock.lockB) {
13                         System.out.println(Thread.currentThread().getName()+"....if...lockB");
14                     }
15                 }
16             }
17             
18         }else {
19             while(true) {
20                 synchronized(Mylock.lockB) {
21                     System.out.println(Thread.currentThread().getName()+"....else....lockB");
22                     synchronized(Mylock.lockA) {
23                         System.out.println(Thread.currentThread().getName()+"....else.....lockA");
24                     }
25                 }
26             }
27         }
28     }
29 }
30 
31 class Mylock{
32     public static final Object lockA=new Object();
33     public static final Object lockB=new Object();
34 }
35 public class Deadlocks {
36     public static void main(String[] args) {
37         Test t1=new Test(true);
38         Test t2=new Test(false);
39         Thread t11=new Thread(t1);
40         Thread t22=new Thread(t2);
41         t11.start();
42         t22.start();
43     }
44     
45     
46 }

Deadlocks