• Synchronized关键字


    案例引入

    多个线程访问同一个对象,实现两个线程,t1,t2同时对变量i进行++运算,预想最后的结果是20000

    public class SynchronizedDemo1 implements Runnable {
        static  SynchronizedDemo1 ins = new SynchronizedDemo1();
        static int i = 0;
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(ins);
            Thread t2 = new Thread(ins);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
        @Override
        public void run() {
            for (int j = 0; j < 10000; j++) {
                i++;
            }
        }
    }

    代码运行结果

    由结果可以看出,每次运行都会得到不同的结果,那么这是什么原因导致的呢?

    i++,它看上去只是一个操作,实际上包含了三个动作:

    1.读取 i
    2.将 i 加一
    3.将 i 的值写入到内存中

    由于线程并发执行,三个步骤中任何一个步骤,被其他线程打断都可能会导致值丢失,因此当第二个线程取数据时,取到还是原来的数据,所以导致了结果的不准确,这种行为我们称之为线程不安全。

    synchronized

    作用

    官方定义

    Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object'svariables are done through synchronized methods.

    自己总结

    能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

    Synchronized的两种方法

    对象锁

    包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁)(自己指定锁对象)

    • 代码块形式:手动指定锁对象
      public class SynchronizedDemo2 implements Runnable {
      static SynchronizedDemo2 sy = new SynchronizedDemo2();
      @Override    public void run() {
      synchronized (this){
      System.out.println("对象锁代码块形式,线程:"+Thread.currentThread().getName()+", 开始运行");
      try {
      Thread.sleep(3000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
      System.out.println(Thread.currentThread().getName() + "运行结束");
              }
          }
      public static void main(String[] args) {
      Thread t1 = new Thread(sy);
      Thread t2 = new Thread(sy);
      t1.start();
      t2.start();
      while (t1.isAlive() || t2.isAlive()){}
      System.out.println("finished ...");
          }
      }
    • 方法锁形式:synchronized修饰普通方法,锁对象默认为this,或者自己创建一个对象锁
      public class SynchronizedDemo3 implements Runnable {
      
          static SynchronizedDemo3 sy  = new SynchronizedDemo3();
          @Override
          public void run() {
              methon();
          }
      
          public static void main(String[] args) {
              Thread t1 = new Thread(sy);
              Thread t2 = new Thread(sy);
              t1.start();
              t2.start();
              while (t1.isAlive() || t2.isAlive()){}
              System.out.println("finished ...");
          }
      
          public  synchronized void methon() {
              System.out.println("线程对象为:" + Thread.currentThread().getName());
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
          }
      
      }

    类锁

    指synchronized修饰静态的方法或指定锁为Class对象。

    • 只有一个Class对象:Java类可能会有很多个对象,但是只有1个Class对象。
    • 本质∶所以所谓的类锁,不过是Class对象的锁而已。------概念上的存在
    • 用法和效果:类锁只能在同一时刻被一个对象拥有。

    类锁的两种形式

    • 形式1: synchronized加在static方法上
      public class SynchronizedDemo4 implements Runnable{
          @Override
          public void run() {
                  methon();
          }
          static SynchronizedDemo4 sy1 = new SynchronizedDemo4();
          static SynchronizedDemo4 sy2 = new SynchronizedDemo4();
      
          public static void main(String[] args) {
              Thread t1 = new Thread(sy1);
              Thread t2 = new Thread(sy2);
              t1.start();
              t2.start();
              while (t1.isAlive() || t2.isAlive()){}
              System.out.println("finished ...");
          }
      
          public static  synchronized void methon() {
              System.out.println("线程对象:" + Thread.currentThread().getName());
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
          }
      }
    • 形式2: synchronized ( *.class )代码块
    public class SynchronizedDemo5 implements Runnable{
    @Override    public void run() {
                methon();
        }
    static SynchronizedDemo5 sy1 = new SynchronizedDemo5();
    static SynchronizedDemo5 sy2 = new SynchronizedDemo5();
    public static void main(String[] args) {
    Thread t1 = new Thread(sy1);
    Thread t2 = new Thread(sy2);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()){}
    System.out.println("finished ...");
        }
    public  void methon() {
    synchronized (SynchronizedDemo5.class){
    System.out.println("线程对象:" + Thread.currentThread().getName());
    try {
    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
            }
        }
    }

    常见问题总结

    1.两个线程同时访问一个对象的同步方法

     1 public class SynchronizedDemo4 implements Runnable{
     2     @Override
     3     public void run() {
     4             methon();
     5     }
     6     static SynchronizedDemo4 sy1 = new SynchronizedDemo4();
     7     static SynchronizedDemo4 sy2 = new SynchronizedDemo4();
     8 
     9     public static void main(String[] args) {
    10         Thread t1 = new Thread(sy1);
    11         Thread t2 = new Thread(sy2);
    12         t1.start();
    13         t2.start();
    14         while (t1.isAlive() || t2.isAlive()){}
    15         System.out.println("finished ...");
    16     }
    17 
    18     public static  synchronized void methon() {
    19         System.out.println("线程对象:" + Thread.currentThread().getName());
    20         try {
    21             Thread.sleep(2000);
    22         } catch (InterruptedException e) {
    23             e.printStackTrace();
    24         }
    25         System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    26     }
    27 }
    代码

    由代码运行结果可以看出,其运行结果为串行,这是因为修饰的锁为this对象,为同一把锁,必然相互等待只能由一个线程所有。

    2.两个线程访问的是两个对象的同步方法

     1 public class SynchronizedDemo6 implements Runnable{
     2     @Override
     3     public void run() {
     4             methon();
     5     }
     6     static SynchronizedDemo6 sy1 = new SynchronizedDemo6();
     7     static SynchronizedDemo6 sy2 = new SynchronizedDemo6();
     8 
     9     public static void main(String[] args) {
    10         Thread t1 = new Thread(sy1);
    11         Thread t2 = new Thread(sy2);
    12         t1.start();
    13         t2.start();
    14         while (t1.isAlive() || t2.isAlive()){}
    15         System.out.println("finished ...");
    16     }
    17 
    18     public void methon() {
    19         synchronized (this){
    20             System.out.println("线程对象:" + Thread.currentThread().getName());
    21             try {
    22                 Thread.sleep(2000);
    23             } catch (InterruptedException e) {
    24                 e.printStackTrace();
    25             }
    26             System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    27         }
    28     }
    29 }
    代码

      由上结果可以看出,两个线程为并行执行,因为两个线程所用的锁对象this不相同,两个线程互不干扰,呈现了并行执行的效果。

    3.两个线程访问的是synchronized的静态方法

    public class SynchronizedDemo4 implements Runnable{
        @Override
        public void run() {
                methon();
        }
        static SynchronizedDemo4 sy1 = new SynchronizedDemo4();
        static SynchronizedDemo4 sy2 = new SynchronizedDemo4();
    
        public static void main(String[] args) {
            Thread t1 = new Thread(sy1);
            Thread t2 = new Thread(sy2);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()){}
            System.out.println("finished ...");
        }
    
        public static  synchronized void methon() {
            System.out.println("线程对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    }
    View Code

     

     由执行结果可以看出为串行,会一个一个执行,由此可以把看出锁生效。

    4.同时访问同步方法与非同步方法

    public class SynchronizedDemo7 implements Runnable {
        @Override
        public void run() {
            // 使线程再同一时刻访问同一个方法
            if (Thread.currentThread().getName().equals("Thread-0")) {
                methon1();
            } else {
                methon2();
            }
        }
    
        static SynchronizedDemo7 sy1 = new SynchronizedDemo7();
        static SynchronizedDemo7 sy2 = new SynchronizedDemo7();
    
        public static void main(String[] args) {
            Thread t1 = new Thread(sy1);
            Thread t2 = new Thread(sy2);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
            }
            System.out.println("finished ...");
        }
    
        public void methon1() {
            synchronized (this) {
                System.out.println("同步线程对象:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
            }
        }
    
        public void methon2() {
            System.out.println("非同步线程对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    
    }
    View Code

     由执行结果可以看出,synchronized只执行于被修饰的方法,其他方法并不受到影响。所以非同步方法不受到影响

    5.访问同一个对象的不同的普通同步方法

    public class SynchronizedDemo8 implements Runnable {
        @Override
        public void run() {
            // 使线程再同一时刻访问同一个方法
            if (Thread.currentThread().getName().equals("Thread-0")) {
                methon1();
            } else {
                methon2();
            }
        }
        static SynchronizedDemo8 sy1 = new SynchronizedDemo8();
        public static void main(String[] args) {
            Thread t1 = new Thread(sy1);
            Thread t2 = new Thread(sy1);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
            }
            System.out.println("finished ...");
        }
    
        public synchronized void methon1() {
                System.out.println("同步线程1对象:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    
        public synchronized  void methon2() {
            System.out.println("同步线程2对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    }
    View Code

     访问同一个对象的不同的普通方法,由于时同一个对象,所以可以推断出锁对象this指向为同一个锁,所以会串行执行。

    6.同时访问静态synchronized和非静态synchronized方法

    public class SynchronizedDemo9 implements Runnable {
        @Override
        public void run() {
            // 使线程再同一时刻访问同一个方法
            if (Thread.currentThread().getName().equals("Thread-0")) {
                methon1();
            } else {
                methon2();
            }
        }
        static SynchronizedDemo9 sy1 = new SynchronizedDemo9();
        public static void main(String[] args) {
            Thread t1 = new Thread(sy1);
            Thread t2 = new Thread(sy1);
            t1.start();
            t2.start();
            while (t1.isAlive() || t2.isAlive()) {
            }
            System.out.println("finished ...");
        }
    
        public static synchronized void methon1() {
                System.out.println("同步静态线程1对象:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    
        public synchronized  void methon2() {
            System.out.println("同步非静态线程2对象:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
        }
    }
    View Code

     

     由结果可以看出为串行。这是因为被static修饰之后就变成了类锁,锁的对象为class;而没有被static修饰的锁的对象时方法对象本身;他们之间锁不一样,所以互不干扰。

    7.方法抛异常后,会释放锁

    说明:synchronizatied出现异常会自动释放锁,而lock需要再findly中手动释放锁。

    通过展示不抛出异常前和抛出异常后的对比:一旦抛出了异常,第二个线程会立刻进入同步方法,意味着锁已经释放。

    手动抛出异常

     1 public class SynchronizedDemo10 implements Runnable {
     2     @Override
     3     public void run() {
     4         // 使线程再同一时刻访问同一个方法
     5         if (Thread.currentThread().getName().equals("Thread-0")) {
     6             methon1();
     7         } else {
     8             methon2();
     9         }
    10     }
    11     static SynchronizedDemo10 sy1 = new SynchronizedDemo10();
    12     public static void main(String[] args) {
    13         Thread t1 = new Thread(sy1);
    14         Thread t2 = new Thread(sy1);
    15         t1.start();
    16         t2.start();
    17         while (t1.isAlive() || t2.isAlive()) {
    18         }
    19         System.out.println("finished ...");
    20     }
    21 
    22     public  synchronized void methon1() {
    23             System.out.println("同步线程1对象:" + Thread.currentThread().getName());
    24             try {
    25                 Thread.sleep(2000);
    26             } catch (InterruptedException e) {
    27                 e.printStackTrace();
    28             }
    29             throw  new RuntimeException();
    30 //            System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    31     }
    32 
    33     public synchronized  void methon2() {
    34         System.out.println("同步线程2对象:" + Thread.currentThread().getName());
    35         try {
    36             Thread.sleep(2000);
    37         } catch (InterruptedException e) {
    38             e.printStackTrace();
    39         }
    40         System.out.println("线程:" + Thread.currentThread().getName() + " 运行结束");
    41     }
    42 }
    手动抛出异常

     总结

    1.一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);

    2.每个实例都对应有自己的一把锁,不同实例之间互不影响; 例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况) ;

    3.无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)

    synchronizatied的性质

     1.可重入

    什么是可重入︰指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁(也叫做递归锁)

    好处:避免死锁、提升封装性

    2.不可中断

    一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等下去。

    相比之下,未来会介绍的Lock类,可以拥有中断的能力,第一点,如果我觉得我等的时间太长了,有权中断现在已经获取到锁的线程的执行;第二点,如果我觉得我等待的时间太长了不想再等了,也可以退出。

    加锁和释放锁的原理:现象、时机、深入JVM看字节码

    现象:每个类都有一个锁,不同的线程都要获取到相应的锁才能执行,否则只能阻塞;而释放、加锁等功能由jvm管理。

    获取释放锁的时机:内置锁

    加锁和释放锁的 等价代码

    public class SynchronizedDemo11 {
        Lock lock = new ReentrantLock();
        public static void main(String[] args) {
            SynchronizedDemo11 sy1 = new SynchronizedDemo11();
            sy1.methon1();
            sy1.methon2();
        }
    
        // method1 等价于 method2
        public synchronized void methon1() {
            System.out.println("synchronized lock method");
        }
    
        public void methon2() {
            lock.lock();  // 加锁
            try {
                System.out.println("lock method");
            } finally {
                lock.unlock();  // 释放锁
            }
        }
    }
    View Code

    深入JVM看字节码

    字节码中哪里体现的某个线程已经获取了该对象的锁:monitorenter 和 monitorexit

    反编译流程

    首先新建一个Java类

    package concurrency_code;
    public class SynchronizedDemo12  {
        private Object object = new Object();
        public  void  insert(Thread thread){
            synchronized (object){
    
            }
        }
    }
    Java类

    接着通过Javac 编译成字节码文件

    接着使用 javap -verbose SynchronizedDemo12.class 命令查看字节码文件

     

    反编译后可以看到 第六行的 monitorenter  和 第8行的 monitorexit

    可重入原理∶加锁次数计数器


    保证可见性的原理︰内存模型

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    开启power management功能有坑,ESP32串口频繁出现UART_BREAK中断
    ESP32音频开发板ESP32-Korvo V1.1踩坑
    驱动开发常用位运算
    ESP8266 RTOS 开发笔记(4)串口透传
    mosquitto服务状态监控(转载)
    ESP8266 RTOS 开发笔记(3)用户参数
    ESP8266 RTOS 开发笔记(2)TCP Client+Server
    ESP8266 RTOS 开发笔记(1)STA+AP模式(共存)
    C uint32 转 uint8
    python 简易计算器(只能计算加减乘除和括号)
  • 原文地址:https://www.cnblogs.com/hoganhome/p/14811969.html
Copyright © 2020-2023  润新知