java

Java-并发(synchronized)

2019-04-29  本文已影响0人  二妹是只猫

线程安全问题的主要诱因

解决问题的根本方法

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

互斥锁的特性

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

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

获取对象锁的两种方法
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(),..)后会发现,由于不在是同一对象导致对象锁不一致,将不在同步。

获取类锁的两种方法:
  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(),..)后,发现锁的限制依然有效。其实从名字都能看出对象锁的作用域是对象实例,而类锁的作用域是类

对象锁和类锁的总结:

synchronized底层实现原理

实现synchronized的基础

通过字节码文件来看看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性能 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 追求吞吐量,同步块或者同步方法执行时间较长的场景
上一篇下一篇

猜你喜欢

热点阅读