美文网首页java
Java-并发(synchronized)

Java-并发(synchronized)

作者: 二妹是只猫 | 来源:发表于2019-04-29 14:43 被阅读0次

线程安全问题的主要诱因

  • 存在共享数据(也称临界数据)
  • 存在多条线程共同操作这些共享数据

解决问题的根本方法

同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后在对共享数据进行操作

互斥锁的特性

  • 互斥性:即在同一时间只运行一个线程有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性
  • 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在你获得锁时应获得最新恭喜那个变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致

synchronized锁的不是代码,锁的是对象

根据获取的锁的分类:获取对象锁和获取类锁

获取对象锁的两种方法
  • 同步代码块(synchronized(this), synchronized(类实例对象)),锁时小括号()中的实例对象
  • 同步非静态方法(synchronized method),锁是当前对象的实例
public class SyncThread implements Runnable {

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("A")) {
            async();
        } else if (threadName.startsWith("B")) {
            syncObjectBlock1();
        } else if (threadName.startsWith("C")) {
            syncObjectMethod1();
        } else if (threadName.startsWith("D")) {
            syncClassBlock1();
        } else if (threadName.startsWith("E")) {
            syncClassMethod1();
        }

    }

    /**
     * 异步方法
     */
    private void async() {
        try {
            System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法中有 synchronized(this|object) {} 同步代码块
     */
    private void syncObjectBlock1() {
        System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * synchronized 修饰非静态方法
     */
    private synchronized void syncObjectMethod1() {
        System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void syncClassBlock1() {
        System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SyncThread.class) {
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private synchronized static void syncClassMethod1() {
        System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  public static void main(String[] args) {
      SyncThread syncThread = new SyncThread();
      Thread A_thread1 = new Thread(syncThread, "A_thread1");
      Thread A_thread2 = new Thread(syncThread, "A_thread2");
      Thread B_thread1 = new Thread(syncThread, "B_thread1");
      Thread B_thread2 = new Thread(syncThread, "B_thread2");
      Thread C_thread1 = new Thread(syncThread, "C_thread1");
      Thread C_thread2 = new Thread(syncThread, "C_thread2");
      A_thread1.start();
      A_thread2.start();
      B_thread1.start();
      B_thread2.start();
      C_thread1.start();
      C_thread2.start();
  }
C_thread1_SyncObjectMethod1: 10:38:14
B_thread2_SyncObjectBlock1: 10:38:14
B_thread1_SyncObjectBlock1: 10:38:14
C_thread1_SyncObjectMethod1_Start: 10:38:14
A_thread1_Async_Start: 10:38:14
A_thread2_Async_Start: 10:38:14
C_thread1_SyncObjectMethod1_End: 10:38:15
A_thread1_Async_End: 10:38:15
A_thread2_Async_End: 10:38:15
B_thread1_SyncObjectBlock1_Start: 10:38:15
B_thread1_SyncObjectBlock1_End: 10:38:16
B_thread2_SyncObjectBlock1_Start: 10:38:16
B_thread2_SyncObjectBlock1_End: 10:38:17
C_thread2_SyncObjectMethod1: 10:38:17
C_thread2_SyncObjectMethod1_Start: 10:38:17
C_thread2_SyncObjectMethod1_End: 10:38:18

Process finished with exit code 0

通过以上代码可以看到,A线程异步执行不受影响,B和C被上对象锁需要等待被上锁的线程执行完毕后,其他线程才竞争去获取对象锁然后执行。
当把new Thread(syncThread,..)改为new Thread(new syncThread(),..)后会发现,由于不在是同一对象导致对象锁不一致,将不在同步。

获取类锁的两种方法:
  • 同步代码块(synchronized(类.class)),锁时小括号()中的类对象(Class对象)
  • 同步非静态方法(synchronized static method),锁是当前的类对象(Class对象)
  public static void main(String[] args) {
     SyncThread syncThread = new SyncThread();
      Thread A_thread1 = new Thread(syncThread, "A_thread1");
      Thread A_thread2 = new Thread(syncThread, "A_thread2");
      Thread D_thread1 = new Thread(syncThread, "D_thread1");
      Thread D_thread2 = new Thread(syncThread, "D_thread2");
      Thread E_thread1 = new Thread(syncThread, "E_thread1");
      Thread E_thread2 = new Thread(syncThread, "E_thread2");
      A_thread1.start();
      A_thread2.start();
      D_thread1.start();
      D_thread2.start();
      E_thread1.start();
      E_thread2.start();
  }
A_thread1_Async_Start: 10:52:24
D_thread1_SyncClassBlock1: 10:52:24
D_thread2_SyncClassBlock1: 10:52:24
E_thread1_SyncClassMethod1: 10:52:24
A_thread2_Async_Start: 10:52:24
E_thread1_SyncClassMethod1_Start: 10:52:24
A_thread1_Async_End: 10:52:25
A_thread2_Async_End: 10:52:25
E_thread1_SyncClassMethod1_End: 10:52:25
D_thread2_SyncClassBlock1_Start: 10:52:25
D_thread2_SyncClassBlock1_End: 10:52:26
D_thread1_SyncClassBlock1_Start: 10:52:26
D_thread1_SyncClassBlock1_End: 10:52:27
E_thread2_SyncClassMethod1: 10:52:27
E_thread2_SyncClassMethod1_Start: 10:52:27
E_thread2_SyncClassMethod1_End: 10:52:28

Process finished with exit code 0

这里类锁和上面的对象锁效果一致,但是当我们将new Thread(syncThread,..)改为new Thread(new syncThread(),..)后,发现锁的限制依然有效。其实从名字都能看出对象锁的作用域是对象实例,而类锁的作用域是类

对象锁和类锁的总结:
  • 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
  • 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞
  • 若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象的同步方法的线程会被阻塞
  • 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步方法的线程会被阻塞,反之同理
  • 同一个类的不同对象的对象锁互不干扰
  • 类锁由于也是一种特殊的对象锁,因此表现和上诉的前4点一致,而由于一类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的
  • 类锁和对象锁互不干扰

synchronized底层实现原理

实现synchronized的基础
  • Java对象头


    对象头结构
  • Monitor
    存在于对象头中,其具体实现为ObjectMonitor位于虚拟机底层源码:
    ObjectMonitor
    可以看到它里面有多线程那篇中介绍过的waitSet(等待池)和EntryList(锁池),它们就是用来保存ObjectMonitor对象列表。owner指向持有ObjectMonitor的线程。
    当多个线程访问对象时,首先会进入到EntryList获取到锁,然后owen指向当前线程,count加1。调用wait()后,owner设置为null,count减1,当前对象进入到waitSet区

通过字节码文件来看看monitor的运行
java:

public class SyncBlockAndMethod {
    public void syncsTask() {
        //同步代码库
        synchronized (this) {
            System.out.println("Hello");
            synchronized (this){
                System.out.println("World");
            }
        }
    }

    public synchronized void syncTask() {
        System.out.println("Hello Again");
    }

}
同步代码块字节码文件
同步代码块分析synchronized (this):
通过字节码文件3-42行能够看出同步语句块的实现是有monitorenter开始到monitorexit结速,第一次在3行执行synchronized (this)调用monitorenter执行同步,然后4-6行调用PrintStream进行打印,在15行时再次执行synchronized (this)调用monitorenter执行重入,但是我们在后面看到多出了两个monitorexit,这是因为编译器会自动产生一个异常处理器防止在异常情况下monitor能正常退出
重入(补充)

从互斥锁的设计来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入

同步方法分析

同步方法字节码文件
同步方法并没有显示的持有monitor,而是通过ACC_SYNCHRONIZED标志位来隐式的持有monitor,进入到同步状态
锁的分类

偏向锁->轻量级锁->重量级锁(由小到大)

优点 缺点 使用场景
偏向锁 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比存在纳秒级差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 只有一个线程访问同步块或者同步方法的场景
轻量级锁 竞争的线程不会阻塞,提高响应速度 若线程长时间抢不到锁,自旋会消耗CPU性能 线程交替执行同步块或者同步方法的场景
重量级锁 线程竞争不使用自旋,不会消耗CPU性能 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 追求吞吐量,同步块或者同步方法执行时间较长的场景

相关文章

  • Java-并发(synchronized)

    线程安全问题的主要诱因 存在共享数据(也称临界数据) 存在多条线程共同操作这些共享数据 解决问题的根本方法 同一时...

  • JAVA-并发编程(一)

    JAVA-并发编程(一) sschrodinger 2018/11/28 引用 《Java 并发编程的艺术》 方腾...

  • JAVA-并发编程(二)

    JAVA-并发编程(二) sschrodinger 2019/05/14 引用 《Java 并发编程的艺术》 方腾...

  • Java并发编程:Lock Java并发编程:synchronized

  • synchronized锁升级原理分析(偏向锁-轻量级锁-重量级

    synchronized原理分析 初识 synchronized在并发编程中,synchronized对我们来说并...

  • 精品收藏

    Synchronized彻底理解synchronized ConcurrentHashMap并发容器之Concur...

  • Java-线程同步(2)

    Java-线程同步(1)说到Lock对象,但是和synchronized相比似乎只是多了一个tryLock和loc...

  • Lock与synchronized

    Lock与synchronized 的区别 ReentrantLock 拥有Synchronized相同的并发性和...

  • 并发学习之 - ReentrantLock

    synchronized 和 ReentrantLock 上一篇文章 并发学习之 - synchronized 中...

  • 并发——Synchronized

    一、作用 保证同一时刻只有一个线程可以执行某个方法或代码块(原子性、有序性),同时可保证一个线程对共享变量的操作可...

网友评论

    本文标题:Java-并发(synchronized)

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