面试复习—— synchronized

 

synchronized

线程安全的主要原因

1.存在共享数据(也称临界资源)

2.存在多条线程共同操作这些共享数据

解决问题的根本方法

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

 

互斥锁的特性

互斥性:即再同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样再同以时间只有一个线程对需要同步的代码块(符合操作)进行访问。互斥性也称为操作的原子性。

可见性:必须确保再锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致。

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

 

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

获取对象锁的两种用法

1.同步代码块(synchronized (thihs) synchronized (类的实例对象),锁是小括号(()中的实例对象 )

2.同步非静态方法( synchronized method ),锁是当前对象的实例对象。

获取类锁的两种用法 

1.同步代码块(  synchronized (类.class)), 锁是小括号中的类对象(Class对象)

2.同步静态方法( synchronized static method ),锁是当前对象的类对象( Class 对象)

 

public class SyncThread implements Runnable {
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("A")) {
            async();  //异步
        } else if (threadName.startsWith("B")) {
            syncObjecyBlock1();  //同步代码
        } else if (threadName.startsWith("C")) {
            syncObjectMethod1();  //同步方法
        }else if (threadName.startsWith("D")) {
            syncClassBlock1();
        }else if (threadName.startsWith("E")) {
            syncClassMethod1();
        }
    }

    // synchronized 修饰非静态对象
    private synchronized void syncObjectMethod1() {
        System.out.println(Thread.currentThread().getName() + "  同步方法 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "  同步方法 开始 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "  同步方法 结束 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //方法中有 synchronized(this|object) {} 同步代码块
    private void syncObjecyBlock1() {
        System.out.println(Thread.currentThread().getName() + "  同步代码块 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this) {  //this 就算当前调用此方法的 Thread对象
            try {
                System.out.println(Thread.currentThread().getName() + "  同步代码块 开始 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "  同步代码块 结束 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

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

    //类锁 同步代码块
    private void syncClassBlock1(){
        System.out.println(Thread.currentThread().getName() + "  同步类代码块 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SyncThread.class) {  //this 就算当前调用此方法的 Thread对象
            try {
                System.out.println(Thread.currentThread().getName() + "  同步类代码块 开始 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "  同步类代码块  结束 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //类锁 修饰静态方法
    private synchronized  static void syncClassMethod1() {
        System.out.println(Thread.currentThread().getName() + "  同步类 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "  同步类 开始 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "  同步类 结束 " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class SyncDemo {
    public static void main(String[] args) {
        //下面的线程都使用同一个锁
        // 如果使用的是 不同对象 所有线程会编程异步 ( Thread A1 = new Thread(new SyncThread(), "A1"); 就算创建不同对象
        //同一个类不同对象锁 是互补干扰的
        SyncThread syncThread = new SyncThread();
        //异步执行
        /*从结果中可以发现 A1 A2线程是异步执行的 A1启动之后,A2并未等待A1结束就开始执行start
        并且A2线程 还比 A1线程先结束了  并且可以看到 A开头的线程并未受到其他线程同步代码块的影响
        * */
        Thread A1 = new Thread(syncThread, "A1");
        Thread A2 = new Thread(syncThread, "A2");
        //同步代码块执行
        /*
        * B类线程是同步的 其中一个线程在访问对象代码块时 另一个访问对象同步代码块的线程会被阻塞
        * 看到结果就可以看得出 B2执行完之后 B1才能开始执行 是顺序执行的
        * 原因是 synchronized()方法是要等待当前对象的 SyncThread 的同步锁
        * 输出 同步代码块  这条语句还是异步的  没有相互等待
        * */
        Thread B1 = new Thread(syncThread, "B1");
        Thread B2 = new Thread(syncThread, "B2");
        //同步方法执行
        /*
        synchronized 的修饰方法 C 类的线程都是同步的  C的 同步方法语句输出  和 开始都是同时发生的
        一个线程在访问对象的同步方法的时候 另一个访问同步方法的线程 会被阻塞
        * */
        Thread C1 = new Thread(syncThread, "C1");
        Thread C2 = new Thread(syncThread, "C2");

        //在统一对象锁的时候 类锁表现出来的行为和  对象锁表现的行为是一致的
        Thread D1 = new Thread(syncThread, "D1");
        Thread D2 = new Thread(syncThread, "D2");

        Thread E1 = new Thread(syncThread, "E1");
        Thread E2 = new Thread(syncThread, "E2");

        /*B类线程 和C类线程做对比
        B类线程是 同步代码块 C类线程是 同步方法
        C类线线程 其中一个执行完了之后 B类线程才能开始执行
        所以可以看出  synchronized 同步代码块 和 同步方法 synchronized 锁的是同一个对象
        都是共用this 对象
        * */
        A1.start();
        A2.start();
        B1.start();
        B2.start();
        C1.start();
        C2.start();

        D1.start();
        D2.start();
        E1.start();
        E2.start();
    }
}

结果
B1  同步代码块 18:11:36
B2  同步代码块 18:11:36
C1  同步方法 18:11:36
D1  同步类代码块 18:11:36
E2  同步类 18:11:36
C1  同步方法 开始 18:11:36
E2  同步类 开始 18:11:36
D2  同步类代码块 18:11:36
A2  异步开始 18:11:36
A1  异步开始 18:11:36
E2  同步类 结束 18:11:37
C1  同步方法 结束 18:11:37
B2  同步代码块 开始 18:11:37
D2  同步类代码块 开始 18:11:37
A1  异步结束 18:11:37
A2  异步结束 18:11:37
B2  同步代码块 结束 18:11:38
D2  同步类代码块  结束 18:11:38
D1  同步类代码块 开始 18:11:38
B1  同步代码块 开始 18:11:38
B1  同步代码块 结束 18:11:39
D1  同步类代码块  结束 18:11:39
E1  同步类 18:11:39
C2  同步方法 18:11:39
E1  同步类 开始 18:11:39
C2  同步方法 开始 18:11:39
C2  同步方法 结束 18:11:40
E1  同步类 结束 18:11:40

对象锁和l类锁的总结

1.有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块

2.若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞

3.若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象同步方法的线程会被阻塞

4.若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象同步方法的线程会被阻塞,反之亦然

5.同一个类的不同对象的对象锁互不干扰

6.类锁由于也是一种特殊的对象锁,因此表现和上述1,2,3,4一致,而由于一个类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的

7.类锁和对象锁何不干扰

 

synchronized底层实现原理

实现synchronized的基础

1.java对象头

2.Monitor

 

对象在内存中的布局

1.对象头

2.实例数据

3.对齐填充

 

对象头结构

 

Mark Word

考虑JVM的空间效率,Mark Word被设计成为非固定的数据结构,以便存储更多有效的数据,会根据对象本身的状态,复用自己的存储空间。

重量级锁,通常使用的synchronized锁,指针指向的是Monitor的起始地址,每个对象都存在着Monitor与之关联。对象与Monitor存在着多种实现方式,例如Monitor可以与对象一起创建销毁,或者单线程试图获取对象锁池,自动生成。但当Monitor被线程持有之后,便处于锁定状态

 

Monitor:每个java对象天生自带了一把看不见的锁

Monitor是由ObjectMonitor实现的  查看ObjectMonitor源码

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/3ece33697a35/src/share/vm/runtime/objectMonitor.hpp

ObjectMonitor里面会有两个队列,一个是WaitSet 等待池 另一个是 EntryList 锁池用来保存ObjectWait的对象列表,每个对象锁的线程都会被封装称ObjectWait来保存到里面。

_owner 是指向持有ObjectMonitor 对象的线程,当多个线程同时访问,同一个代码的时候,首先会进入到 EntryList 集合里面,

当线程获取到线程的Monitor后,就进入到_object区,并把Monitor中的_owner变量设置为当前线程,同时Monitor的 _count就会+1。若线程调用wait方法,将释放当前持有的Monitor。_owner就会被恢复称NULL,_count也会被-1。同时该线程的objectwait实例就会被记录到_WaitSet集合中,等待被唤醒。若当前线程执行完毕,_WaitSet也会释放 Monitor 锁。并复位对应变量的值,以便其他线程进入获取Monitor锁。

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;  //等待池
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //锁池
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

 

Monitor锁的竞争,获取与释放

重入

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

 

早期的synchronized缺点

1.早期版本,synchronized属于重量级锁,依赖于Mutex Lock实现

2.线程之间的切换需要从用户转换到核心态,开销比较大

 

java6以后,synchronized性能得到了很大的提升 因为有了下面的技术

Adaptive Spinning(自适应自旋锁)   Lock Eliminate(锁消除) Lock Coarasening(锁初始化) Lightweight Locking(轻量级锁)

Biased Locking(偏向锁)

这些技术都是为了在线程之间更高效的共享数据,以及解决竞争问题,从而提高程序的执行效率

 

自旋锁

1.许多情况下,共享数据的锁定状态持续时间比较短,切换线程不值得。

2.通过让线程执行忙循环等待锁的释放,不让出CPU

缺点:若锁被其他线程长时间占用,会带来许多性能上的开销

 

自适应自旋锁

1.自旋的次数不再固定

2.由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定

 

 

锁消除

更彻底的优化

JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁

 

public class StringBufferWithoutSync {
    public void add(String str1,String str2){
        //StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他线程引用
        //因此sb属于不可能共享的资源,JVM会自动消除内部的锁
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }

    public static void main(String[] args) {
        StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
        for (int i = 0; i < 100; i ++){
            withoutSync.add("aaa","bbb");
        }
    }
}

synchronized的四种状态

无锁,偏向锁,轻量级锁,重量级锁 

锁膨胀方向:无锁—>偏向锁—>轻量级锁—>重量级锁

 

偏向锁:减少同一线程获取锁的代价

1.大多数情况下,锁不存在竞争,总是由同一线程多次获得

核心思想:如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程ID等于Mark Word的ThreadID即可,这样就省去了大量有关锁申请的操作

不适用于竞争锁比较激烈的多线程场合

 

轻量级锁

轻量级锁是由偏向锁来升级的,偏向锁运行在一个线程进入同步代码块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁

使用的场景:线程交替执行同步块

若存在同一时间访问同一锁的情况,就会导致轻量级锁级别膨胀为重量级锁

 

锁的内存语义

当线程释放锁时, Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;

而当线程获取锁时, Java内存模型会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

 

偏向锁,轻量级锁,重量级锁的汇总

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值