解决线程同步的三大方法

  1、使用synchronize修饰需要同步的代码块。这里就涉及到一个概念叫“对象锁”,我的理解就是java在执行synchronized修饰的代码块时,就要拿到这段代码的执行权限,这里的“权限”,就是“锁”,因为一个对象就只有一个锁,所以一段synchronized修饰的代码,只能同时被一个线程执行,所以当多个线程执行这段代码时,就会“排队执行”。

下面我们模仿一个案例:创建三个线程模拟卖票,票数为10张(下面的三种方法都是基于这个场景)

  我们先不使用synchronized修饰需要同步的代码块

 1 public class Demo1 {
 2     public static void main(String[] args) {
 3         Runnable t = new Ticket();
 4 
 5         new Thread(t).start();
 6         new Thread(t).start();
 7         new Thread(t).start();
 8 
 9     }
10 
11     static class Ticket implements Runnable{
12         private  int count = 10;
13         //Object object = new Object();
14         @Override
15         public void run() {
16             while (true){
17                // synchronized (object){
18                     if (count>0){
19                         System.out.println(Thread.currentThread().getName()+"正在准备卖票");
20                         try {
21                             Thread.sleep(1000);
22                         } catch (InterruptedException e) {
23                             e.printStackTrace();
24                         }
25                         count--;
26                         System.out.println("出票成功,余票"+count);
27 
28                     }else {
29                         return;
30                     }
31                 }
32 
33 
34             }
35         }
36 }

运行结果:

Thread-0正在准备卖票
Thread-1正在准备卖票
Thread-2正在准备卖票
出票成功,余票7
出票成功,余票9
出票成功,余票8
Thread-1正在准备卖票
Thread-0正在准备卖票
Thread-2正在准备卖票
出票成功,余票6
Thread-0正在准备卖票
出票成功,余票5
Thread-2正在准备卖票
出票成功,余票4
Thread-1正在准备卖票
出票成功,余票3
Thread-2正在准备卖票
出票成功,余票2
出票成功,余票1
Thread-1正在准备卖票
Thread-0正在准备卖票
出票成功,余票0
出票成功,余票-1
出票成功,余票-1

  通过运行结果可以看到,三个线程抢着执行,并没有排队,甚至最后出现了“脏数据”,就是因为多个线程同时执行,造成最后的数据错乱。

 

下面我面去掉注释的synchronized代码,这里注意使用synchronized修饰代码时,需要传入一个“锁对象”,任何对象即可。

 

 1 public class Demo1 {
 2     public static void main(String[] args) {
 3         Runnable t = new Ticket();
 4 
 5         new Thread(t).start();
 6         new Thread(t).start();
 7         new Thread(t).start();
 8 
 9     }
10 
11     static class Ticket implements Runnable {
12         private int count = 10;
13         Object object = new Object();
14 
15         @Override
16         public void run() {
17             while (true) {
18                 synchronized (object) {
19                     if (count > 0) {
20                         System.out.println(Thread.currentThread().getName() + "正在准备卖票");
21                         try {
22                             Thread.sleep(1000);
23                         } catch (InterruptedException e) {
24                             e.printStackTrace();
25                         }
26                         count--;
27                         System.out.println("出票成功,余票" + count);
28 
29                     } else {
30                         return;
31                     }
32                 }
33 
34 
35             }
36         }
37     }
38 }


运行结果:

Thread-0正在准备卖票
出票成功,余票9
Thread-0正在准备卖票
出票成功,余票8
Thread-0正在准备卖票
出票成功,余票7
Thread-2正在准备卖票
出票成功,余票6
Thread-2正在准备卖票
出票成功,余票5
Thread-2正在准备卖票
出票成功,余票4
Thread-2正在准备卖票
出票成功,余票3
Thread-2正在准备卖票
出票成功,余票2
Thread-2正在准备卖票
出票成功,余票1
Thread-2正在准备卖票
出票成功,余票0

 

  通过结果不难看到:线程确实是在排队执行,数据也是正常的。

 

2、下面介绍另外一种方法,—同步方法:也就在同步代码块的基础上,把synchronized修饰的部分写成成一个非静态方法。下面看一个案例:

 

 1 public class Demo2 {
 2     public static void main(String[] args) {
 3         Runnable t = new Ticket();
 4 
 5         new Thread(t).start();
 6         new Thread(t).start();
 7         new Thread(t).start();
 8 
 9     }
10 
11     static class Ticket implements Runnable{
12         private  int count = 10;
13         @Override
14         public void run() {
15             while (true){
16                 boolean flag = saleTicket();
17                 if (!flag){
18                     break;
19                 }
20             }
21         }
22         public synchronized boolean saleTicket(){
23             if (count>0){
24                 System.out.println(Thread.currentThread().getName()+"正在准备卖票");
25                 try {
26                     Thread.sleep(1000);
27                 } catch (InterruptedException e) {
28                     e.printStackTrace();
29                 }
30                 count--;
31                 System.out.println("出票成功,余票"+count);
32                 return true;
33 
34             }else {
35                 return false;
36             }
37         }
38     }
39 }

运行结果:

Thread-0正在准备卖票
出票成功,余票9
Thread-0正在准备卖票
出票成功,余票8
Thread-0正在准备卖票
出票成功,余票7
Thread-0正在准备卖票
出票成功,余票6
Thread-0正在准备卖票
出票成功,余票5
Thread-0正在准备卖票
出票成功,余票4
Thread-0正在准备卖票
出票成功,余票3
Thread-0正在准备卖票
出票成功,余票2
Thread-0正在准备卖票
出票成功,余票1
Thread-0正在准备卖票
出票成功,余票0

 

  通过结果发现:线程仍是排队执行,而且数据正确,至于为什么是Thread-0一直卖票,大概是因为,它在执行完saleTicket方法后,又马上能够抢到“锁对象”,又可以执行saleTicket方法。

 

 

 

这里我们对代码做一点小改动(黄色区域是改动的地方),创建三个卖票任务,分别交给三个线程执行。下面看代码:

 

 1 public class Demo2 {
 2     public static void main(String[] args) {
 3         Runnable t1 = new Ticket();
 4         Runnable t2 = new Ticket();
 5         Runnable t3 = new Ticket();
 6 
 7         new Thread(t1).start();
 8         new Thread(t2).start();
 9         new Thread(t3).start();
10 
11     }
12 
13     static class Ticket implements Runnable{
14         private  int count = 5;
15         @Override
16         public void run() {
17             while (true){
18                 boolean flag = saleTicket();
19                 if (!flag){
20                     break;
21                 }
22             }
23         }
24         public synchronized boolean saleTicket(){
25             if (count>0){
26                 System.out.println(Thread.currentThread().getName()+"正在准备卖票");
27                 try {
28                     Thread.sleep(1000);
29                 } catch (InterruptedException e) {
30                     e.printStackTrace();
31                 }
32                 count--;
33                 System.out.println("出票成功,余票"+count);
34                 return true;
35 
36             }else {
37                 return false;
38             }
39         }
40     }
41 }

执行结果:

Thread-0正在准备卖票
Thread-1正在准备卖票
Thread-2正在准备卖票
出票成功,余票4
出票成功,余票4
Thread-2正在准备卖票
Thread-0正在准备卖票
出票成功,余票4
Thread-1正在准备卖票
出票成功,余票3
出票成功,余票3
Thread-2正在准备卖票
出票成功,余票3
Thread-0正在准备卖票
Thread-1正在准备卖票
出票成功,余票2
出票成功,余票2
出票成功,余票2
Thread-2正在准备卖票
Thread-1正在准备卖票
Thread-0正在准备卖票
出票成功,余票1
出票成功,余票1
出票成功,余票1
Thread-1正在准备卖票
Thread-0正在准备卖票
Thread-2正在准备卖票
出票成功,余票0
出票成功,余票0
出票成功,余票0




  通过结果可以看到:三个进程同时执行,但是执行的结果是不合逻辑的。原因是:首先,在执行的synchronized修饰的方法时,系统会判断该线程持有的“对象锁(这里也就是买票的任务)”是否被锁上,很明显我们给三个线程都创建了自己的任务,所以他们执行时,一判断,哦原来自己的“对象锁”都没有 被锁,所以三个线程就各自执行自己的任务,就造成这个现象。所以这里也是我们容易犯的错误:每个线程持有自己 线程对象的那个对象锁。这必然不能产生同步的效果如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!

 

 


3、下面我们我们介绍第三种方法:lock锁(JDK1.5时java.util.concurrent.lockst开始)

    

    就在方法1上做小的改动,下面看案例(黄色区域是改动部分)

 

 1 import java.util.concurrent.locks.Lock;
 2 import java.util.concurrent.locks.ReentrantLock;
 3 
 4 public class Demo3 {
 5     public static void main(String[] args) {
 6         Runnable t = new Ticket();
 7 
 8         new Thread(t).start();
 9         new Thread(t).start();
10         new Thread(t).start();
11 
12     }
13 
14     static class Ticket implements Runnable{
15         private  int count = 10;
16         Lock l = new ReentrantLock();
17         @Override
18         public void run() {
19             while (true){
20               l.lock();
21                     if (count>0){
22                         System.out.println(Thread.currentThread().getName()+"正在准备卖票");
23                         try {
24                             Thread.sleep(10);
25                         } catch (InterruptedException e) {
26                             e.printStackTrace();
27                         }
28                         count--;
29                         System.out.println("出票成功,余票"+count);
30 
31                     }else {
32                         return;
33                     }
34 
35                     l.unlock();
36 
37 
38 
39             }
40         }
41     }
42 }

 

  通过结果可以看出,确实是排队执行,数据也没有异常。

 

 

总结:

1、对于同步代码块,或者同步非静态方法,必须持有“对象锁”且持有的对象锁没有被锁住,才能执行任务

2、如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!

 


最后;如果以上部分,有不正确的地方发,希望大家指正,第一次书写博客,还需要多多的学习。(*^__^*) 嘻嘻……

 

posted @ 2021-03-04 11:59  kunmin  阅读(253)  评论(0编辑  收藏  举报