对比同步块和同步方法--->粒度更小的锁定资源,尽可能地提升性能
package iostudy.synchro; /** * 测试同步方法和同步块对粒度更小地资源锁定 * 感受性能上地差异 * @since JDK 1.8 * @date 2021/06/18 * @author Lucifer */ public class SynBlockTestNo3 { public static void main(String[] args) { /*资源实现类*/ SynWeb12306 synWeb12306 = new SynWeb12306(); /*多个线程代理对象*/ new Thread(synWeb12306, "代劳").start(); new Thread(synWeb12306, "一楼").start(); new Thread(synWeb12306, "丙楼").start(); } } /** * 创建内部资源类 */ class SynWeb12306 implements Runnable{ /*设置资源数量*/ private int ticketNums = 10; private boolean flag = true; /*重写接口run方法,实现具体逻辑*/ @Override public void run(){ while (flag){ /*模拟线程等待*/ try { Thread.sleep(100); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*调用内部具体实现逻辑的方法*/ test3(); } } /** * 写一个内部具体实现逻辑的方法 * 同步方法 */ public synchronized void test1(){ if (ticketNums<0){ /*开关改变*/ flag = false; /*结束方法*/ return; } /*模拟延时*/ try { Thread.sleep(200); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*获取线程名称、资源数量减少*/ System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); } /** * 写另一个一样逻辑的实现方法 * 同步块 */ public void test2(){ /*明确锁的对象为资源对象*/ synchronized (this){ if (ticketNums<0){ /*开关改变*/ flag = false; /*结束方法*/ return; } /*模拟延时等待*/ try { Thread.sleep(200); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*获取当前线程名称和资源数量*/ System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); } } /*只锁定资源*/ /** * 这样锁会不安全,导致数据不准确 * 原因是因为ticketNums这个资源是会变的,在锁资源的时候大的对象不能变动,里面的资源随便变化 * 大的对象不变,小的资源对象不变。 * 搞清楚对象在变和对象的属性在变的区别 */ public void test3(){ /*明确锁的对象为资源对象*/ synchronized ((Integer) ticketNums){ if (ticketNums<0){ /*开关改变*/ flag = false; /*结束方法*/ return; } /*模拟延时等待*/ try { Thread.sleep(200); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*获取当前线程名称和资源数量*/ System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); } } }
/*只锁定对象的属性--->缩小区域--->还是会有线程不安全问题。范围太小锁不住*/ /*小粒度锁定资源观察数据是否安全*/ public void test4(){ /*只锁定this的ticketNums属性*/ synchronized (this){ if (ticketNums<0){ /*开关变化*/ flag = false; /*结束方法*/ return; } } /*模拟网络延迟*/ try { Thread.sleep(200); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*获取当前线程名称以及资源数量*/ System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); }
/*锁定范围太大--->损耗性能资源*/ /** * 写另一个一样逻辑的实现方法 * 同步块 */ public void test2(){ /*明确锁的对象为资源对象*/ synchronized (this){ if (ticketNums<0){ /*开关改变*/ flag = false; /*结束方法*/ return; } /*模拟延时等待*/ try { Thread.sleep(200); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*获取当前线程名称和资源数量*/ System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); } }
/** * 写另一个一样逻辑的实现方法 * 同步块,缩小锁定范围 */ public void test5(){ /*明确锁的对象为资源对象*/ if (ticketNums<0){ /*开关改变*/ flag = false; /*结束方法*/ return; } /* 上面的部分考虑的是没有票的情况 如果有多个线程进来最开始拦截不住还是会出现数据不安全的情况 */ /*只锁定下面部分的范围*/ synchronized (this){ if (ticketNums<0){ /*开关改变*/ flag = false; /*结束方法*/ return; } /* 这里加入判断考虑最后一张票的情况 */ /*模拟延时等待*/ try { Thread.sleep(200); }catch (InterruptedException e){ System.out.println(e.getMessage()); e.printStackTrace(); } /*获取当前线程名称和资源数量*/ System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); } /* 尽可能地考虑锁合理地范围不是指代码地数量而是指数据地完整性 --->双重检测(double checking) 线程安全 两个检查,第一个检查不用锁定,第二个检查需要锁定 */ }