Java进阶之深入理解synchronized

Java进阶之深入理解synchronized

**

首先区别进程和线程的概念

进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。
进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成。

线程:在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
eg:
QQ running —>进程
QQ running —>进程
线程—>一个进程里面的不同执行路径
二者的区别
1、线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
2、一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
3、进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间 (包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
4、调度和切换:线程上下文切换比进程上下文切换要快得多。

深入分析在多线程情况下synchronized关键字如何保证同步

sync有三种应用方式:

锁实例方法,锁静态方法,锁同步代码块

锁实例方法:用sync修饰实例对象中的实例方法,不包括静态方法。此时获得实例对象的线程可以访问多个被锁的实例方法,此时即为锁重入(锁重入同时支持父类继承),由于其他线程不能再次获得这个实例对象,所以无法访问被锁的实例方法,但是可以访问非同步方法。但是此时也有问题,如果有多个实例对象的话,则可能有多个线程同时获得实例对象的锁,此时多个线程便都可以都共享数据进行修改,这样同样导致了数据的不一致,此时可以将sync作用于静态方法。
锁静态方法:当sync作用于static方法时,锁的对象是当前类的class对象(只有一个),静态成员不属于任何实例,属于类成员,因此通过class锁可以控制静态成员的并发操作。但是当一个线程调用实例对象的非static的sync方法,另一个线程调用类的静态的sync方法,此时是允许的,并不会发生互斥现象,因此,当一个静态方法和一个非静态方法同时对一个共享数据进行操作时依然存在线程安全问题。
锁同步代码块:为了解决方法体较大或者方法体里面有耗时操作,而同步代码却只是一小部分,此时如果对整个方法体进行同步操作,则会得不偿失,因此可以对需要的代码进行同步。

sync底层原理(个人理解)

1、每个Java对象都与一个monitor对象(管程或监视器锁)一一对应,Java对象与monitor对象之间的关系存在多种实现形式,如monitor可以与Java对象一起创建销毁或者当线程试图获得对象锁时自动生成,但是当一个monitor被一个线程持有时,则其处于锁定状态,其他线程无法获得,若试图获得,则会被放置等待队列当中。
2、sync作用于代码块时,底层实现使用的是monitorenter和monitorexit指令,monitorenter指令指向同步代码块开始的地方,monitorexit指令指向同步代码块结束的地方。当执行到monitorenter指令时,线程试图获得对象所对应的monitor的持有权,当对象对应monitor对应的进入计数器为0时,线程可以成功获得monitor的使用权,同时此时支持锁重入,每次重入都会将计数器加1,退出时减1,倘若其他线程已经获得了该对象的monitor,此时该线程则会被阻塞。
3、sync作用于方法时,jvm通过从方法常量池中的方法表结构中的acc-sync访问标志位来确定一个方法是不是同步方法,当发生方法调用时,调用指令首先检查acc-sync是否设置,如果设置了则线程需要先获得monitor后才能执行方法,如果获得不了monitor则会阻塞,最后无论线程是正常完成还是非正常完成都会释放monitor,如果一个同步方法在执行期间抛出了异常,并且方法内部没有处理异常的逻辑,那么当异常被抛出之后monitor将会被自动释放。
4、sync在早期属于重量级锁,效率低下,因为monitor依赖于操作系统的Mutex-Lock实现,而操作系统实现线程的切换时需要从用户态转换到内核态,时间成本较高。后期Java进行了sync的锁升级(轻量级锁和偏向锁)。
5、偏向锁,在针对同一线程多次获得同一把锁引起不必要的代价,从而引入了偏向锁,偏向锁是指当一个线程获得了锁之后便进入到了偏向模式,此时当该线程再次请求该锁时则无需再做任何同步操作,省去了大量申请锁的有关操作,但是在锁竞争激烈的情况下偏向锁就会失效,此时引入了轻量级锁。
6、轻量级锁,由于对于绝大部分的锁,在整个同步周期内都是不存在竞争的(区别于偏向锁)。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。
7、自旋锁,当轻量级锁失败后,虚拟机为了避免线程直接在操作系统层面挂起,所以此时引入了自旋锁。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果此时直接将线程挂起到操作系统层面则可能会得不偿失,因为操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起。但这种方式在有大量线程都在自旋或者拥有锁的线程需要执行时间长的情况下将会耗费大量的cpu,在这种情况下需要将线程升级到重量级锁。
8、锁消除,Java虚拟机在JIT编译时通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间。

本文参考

本文主要参考以下文章,谨以技术分享为目的,将此文搬到CSDN上,如有侵权问题请联系本人,乐于分享提高。

作者:zejian
链接:http://blog.csdn.net/javazejian/article/details/72828483

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值