转载:Synchronized和 lock区别

1、两者所处层面不同
synchronized是Java中的一个关键字,当我们调用它时会从在虚拟机指令层面加锁,关键字为monitorenter和monitorexit
Lock是Java中的一个接口,它有许多的实现类来为它提供各种功能,加锁的关键代码为大体为Lock和unLock
2、获锁方式
synchronized可对实例方法、静态方法和代码块加锁,相对应的,加锁前需要获得实例对象的锁或类对象的锁或指定对象的锁。说到底就是要先获得对象的监视器(即对象的锁)然后才能够进行相关操作。
Lock的使用离不开它的实现类AQS,而它的加锁并不是针对对象的,而是针对当前线程的,并且AQS中有一个原子类state来进行加锁次数的计数
3、获锁失败
使用关键字synchronized加锁的程序中,获锁失败的对象会被加入到一个虚拟的等待队列中被阻塞,直到锁被释放;1.6以后加入了自旋操作
使用Lock加锁的程序中,获锁失败的线程会被自动加入到AQS的等待队列中进行自旋,自旋的同时再尝试去获取锁,等到自旋到一定次数并且获锁操作未成功,线程就会被阻塞
4、偏向或重入
synchronized中叫做偏向锁
当线程访问同步块时,会使用 CAS 将线程 ID 更新到锁对象的 Mark Word 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
Lock中叫做重入锁
AQS的实现类ReentrantLock实现了重入的机制,即若线程a已经获得了锁,a再次请求锁时则会判断a是否持正有锁,然后会将原子值state+1来实现重入的计数操作
5、Lock独有的队列
condition队列是AQS中的一个Lock的子接口的内部现类,它一般会和ReentrantLock一起使用来满足除了加锁和解锁以外的一些附加条件,比如对线程的分组和临界数量的判断(阻塞队列)
6、解锁操作
synchronized:不能指定解锁操作,执行完代码块的对象会自动释放锁
Lock:可调用ulock方法去释放锁比synchronized更灵活

来看下面具体例子:

public class DeadLockTest implements Runnable{

	//final ReentrantLock lock = new ReentrantLock();
	
	static String source1 = "A";
	static String source2 = "B";
	
	public void run() {
		if(Thread.currentThread().getName().equals("1")) {
			synchronized(source1) {
			//lock.lock();
				System.out.println("线程1获取资源1锁");
				try {
					Thread.currentThread().sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//lock.lock();
				synchronized(source2) {
					System.out.println("线程1获取资源2锁");
					try {
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
				}
				}
			}
					//lock.unlock();
				//lock.unlock();
		}else {
			synchronized(source2) {
			//lock.lock();
				System.out.println("线程2获取资源2锁");
				try {
					Thread.currentThread().sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized(source1) {
				//lock.lock();
					System.out.println("线程2获取资源1锁");
					try {
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				//lock.unlock();
				//lock.unlock();
				}
				}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		DeadLockTest run = new DeadLockTest();
		new Thread(run,"1").start();
		new Thread(run,"2").start();
	}
}

上述代码使用JVM关键字synchronized简单的实现了一个死锁的效果

我们先来分析一下使用synchronized关键字得出的结果:
结果:

线程1获取资源1锁
线程2获取资源2

分析:
我们由两个资源source1和source2、两个线程1和2,这两个线程都想获取资源source1和source2
首先,最好先将两个资源source1和source2设置为static属性,让两个线程能够共享它
source1和source2都是String类型的,因为synchronized关键字修饰的应该是一个对象,普通的变量(int、float等)不能获取对象锁
接下来,两个资源
1、线程1使用synchronized来对资源source1加锁(即资源source1尝试获取对象锁),成功后获取资源source1,接下来调用sleep方法阻塞,但不释放对象锁
2、线程2使用synchronized来对资源source2加锁,成功后获取资源source2,接下来调用sleep方法阻塞,但不释放对象锁
3、线程1醒来,再次使用synchronized来对资源source2加锁,因为资源source2已被线程2所占用,资源source2还没有释放自己的锁,所以线程1阻塞,等待资源source2释放锁
4、线程2醒来,再次使用synchronized来对资源source1加锁,因为资源source1已被1所占用,资源source1还没有释放自己的锁,所以线程2阻塞,等待资源source1释放锁
5、线程1和线程2都等待着资源source1和资源source2释放锁,导致死锁

接下来,我们将synchronized关键字注释掉,注释掉的ReentrantLock锁对象,然后打开lock和unlock的注释,运行结果如下:

线程2获取资源2锁
线程2获取资源1锁
线程1获取资源1锁
线程1获取资源2

1、线程2尝试获取ReentrantLock对象lock,成功后加锁,并使用资源2,然后睡眠,阻塞自己
2、线程1尝试获锁去lock,因为线程2正持有此对象并且还未释放锁,所以线程1获锁失败,并被加入到等待队列中然后阻塞
3、线程2醒来,再次尝试获锁,因为ReentrantLock可重入,所以再次获锁成功,于是线程2使用资源1,然后两次释放锁。释放完毕后,会唤醒在等待队列中等待的线程1
4、线程1尝试获锁,因为ReentrantLock对象空着,所以获锁成功,然后执行流程如上述第三步
那么同样的代码使用ReentrantLock为什么不会产生死锁呢?
第一、 因为synchronized关键字是JVM为我们提供的属性,sleep方法也是一个native方法,两者确实应该配套使用。而ReentrantLock锁是Java类库中提供的一个类,使用Java代码来模拟锁,synchronized和ReentrantLock在层次级别上不同
第二、 两者实现原理稍有不同。synchronized修饰的必定是一个对象,并且每一个对象都有着属于自己的锁记录指针,我们每次加锁或释放锁都是修改此指针的指向
而ReentrantLock的内部类实现了AQS部分方法,实现了对线程操作的功能。每次加锁失败会将当前线程放入等待队列中,并阻塞它;否则,当前尝试获取资源的线程可以无阻的执行
即synchronized操作的是对象,ReentrantLock操作的是线程

ReentrantLock产生死锁的过程:

public class IntLock implements Runnable{
	private boolean flag;
	private static ReentrantLock lock1=new ReentrantLock();
	private static ReentrantLock lock2=new ReentrantLock();

	public IntLock(boolean flag) {
	    this.flag = flag;
	}

	@Override
	public void run() {
	    try {
	        if(flag){
	            lock1.lock();
	            System.out.println(flag + "线程获取了Lock1");
	            Thread.currentThread().sleep(100);
	            lock2.lock();
	            System.out.println(flag+"线程获取了Lock2");
	        }else{
	            lock2.lock();
	            System.out.println(flag + "线程获取了Lock2");
	            Thread.currentThread().sleep(100);
	            lock1.lock();
	            System.out.println(flag+"线程获取了Lock1");
	        }
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    } finally {
	        if(lock1.isHeldByCurrentThread()){
	            lock1.unlock();
	        }
	        if(lock2.isHeldByCurrentThread()){
	            lock2.unlock();
	        }
	    }
	}

	public static void main(String[] args) throws InterruptedException {
	    Thread thread1=new Thread(new IntLock(true));
	    Thread thread2=new Thread(new IntLock(false));
	    thread1.start();
	    thread2.start();
	    thread1.join();
	    thread2.join();
	    System.out.println("主线程已结束");
	}
}

以上代码创建了两个ReentrantLock对象,同时开启两个副线程去进行加锁,每个线程对应各自的ReentrantLock对象。当线程1持有了它的锁对象lock1,并且线程2持有了它的对象锁lock2后两个线程再次尝试获取对方的对象锁时,因为ReentrantLock为排他锁(即一把锁同时只能被一个线程锁持有),所以其它线程去获取当前线程的ReentrantLock时就会被放入此ReentrantLock的等待队列中被阻塞,这样就造成了死锁。

我们使用ReentrantLock时最好用static来修饰它的对象来确保此对象被所有线程共享

附上死锁产生原因和避免死锁方法:
以下资源来自网络:
死锁产生条件:
1、 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

2、 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

3、 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

4、 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。



两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

相同点:
Lock是一个接口,为了使用一个Lock对象,需要用到
Lock lock = new ReentrantLock();
与 synchronized (someObject) 类似的,lock()方法,表示当前线程占用lock对象,一旦占用,其他线程就不能占用了。
与 synchronized 不同的是,一旦synchronized 块结束,就会自动释放对someObject的占用。 lock却必须调用unlock方法进行手动释放,为了保证释放的执行,往往会把unlock() 放在finally中进行。
synchronized 是不占用到手不罢休的,会一直试图占用下去。
与 synchronized 的钻牛角尖不一样,Lock接口还提供了一个trylock方法。
trylock会在指定时间范围内试图占用。 如果时间到了,还占用不成功,就选择放弃。
注意: 因为使用trylock有可能成功,有可能失败,所以后面unlock释放锁的时候,需要判断是否占用成功了,如果没占用成功也unlock,就会抛出异常
使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法
Lock也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await, signal,signalAll 方法
注意: 不是Condition对象的wait,nofity,notifyAll方法,是await,signal,signalAll
不同点:

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
  2. Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
  3. synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。


Synchronized 用过吗,其原理是什么?synchronized 的作用?synchronized 和 lock 区别?synchronized是重量级锁还是轻量级锁?是重入锁吗?

Synchronized 用过吗,其原理是什么

Synchronized 是 Java 中用于实现同步的关键字,它可以用于方法和代码块。Synchronized 的原理是,它会使用对象的内置锁(也称为监视器锁)来实现同步,每个对象都有一把内置锁,当一个线程访问一个同步代码块时,它会尝试获取这个锁,如果锁被其他线程持有,则该线程将被阻塞,直到锁被释放。

Synchronized 有以下几个特点:

  1. 互斥性:Synchronized 保证同一时刻只有一个线程可以获取锁,并且只有该线程可以执行同步代码块中的代码。
  2. 可重入性:同一个线程可以多次获取同步锁而不会被阻塞,这样可以避免死锁的发生。
  3. 独占性: 如果一个线程获得了对象的锁,则其他线程必须等待该线程释放锁之后才能获取锁。

4.缺点:非公平锁 ,当锁被释放后,任何一个线程都有机会竞争得到锁,这样做的目的是提高效率,但缺点是可能产生线程饥饿现象。

除了使用 Synchronized 关键字之外,Java 还提供了一些其他的同步机制,例如 ReentrantLock、Semaphore、CountDownLatch 等。在选择同步机制时,需要根据具体的应用场景和需求来选择合适的同步方式。

synchronized 的作用?

在Java中,synchronized 关键字是用来控制线程同步的,

在多线程的环境下,控制synchronized 代码段不被多个线程同时执行

synchronized 既可以加在一段代码上,也可以加在方法上

synchronized 和 lock 区别?

类别synchronizedlock
存在层次Java 关键字,JVM层面是一个类
锁的释放JVM将确保锁会获得自动释放lock必须在finally块中释放
锁状态无法判断可以判断
锁性能可重入,不可中断、非公平可重入、可判断、可公平(两者皆可默认非公平)
性能少量同步大量同步

synchronized是重量级锁还是轻量级锁?

synchronized重量级锁(Heavyweight Lock)。

在Java中,锁有不同的实现方式,包括重量级锁、轻量级锁和偏向锁。synchronized在多线程同步中通常使用的是重量级锁。

重量级锁的实现涉及到操作系统层面的线程调度和内核态与用户态之间的切换,因此它的性能开销相对较大。在多线程竞争激烈的情况下,重量级锁可能导致性能下降,因为线程需要频繁地切换到内核态进行锁的申请和释放。

轻量级锁和偏向锁是Java虚拟机为了提高synchronized性能而引入的优化机制。这些锁会根据竞争情况自动升级或降级为重量级锁。

总之,synchronized 通常是重量级锁,它确保了多线程访问的互斥性,但在高并发情况下,性能开销较大。 Java虚拟机会根据实际情况尝试使用轻量级锁和偏向锁来提高性能。

synchronized是重入锁吗?

是的,synchronized 是一个重入锁(Reentrant Lock)。

重入锁是指允许同一个线程多次获得同一个锁,而不会因为重复获取锁而被阻塞。这种机制被称为锁的重入性

在Java中,synchronized 具有重入性,这意味着如果一个线程已经获得了某个对象上的锁,那么它可以多次进入被锁定的代码块,而不会被阻塞。这是通过在每个锁对象上维护一个锁计数器来实现的。每次线程获取锁时,计数器加一,释放锁时,计数器减一。只有当计数器归零时,锁才会完全释放,其他线程才能获取该锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值