您的位置:首页 > 编程语言 > Java开发

Java中线程同步锁和互斥锁-多线程编程

2016-05-13 00:41 302 查看
所谓互斥,就是不同线程通过竞争进入临界区(共享的数据和硬件资源),为了防止访问冲突,在有限的时间内只允许其中之一独占性的使用共享资源。如不允许同时写

同步关系则是多个线程彼此合作,通过一定的逻辑关系来共同完成一个任务。一般来说,同步关系中往往包含互斥,同时对临界区的资源会按照某种逻辑顺序进行访问。如先生产后使用

总的来说,两者的区别就是:

互斥是通过竞争对资源的独占使用,彼此之间不需要知道对方的存在,执行顺序是一个乱序。

同步是协调多个相互关联线程合作完成任务,彼此之间知道对方存在,执行顺序往往是有序的。
------------------------------------------------------------

读写锁特点:

1)多个读者可以同时进行读

2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)

3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

互斥锁特点:

一次只能一个线程拥有互斥锁,其他线程只有等待

---------------------------------------------------------
使用同步机制获取互斥锁的情况,进行几点说明:

1、如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。

2、类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问synchronized同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁。

3、访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响。

4、持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。当一个线程A持有一个对象级别锁(即进入了synchronized修饰的代码块或方法中)时,线程也有可能被交换出去,此时线程B有可能获取执行该对象中代码的时间,但它只能执行非同步代码(没有用synchronized修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有对象级别锁,当A线程退出同步代码时(即释放了对象级别锁),如果B线程此时再运行,便会获得该对象级别锁,从而执行synchronized中的代码。

5、持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。例如,在一个类中有三个synchronized方法a,b,c,当线程A正在执行一个实例对象M中的方法a时,它便获得了该对象级别锁,那么其他的线程在执行同一实例对象(即对象M)中的代码时,便会在所有的synchronized方法处阻塞,即在方法a,b,c处都要被阻塞,等线程A释放掉对象级别锁时,其他的线程才可以去执行方法a,b或者c中的代码,从而获得该对象级别锁。

6、使用synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj为对象的引用,如果获取了obj对象上的对象级别锁,在并发访问obj对象时时,便会在其synchronized代码处阻塞等待,直到获取到该obj对象的对象级别锁。当obj为this时,便是获取当前对象的对象级别锁。

7、类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。

8、互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。

------------------------------------------------------
在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:

  由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。

  这里面,这个资源被称为:临界资源(也有称为共享资源)。

 也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。

是如何解决线程安全问题的呢?

  基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。

在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁。举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。

  在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

  在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

-------------------------------------------------

在JDK 1.6中引入了“偏向锁”和“轻量级锁“。锁一共有四种状态:无锁、偏向锁、轻量级锁和重量级锁。锁只能升级,不能降级。当对锁的竞争加剧的时候,锁会发生升级。

1.偏向锁

之所以引入偏向锁,是为了让线程获得锁的代价更低。当一个线程访问同步块并获取锁的时候,会在对象的对象头(对象头包括两部分的信息:一部分是”Mark Word“,主要存放的是哈希码、对象的分代年龄、锁的标记等信息;另一部分是对象的类型指针)和栈帧中的锁记录中存储锁偏向的ID,以后该线程在进入方法的同步块的时候,就检查这个ID(可以理解为一种标记,是一种身份的标识),如果测试成功,表明对象已经获得了锁;如果测试失败,继续测试偏向锁的标识是否设置为1(1的话就是偏向锁),如果没有则使用CAS(Compare And
Swap)锁。

2.轻量级锁

分为加锁和解锁。当线程执行到同步块之前,JVM会首先检查当前线程的栈帧中创建用于存储记录锁记录的空间,并将对象头中Mark Word复制到锁记录中,也称为Displaced Mark Word,然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,则线程获得锁,否则当前线程尝试使用自旋来获取锁。这就是加锁的过程。

这里多次提到CAS,那么CAS是个什么鬼?CAS是Compare and swap(比较和替换)的简写,具体而言就是:当进行CAS操作的时候,需要输入两个数值,一个是旧值,该旧值是原来的值,另一个是新值,也就是发生改变的值,得到这两个值后,在CAS操作期间会去比较旧值是否发生变化,如果没有发生变化就用新值进行替换,如果发生了变化就不进行替换。

那么解锁的过程又是怎样的呢?就是使用CAS操作将Displaced Mark Word替换回对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀,膨胀的结果是导致锁的升级,并进入阻塞状态。直到需要释放锁的线程释放锁并唤醒其他等待的线程。

锁的使用场景

由于偏向锁在线程存在竞争的时候会带来额外的性能开销,所以偏向锁适用于只有一个线程方法同步快的情况;轻量级锁在线程竞争锁的情况下不会导致线程阻塞,但是会通过自旋消耗CPU,所以轻量级锁适用于追求响应时间的情况。重量级锁线程竞争不会使用自旋,但是线程竞争会导致阻塞,所以响应时间比较慢,重量级锁一般使用在追求吞吐量的情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: