解决线程同步的三大方法
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、如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!
最后;如果以上部分,有不正确的地方发,希望大家指正,第一次书写博客,还需要多多的学习。(*^__^*) 嘻嘻……