JAVA并发包——锁

1.java多线程中,可以使用synchronized关键字来实现线程间的同步互斥工作,其实还有个更优秀的机制来完成这个同步互斥的工作——Lock对象,主要有2种锁:重入锁和读写锁,它们比synchronized具有更强大的功能,并且有嗅探锁定、多路分支等功能。

2.ReentrantLock(重入锁)

重入锁,在需要进行同步的代码部分加上锁定,但不要忘记最后一定要释放锁定,否则会造成锁永远无法释放,其他线程永远进不来的结果(使用起来跟synchronized很像,并且,在jdk1.8之前,ReentrantLock比synchronized性能好,jdk1.8对synchronized做了优化,性能接近了。但是ReentrantLock比synchronized灵活)

代码示例:

package lock020;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseReentrantLock {
	
	private Lock lock = new ReentrantLock();
	
	public void method1(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1..");
			Thread.sleep(1000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1..");
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			
			lock.unlock();
		}
	}

	public static void main(String[] args) {

		final UseReentrantLock ur = new UseReentrantLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				ur.method1();
			}
		}, "t1");

		t1.start();

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				ur.method1();
			}
		}, "t2");

		t2.start();

		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//System.out.println(ur.lock.getQueueLength());
	}
	
	
}

执行以后,可以发现,t1和t2是串行执行method1的

3.ReentrantLock锁的等待与通知

synchronized关键字里,有Object的wait()方法和notify()/notifyAll()方法进行多线程之间的工作协调。而同样的,Lock也有自己的等待/通知类,它就是Condition。这个Condition一定是针对某一把具体的锁的,就是说,只有有锁的基础之才会产生Condition

代码实现:

package lock020;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseCondition {

	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void method1(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁..");
			condition.await();	// Object wait,释放锁
			System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void method2(){
		try {
			lock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒..");
			condition.signal();		//Object notify
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		
		final UseCondition uc = new UseCondition();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.method1();
			}
		}, "t1");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				uc.method2();
			}
		}, "t2");

		t1.start();
		t2.start();
	}
	
	
	
}

以上代码,实现了wait/nofity的功能

ps:不同的线程之间,可以用不同的Condition对象来进行通信。例如t1唤醒t2用 Condition1,t3唤醒t4用Condition2,这也是比wait/notify灵活的地方

3.公平锁和非公平锁

扫描二维码关注公众号,回复: 1061029 查看本文章

公平锁的作用就是严格按照线程启动的顺序来执行的,不允许其他线程插队执行的;而非公平锁是允许插队的。(默认是非公平锁)

可以通过构造方法指定参数是ture(公平)或者false(非公平),一般说来,非公平锁比公平锁性能要好,因为公平锁要维护顺序

4.ReentrantLock锁与synchronized的区别

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步

5.ReenTrantReadWriteLock(读写锁)

核心其实就是实现读写分离。在高并发的情况下,尤其是读多写少的情况下,性能远高于重入锁

之前的ReentrantLock和synchronized的使用时,同一时间内,只能一个线程访问被锁定的代码。读写锁不同,其本质是2个锁,即读锁和写锁。在读锁下,多个线程可以并发访问,但是在写锁下,只能串行访问

口诀:读读共享,写写互斥,读写互斥

代码示例:

package lock021;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class UseReentrantReadWriteLock {

	private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
	private ReadLock readLock = rwLock.readLock();
	private WriteLock writeLock = rwLock.writeLock();
	
	public void read(){
		try {
			readLock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();
		}
	}
	
	public void write(){
		try {
			writeLock.lock();
			System.out.println("当前线程:" + Thread.currentThread().getName() + "进入...");
			Thread.sleep(3000);
			System.out.println("当前线程:" + Thread.currentThread().getName() + "退出...");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			writeLock.unlock();
		}
	}
	
	public static void main(String[] args) {
		
		final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
		
		Thread tr1 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.read();
			}
		}, "t1");
		Thread tr2 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.read();
			}
		}, "t2");
		Thread tw3 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.write();
			}
		}, "t3");
		Thread tw4 = new Thread(new Runnable() {
			@Override
			public void run() {
				urrw.write();
			}
		}, "t4");		
		
		tr1.start();
		tr2.start();

		tw3.start();
		tw4.start();

	}
}

模拟代码可发现:

如果是都是读操作,基本是同时进行的

如果有写操作,是要锁定的

6.锁的优化

(1)避免死锁

(2)减小锁的持有时间

(3)减小锁的粒度

(4)锁的分离

(5)尽量使用无锁的操作,如原子操作(Atomic类系列)、volatile关键字

7.分布式锁的概念

2台机器都部署了项目,显然,运行的jvm是不同的,如果t1线程在机器A,t2线程在机器B,同时要访问同一段锁定的代码块

显然,jvm的锁机制是无法处理这种情况的,这时候要考虑第三方帮助实现,如zookkeeper

猜你喜欢

转载自blog.csdn.net/mweibiao/article/details/80383269