谈谈对java线程的理解(三)--------关键字synchronized

提起关键字synchronized,想必大家都不陌生,或许大家在代码中很少用到这个关键字,毕竟单例模式已经封装,多线程并发问题的话大家一般使用分布式锁,因为关于资源的抢占都是针对数据库或者其他共有资源的,而且现在的部署都是分布式的,所以在平时代码中synchronized的作用显得少了许多。

不过,大家还是会经常看到这个字段,比如,我们知道ArrayList是线程不安全的,那Vector为什么是线程安全的,哦,原来是加了synchronized锁,防止并发增删,hashMap是线程不安全的,并发而且发生hash碰撞的时候,几乎等同于操作一个linkedList,新增的时候也有可能出问题,那ConcurrentHashMap为什么是线程安全的,奥,原来是通过CAS操作和使用synchronized锁槽,细化颗粒度锁,同时避免了HashMap可能会出现的问题。

好了,我们来一起看下synchronized这个关键字吧。

synchronized一般来说,就是锁方法,静态方法以及代码块,还有些地方说会锁类,其实总的来说就两种,类级锁和方法级别的锁。我们来看下代码:

1、锁同步代码块和锁对象。

public class SyncThread implements Runnable {

    private Integer number = 0;

    public void method() throws Exception{
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"-synchronized锁同步代码块:"+number++);
                Thread.sleep(10);
            }
        }

    }

    @Override
    public void run() {
        try {
            method();
        } catch (Exception e) {

        }
    }
}

我们来运行下

    public static void main(String[] args) {

        SyncThread syncThread = new SyncThread();
         Thread thread1 = new Thread(syncThread, "SyncThread1");
         Thread thread2 = new Thread(syncThread, "SyncThread2");
         thread1.start();
         thread2.start();
    }

我们创建了一个SyncThread 对象,然后通过SyncThread 创建了两个线程,然后启动,也就是说两个线程都会去操作同一个number,当然先抢到的会加锁,然后执行完毕,下一个线程继续执行,那么结果就是:

SyncThread1-synchronized锁同步代码块:0
SyncThread1-synchronized锁同步代码块:1
SyncThread1-synchronized锁同步代码块:2
SyncThread1-synchronized锁同步代码块:3
SyncThread1-synchronized锁同步代码块:4
SyncThread2-synchronized锁同步代码块:5
SyncThread2-synchronized锁同步代码块:6
SyncThread2-synchronized锁同步代码块:7
SyncThread2-synchronized锁同步代码块:8
SyncThread2-synchronized锁同步代码块:9

有些伙伴会诧异,怎么不是线程一线程二交替执行累加啊,他们操作的是同一个实现runnable的方法,相当于进入方法之后在synchronized这里就会有一个线程阻塞,哪来的交替执行?

当然希望交替执行的话也可以,创建两个SyncThread就是了。


         Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
         Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
         thread1.start();
         thread2.start();

一运行,铛铛。。

SyncThread1-synchronized锁同步代码块:0
SyncThread2-synchronized锁同步代码块:0
SyncThread2-synchronized锁同步代码块:1
SyncThread1-synchronized锁同步代码块:1
SyncThread2-synchronized锁同步代码块:2
SyncThread1-synchronized锁同步代码块:2
SyncThread2-synchronized锁同步代码块:3
SyncThread1-synchronized锁同步代码块:3
SyncThread2-synchronized锁同步代码块:4
SyncThread1-synchronized锁同步代码块:4

发现线程一和线程二各干各的,本来就是两个类么,加锁锁的又是方法,变量也是普通的变量,两个对象并没有竞争关系啊,注意,是对象。那怎么让它们有竞争关系呢?那就是吧锁升级到类锁,最简单的就是number加上static,这就相当于和对象绑定了,所有的类都是对这一个number操作。当然这个时候,我们还继续只是做代码块的话,肯定会有并发问产生的,锁肯定要上升到类锁或者说是静态对象的锁,我们看下代码:

 private static Integer number = 0;

    public void method() throws Exception{
        synchronized (number) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+"-synchronized锁同步代码块:"+number++);
                Thread.sleep(10);
            }
        }

    }

然后在运行两个对象的那种测试方法:

SyncThread1-synchronized锁同步代码块:0
SyncThread2-synchronized锁同步代码块:1
SyncThread2-synchronized锁同步代码块:2
SyncThread1-synchronized锁同步代码块:3
SyncThread2-synchronized锁同步代码块:4
SyncThread1-synchronized锁同步代码块:5
SyncThread2-synchronized锁同步代码块:6
SyncThread2-synchronized锁同步代码块:7
SyncThread1-synchronized锁同步代码块:8
SyncThread1-synchronized锁同步代码块:9

Process finished with exit code 0

如果还是synchronized (this)的话,会有并发问题,大家可以自己试下。

2、锁方法

    public synchronized void method() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "-synchronized锁同步代码块:" + number++);
            Thread.sleep(10);
        }
    }

我们看下运行结果:

SyncThread1-synchronized锁同步代码块:0
SyncThread2-synchronized锁同步代码块:1
SyncThread2-synchronized锁同步代码块:2
SyncThread1-synchronized锁同步代码块:2
SyncThread1-synchronized锁同步代码块:3
SyncThread2-synchronized锁同步代码块:4
SyncThread1-synchronized锁同步代码块:5
SyncThread2-synchronized锁同步代码块:6
SyncThread1-synchronized锁同步代码块:7
SyncThread2-synchronized锁同步代码块:8

Process finished with exit code 0

这个时候可以看到,锁方法和锁同步代码块其实是一个级别的,都是对方法中的循环进行了加锁,共同抢占资源number。

我们看下静态方法呢?

    public static synchronized void method() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "-synchronized锁同步代码块:" + number++);
            Thread.sleep(10);
        }
    }

静态方法明显是类级别的锁,大家可以看下运行结果:

SyncThread1-synchronized锁同步代码块:0
SyncThread1-synchronized锁同步代码块:1
SyncThread1-synchronized锁同步代码块:2
SyncThread1-synchronized锁同步代码块:3
SyncThread1-synchronized锁同步代码块:4
SyncThread2-synchronized锁同步代码块:5
SyncThread2-synchronized锁同步代码块:6
SyncThread2-synchronized锁同步代码块:7
SyncThread2-synchronized锁同步代码块:8
SyncThread2-synchronized锁同步代码块:9

Process finished with exit code 0

一旦线程一占有了资源,那么这个线程会一直等待这个方法运行完毕,然后才会释放资源,它锁的其实就是这个类。

当然,这样写也是一个效果。


    public synchronized void method() throws Exception {
        synchronized (SyncThread.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "-synchronized锁同步代码块:" + number++);
                Thread.sleep(10);
            }
        }
    }

好了,synchronized锁我们已经大致看明白了,那么synchronized是怎么实现锁机制的呢?

我们都知道,对象被创建在堆中。并且对象在内存中的存储布局方式可以分为3块区域:对象头、实例数据、对齐填充。其中,对象头中就包含锁的信息。下面就是对象头自身运行的数据。

存储内容 标志位 状态
对象的哈希码、对象的分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 重量级锁定
11 GC标记
偏向线程ID,偏向时间戳,分代年龄 01 可偏向

当然,还有一部分是类型指针,JVM通过这个指针来确定这个对象是哪个类的实例。

synchronized锁其实就是一个乐观+悲观混合的锁,先进行乐观锁处理,失败的话就使用悲观锁,悲观锁,也就是重量级锁,是通过monitor(监视器)来实现。synchronized的对象锁,其指针指向的是一个monitor对象的地址,每个对象都会有一个monitor,其中monitor可以和对象一起创建、销毁,也可以在线程试图获取锁的时候自动生成。monitor是由C++实现的,这个有兴趣的可以去探索下,我这里就不多说了。

我们将上面锁定同步代码块的那个方法编译后查看(javap).我们可以看到monitorenter和monitorexit,其实就是锁定和释放锁。在执行monitorenter指令时,首先要尝试获取对象锁,也就是monitor对象,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,那么我们就把锁的数值加1(所以synchronized是可重入的),当然,释放的时候monitor就会减1。

好了,synchronized就讲到这里,那里有不对的,大家可以在评论中指出,不胜感激!

 

No sacrifice,no victory !

猜你喜欢

转载自blog.csdn.net/zsah2011/article/details/107946415