本文共 5236 字,大约阅读时间需要 17 分钟。
同步代码块
当多个线程使用同一个共享资源时,可以将处理共享资源的代码放在一个使用synchronized关键字来修饰的代码块中,这个代码块被称作同步代码块,格式如下:
synchronized(对象) { 需要被同步的代码}
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有锁。火车上的卫生间就是一个经典的同步例子,对于简单的卖票程序,加入同步之后的代码为:
class SaleTicket implements Runnable{ private int tickets = 300; Object obj = new Object(); public void run() { while (true) { synchronized (new Object()) { if (tickets > 0) { try {Thread.sleep(10);} catch (InterruptedException e) {}//让线程到这里稍微停一下。 System.out.println(Thread.currentThread().getName() + "...." + tickets--); } } } }}class TicketDemo3 { public static void main(String[] args) { //线程任务对象。 SaleTicket t = new SaleTicket(); //创建四个线程。通过Thread类的对象。 Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); }}
同步的前提
有可能出现这样一种情况,多线程安全问题出现后,加入了同步机制,没有想到,安全问题依旧!这时咋办呢?要想到肯定是同步出现了问题,我们只要遵守了同步的前提,就可以解决。同步的前提:
- 必须要有两个或者两个以上的线程;
- 必须是多个线程使用同一个锁。
必须保证同步中只有一个线程在运行。同步在目前的情况下保证了一次只能有一个线程在执行,其他线程进不来,这就是同步的锁机制。
同步的好处
解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步函数
例,银行有一个金库,有两个储户,分别存300元,每次存100元,存3次。试问如下程序是否有安全问题,如果有,该如何解决?
class Bank{ private int sum; public void add(int n) { sum = sum + n; try {Thread.sleep(10);} catch (InterruptedException e) {} System.out.println("sum = " + sum); }}class Customer implements Runnable{ private Bank b = new Bank(); public void run() { for (int x = 0; x < 3; x++) { b.add(100); } }}class BankDemo { public static void main(String[] args) { //1,创建任务对象。 Customer c = new Customer(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); t2.start(); }}
以上程序绝壁是有问题的,那我们又该如何找到问题?我们只要明确以下几点就能找到问题。
- 明确哪些代码是多线程运行代码;
- 明确共享数据;
- 明确多线程运行代码中哪些语句是操作共享数据的。
同步代码块,咱已经很熟了,所以就没必要再用它了,不妨我们试试同步函数,同步函数其实就是在函数上加上了同步关键字进行修饰。
class Bank{ private int sum; public synchronized void add(int n) { sum = sum + n; try {Thread.sleep(10);} catch (InterruptedException e) {} System.out.println("sum = " + sum); }}class Customer implements Runnable{ private Bank b = new Bank(); public void run() { for (int x = 0; x < 3; x++) { b.add(100); } }}class BankDemo { public static void main(String[] args) { //1,创建任务对象。 Customer c = new Customer(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); t2.start(); }}
这里我们思考一个问题,同步函数用的是哪一个锁呢? 答案是函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this。
验证同步函数使用的锁是this
需求:验证同步函数使用的锁是this。
我们可以这样做,启动两个线程,一个线程负责执行同步代码块(使用明锁),另一个线程使用同步函数(使用this锁)。两个线程执行的任务都是一样的(例如都是卖票),如果它们没有使用相同的锁,说明它们没有同步,会出现数据错误。下面我们就以卖票程序来验证该需求。class Ticket implements Runnable { private int tick = 100; Object obj = new Object(); //定义一个boolean标记。 boolean flag = true; public void run() { if(flag) { while(true) { synchronized(this) { if(tick > 0) { try { Thread.sleep(10); } catch(Exception e) {} System.out.println(Thread.currentThread().getName()+"...code:"+tick--); } } } } else { while(true) show(); } } public synchronized void show() { // this if(tick > 0) { try { Thread.sleep(10); } catch(Exception e) {} System.out.println(Thread.currentThread().getName()+"...show...:"+tick--); } }}class ThisLockDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); // 创建一个线程 Thread t2 = new Thread(t); // 创建一个线程 t1.start(); // 主线程停10ms,此刻能运行的线程只有t1 try { Thread.sleep(10); } catch(Exception e) {} t.flag = false; // 主线程醒了之后,t1和t2线程同时运行 t2.start(); }}
验证static同步函数的锁是类名.class
现在我们思考如果同步函数被静态修饰后,使用的锁是什么呢?通过验证,发现不再是this,因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class,该对象的类型是Class。静态的同步方法,使用的锁是该方法所在类的字节码文件对象,即类名.class。
class Ticket implements Runnable { private static int tick = 100; // Object obj = new Object(); boolean flag = true; public void run() { if(flag) { while(true) { synchronized(Ticket.class) { if(tick > 0) { try { Thread.sleep(10); } catch(Exception e) {} System.out.println(Thread.currentThread().getName()+"...code:"+tick--); } } } } else { while(true) show(); } } public static synchronized void show() { if(tick > 0) { try { Thread.sleep(10); } catch(Exception e) {} System.out.println(Thread.currentThread().getName()+"...show...:"+tick--); } }}class StaticMethodDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); // 创建一个线程 Thread t2 = new Thread(t); // 创建一个线程 t1.start(); // 主线程停10ms,此刻能运行的线程只有t1 try { Thread.sleep(10); } catch(Exception e) {} t.flag = false; // 主线程醒了之后,t1和t2线程同时运行 t2.start(); }}
同步函数和同步代码块的区别
- 同步代码块使用的是任意的对象作为锁;
- 同步函数只能使用this作为锁。
同步函数和同步代码块的应用场景
如果说一个类中只需要一个锁,这时可以考虑同步函数,使用this,写法简单。但是,一个类中如果需要多个锁,还有多个类中使用同一个锁,这时只能使用同步代码块。建议使用同步代码块。
转载地址:https://liayun.blog.csdn.net/article/details/82670572 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!