java线程池中锁的竞争_Java多线程-synchronized锁

Java多线程-synchronized锁

Synchronized原理

synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的.而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因.这种依赖于操作系统互斥锁(Mutex Lock)所实现的锁我们称之为“重量级锁”.

monitor锁定过程

当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者.

如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权.

synchronized锁

Java SE1.6对Synchronized进行了各种优化之后,它并不那么重了.在不同的场景中引入不同的锁优化.

偏向锁:适用于锁没有竞争的情况,假设共享变量只有一个线程访问.如果有其他线程竞争锁,锁则会膨胀成为轻量级锁.

轻量级锁:适用于锁有多个线程竞争,但是在一个同步方法块周期中锁不存在竞争,如果在同步周期内有其他线程竞争锁,锁会膨胀为重量级锁.

重量级锁:竞争激烈的情况下使用重量级锁.

偏向锁和轻量级锁之所以会在性能上比重量级锁是因为好,本质上是因为偏向锁和轻量级锁仅仅使用了CAS.

synchronized的三种使用方式

synchronized作为线程方法体中的同步代码块

每个对象都有一个互斥锁标记,用来分配给线程的

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块

线程退出同步代码块,会释放相应的互斥锁标记

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

synchronized (临界资源对象){

//代码:原子性操作

}

}

});

synchronized作为线程方法

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块

线程退出同步方法时,会释放相应的互斥锁标记

Thread thread = new Thread(new Runnable() {

@Override

public synchronized void run() {

}

});

synchronized作为静态静态同步方法与synchronized(class)代码块

synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁.

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

synchronized (this.getClass()){

}

}

});

同步规则

只有在包含同步代码块的方法,或者同步方法时,才需要对象的锁标记

不包含同步代码块的方法,或普通方法,则不需要锁标记,可以直接调用

同步不具有继承性

如果父类有一个带synchronized关键字的方法,子类继承并重写了这个方法.但是同步不能继承,所以还是需要在子类方法中添加synchronized关键字.

synchronized锁重入

“可重入锁”概念是:自己可以再次获取自己的内部锁.比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁.

public class Test{

public static Object obj = new Object();

public static void main(String[] args) throws InterruptedException {

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

synchronized (obj){

synchronized (obj){

System.out.println("跟我要了两把锁");

}

}

}

});

thread.start();

thread.join();

}

}

线程安全死锁问题

当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,等待A对象锁标记,产生死锁.如下例子

public class Test{

public static Object obja = new Object();

public static Object objb = new Object();

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (obja){

System.out.println(Thread.currentThread().getName()+"进入A门");

synchronized (objb){

System.out.println(Thread.currentThread().getName()+"进入B门");

}

}

}

});

Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

synchronized (objb){

System.out.println(Thread.currentThread().getName()+"进入B门");

synchronized (obja){

System.out.println(Thread.currentThread().getName()+"进入A门");

}

}

}

});

thread1.start();

thread2.start();

thread1.join();

thread2.join();

}

}

总结

无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象,如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁.

每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码.

实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制.

不要使用String类型作为锁,因为数据类型String的常量池属性,所以synchronized(string)在使用时某些情况下会出现一些问题,两个线程可能会持有相同的锁,导致某一时刻只有一个线程能运行.所以尽量不要使用synchronized(string)而使用synchronized(object).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值