美文网首页
synchronized

synchronized

作者: 花_清 | 来源:发表于2019-12-08 15:58 被阅读0次

    demo1
    package syndemo1;

    /**

    • synchronized:加锁的意思,他是对某个对象加锁,而不是对某段代码。

    • 下面例子中创建了一个对象o专门用于加锁。
      */
      public class T1 {
      private int count = 1;
      private Object o = new Object();//o的作用就是充当锁

      public static void main(String[] args) {
      T1 t = new T1();
      t.m();
      }

      public void m(){
      /**
      * 1、要执行下面的代码,必须先拿到o的锁。new Object()在堆里创建了一个对象,o是在栈里指向堆里面的对象。这里实际是
      * 要找堆里面的对象申请锁的。下面统一说是找o申请锁。
      * 2、这里可以理解为向o申请锁,也可以理解为给o加锁,怎么好理解怎么整,但是锁只有一把。
      * 3、第一个线程把这把锁释放了,第二个线程才能申请到这把锁,才能调用下面的方法。一个线程拿到了这把锁别的线程就拿不到了,
      * 这就叫互斥锁。
      */
      synchronized(o){
      count--;
      System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
      }
      }
      }

    demo2
    package syndemo2;

    public class T2 {
    private int count = 2;

    public static void main(String[] args) {
        T2 t = new T2();
        t.m();
    }
    
    /**
     * 任何线程要执行下面的代码,先要给this加锁,这里是给自身加锁。或者说要用这个方法,要先new一个T的对象指向自身。
     * 也就是说this就是T2的对象。
     *
     * m()方法里面“一开始就对this加锁了”,对这种有个简单的写法,见T3。
     */
    public void m(){
        synchronized (this){
            count--;
            System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
        }
    }
    

    }

    demo3
    package syndemo3;

    public class T3 {
    private int count = 3;

    public static void main(String[] args) {
        T3 t = new T3();
        t.m();
    }
    
    /**
     * 对T2里面的简单写法如下。这个例子里面锁定的是当前对象this。
     */
    public synchronized void m(){
        count--;
        System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
    }
    

    }

    demo4
    package syndemo4;
    public class T4 {
    private static int count = 4;

    public static void main(String[] args) {
       m();//这里也可以写为T4.m()
       mm();//这里也可以写为T4.mm()
    }
    
    /**
     *这里m()方法和mm()方法中的两个加锁的效果是一样的。
     * 我们知道万物皆对象,T4.class是将类抽象成了一个对象。
     * 静态方法里面synchronized加锁的对象是当前类的class对象。
     */
    public synchronized static void m(){
        count--;
        System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
    }
    
    public static void mm(){
        /**
         * 静态属性和静态方法的调用是类名.类方法或类名.类属性,这个时候调用方法的时候是没有new一个对象的,所以下面这行代码里
         * 就不能写synchronized(this)。即这里不能给this加锁!这个时候锁定的是当前类的class对象。
         */
        synchronized (T4.class){
            count--;
            System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
        }
    }
    

    }

    demo5
    package syndemo5;

    public class T5 implements Runnable{
    private int count = 10;
    public /synchronized/ void run() {
    count--;
    System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
    }

    public static void main(String[] args) {
        T5 t = new T5();
        for(int i = 0; i < 5; i++){
            new Thread(t, "THREAD" + i).start();
        }
    }
    /**
     * 多次运行之后,可能会拿到如下结果:
     * 线程名:THREAD1    count:8
     * 线程名:THREAD0    count:8
     * 线程名:THREAD2    count:7
     * 线程名:THREAD3    count:6
     * 线程名:THREAD4    count:5
     * 分析:1、上面的代码中只new了一个T5的对象t,而不是在每个线程中都new了对象。所以这些线程是共同访问这个对象的。
     * 2、这5个线程访问的是同一个count,count在堆里面,t在栈里面。
     * 3、这里对运行结果做个分析。第一个线程count--之后还没有打印之前,第二个线程进来了,做了个count--操作,这时候
     *第一个线程才开始打印结果,第二个线程也随之打印,所以前两个线程打印的结果都是count--再count--的结果,即8,后边的三个
     * 线程都是count--立马打印,即结果正确。
     * 解决办法。只要将上面的synchronized打开给加上锁,重复多次执行拿到的结果都是正确的。这里不展示测试结果了。
     */
    

    }

    demo6
    package syndemo6;
    /**

    • 问题:同步和非同步方法是否可以同时调用?
    • 答:可以。理解:m1执行需要锁,m2不管有没有锁都会执行,所以可以同时调用。(注意下面两个方法中的休眠时间)
    • https://blog.csdn.net/zhou_fan_xi/article/details/83752697 lambda表达式引入依赖后报错的解决方法
    • 运行结果:
    • 线程名:t1 m1 start
    • 线程名:t2 m2
    • 线程名:t1 m1 end
      */
      public class T6 {
      public synchronized void m1(){
      System.out.println("线程名:"+Thread.currentThread().getName()+" m1 start");
      try {
      Thread.sleep(10000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println("线程名:"+Thread.currentThread().getName()+" m1 end");
      }
      public void m2(){
      try {
      Thread.sleep(5000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println("线程名:"+Thread.currentThread().getName()+" m2");
      }
      public static void main(String[] args) {
      T6 t = new T6();
      new Thread(()->t.m1(),"t1").start();//在一个线程中调用m1()方法
      new Thread(()->t.m2(),"t2").start();//在另一个线程中调用m2()方法
      }
      }

    demo7
    package syndemo7;
    import java.util.concurrent.TimeUnit;
    /**

    • 下面这个类模拟了一个充钱和查询钱的业务。对充钱的过程加了锁,对查询钱的过程没加锁,在执行过程中可能产生脏读。
      */
      public class Account {
      String name;
      double money;

      //充钱
      public synchronized void setMoney(String name, double money){
      this.name = name;

       /**
        *  这里线程休息了2秒后才把钱设置进去。这里是为了模拟真实义务逻辑复杂的情况,将代码执行时间放大
        *  尽管对写的过程加了锁,但是在写的过程还没有完成的时候进行了读,也就是在休息的这2秒内进行了读,
        *  读是没加锁的,就可能发生脏读。
        */
       try {
           Thread.sleep(2000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
      
       this.money = money;
      

      }
      //查询钱

      /**
      如果业务里面允许暂时性的脏读,那么读是不用加锁的,这样可以提高性能。如果不允许,就读写都加锁,把下面的加锁的代码打开即可。
      /
      public /
      synchronized
      / double getMoney(String name){
      return this.money;
      }

      public static void main(String[] args) {
      Account account = new Account();

    // new Thread(new Runnable() {//开启一个线程,调用setMoney()方法
    // @Override
    // public void run() {
    // account.setMoney("张三",101.0);
    // }
    // }).start();

        //开启一个线程,调用setMoney()方法。下面这行代码等同于上面注释的六行代码
        new Thread(()->account.setMoney("张三",100.0)).start();
    
        try {
            TimeUnit.SECONDS.sleep(1);//线程休息1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.println(account.getMoney("张三"));//查询张三的钱
    
        try {
            TimeUnit.SECONDS.sleep(2);//线程休息2秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.println(account.getMoney("张三"));//查询张三的钱
    }
    

    }

    demo8
    package syndemo8;
    import java.util.concurrent.TimeUnit;
    /**

    • 一个同步方法里面可以调用另一个同步方法。一个线程已经拥有了某个对象的锁,再次申请的时候仍然会得到该对象的锁,
    • 也就是说synchronized获得的锁是可重入的。
      /
      public class T8 {
      public static void main(String[] args) {
      new T8().m1();
      }
      /
      *
      • m1方法和m2在同一个线程(主线程)里面,所以这两个方法都能获得这把锁。即同一个线程同一把锁,所以一个同步方法里面可以调用另一个同步方法。
        */
        synchronized void m1(){
        System.out.println("m1 start");
        try {
        TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        m2();
        }
        synchronized void m2(){
        try {
        TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        System.out.println("m2被调用");
        }
        }

    demo9
    package syndemo9;
    /**

    • 这个类里面模拟了死锁
      */
      public class T9 {
      private final Object o1 = new Object();
      private final Object o2 = new Object();
      private void m1(){
      synchronized(o1){
      System.out.println(Thread.currentThread().getName()+"获取对象o1锁");
      try {
      Thread.sleep(2000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      m2();
      }
      System.out.println("m1结束");
      }
      private void m2(){
      synchronized(o2){
      System.out.println(Thread.currentThread().getName()+"获取对象o2锁");
      m1();
      }
      System.out.println("m2结束");
      }
      public static void main(String[] args) {
      T9 t = new T9();
      new Thread(t::m1, "线程1").start();
      new Thread(t::m2, "线程2").start();
      }
      }

    demo10
    package syndemo1011;
    import java.util.concurrent.TimeUnit;
    /**

    • 一个同步方法里面可以调用另一个同步方法。一个线程已经拥有了某个对象的锁,再次申请的时候仍然会得到该对象的锁,
    • 也就是说synchronized获得的锁是可重入的。下面展示的是继承中可能发生的情况,子类的同步方法里面调用父类的同步方法,不会死锁。
    • 运行结果:
    • child m start
    • m start
    • m end
    • child m end
      /
      public class T10 {
      public synchronized void m(){
      System.out.println("m start");
      try {
      TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println("m end");
      }
      public static void main(String[] args) {
      /
      *
      * 根据前面的知识我们知道:这里的两个m()方法都是给this加锁,而this就是下面new出来的T11的对象。所以这俩个加锁的是同一个对象
      */
      new T11().m();
      }
      }
      class T11 extends T10{
      @Override
      public synchronized void m() {
      System.out.println("child m start");
      super.m();//子类调用父类的同步方法
      System.out.println("child m end");
      }
      }

    demo11
    package syndemo12;
    import java.util.concurrent.TimeUnit;
    /**

    • 程序在执行过程中,如果出现异常,默认情况下锁会被释放。在并发过程中有异常要特别注意,不然会发生不一致的情况。

    • 运行结果请自行观察
      /
      public class T12 {
      int count = 0;
      synchronized void m(){
      System.out.println("线程名:"+Thread.currentThread().getName()+" start");
      while(true){
      count++;
      System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
      try {
      TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      if(count == 6){
      /
      *
      * 这里抛异常,锁将会被释放,如果进行try……catch……,锁就不会被释放。
      */
      int i = 1/0;
      }
      }
      }

      public static void main(String[] args) {
      T12 t12 = new T12();
      Runnable runnable = new Runnable() {
      @Override
      public void run() {
      t12.m();
      }
      };

       new Thread(runnable,"t1").start();//创建第一个线程
      
       try {
           TimeUnit.SECONDS.sleep(10);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
      
       new Thread(runnable,"t2").start();//创建第二个线程
      

      }

    }

    相关文章

      网友评论

          本文标题:synchronized

          本文链接:https://www.haomeiwen.com/subject/lnpmgctx.html