Java 锁,
Java 锁,
http://blog.csdn.net/hongchangfirst/article/details/26004335
http://www.javaweb1024.com/java/JavaWebzhongji/2015/09/06/847.html
https://blog.csdn.net/u012545728/article/details/80843595
目录
1、关于乐观锁和悲观锁:
2、关于锁的开销:
3、悲观锁的读写:
4、乐观锁的读写:
5、乐观锁的写修改:
6、不可重入锁:
7、可重入锁:
8、自旋锁:
9、排他锁(ReentrantLock)
9.1、普通ReentrantLock
9.2、带条件的ReenTrantLock
10、synchronized
10.1、同步方法
10.2、代码块对象同步
11、ReentrantReadWriteLock
1、关于乐观锁和悲观锁:
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。java中的乐观锁基本都是通过CAS操作实现的,CAS 是一种更新的原子操作,CAS算法 即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
但是不管用什么锁,都必须保证线程安全,不然锁的意义就不存在了。
2、关于锁的开销:
这里的 CountPojo 是多线程操作的对象,为了验证结果
PessimisticTest 为悲观锁读写实现类,读和写都会对操作加锁
OptimisticTest 为乐观锁,读直接不加锁和验证,写的话加了个版本验证(但是这个版本验证不能实现线程安全)
public class CountPojo {
private int count = 0;
private String uuid = "";
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
import java.util.UUID;
public class PessimisticTest {
public CountPojo countPojo = null;
public PessimisticTest(CountPojo countPojo) {
this.countPojo = countPojo;
}
public int getValue() {
synchronized (countPojo) {
return countPojo.getCount();
}
}
public void updateValue(int addValue) {
synchronized (countPojo) {
countPojo.setCount(countPojo.getCount() + addValue);
countPojo.setUuid(UUID.randomUUID().toString());
}
}
}
import java.util.UUID;
public class OptimisticTest {
public CountPojo countPojo = null;
public OptimisticTest(CountPojo countPojo) {
this.countPojo = countPojo;
}
public int getValue() {
return countPojo.getCount();
}
public void updateValue(int addValue) {
String uuid = countPojo.getUuid();
int currentInt = countPojo.getCount();
int operInt = currentInt + addValue;
if (uuid.equals(countPojo.getUuid())) {
countPojo.setCount(operInt);
countPojo.setUuid(UUID.randomUUID().toString());
} else {
updateValue(addValue);
}
}
}
public class ThreadMainTest {
public static void main(String[] args) throws InterruptedException {
CountPojo countPojo1 = new CountPojo();
PessimisticTest pessimisticTest = new PessimisticTest(countPojo1);
long start1 = System.currentTimeMillis();
for (int i = 0; i < 900000000; i ++) {
pessimisticTest.getValue();
}
long count1 = System.currentTimeMillis() - start1;
System.out.println("PessimisticTest Use : " + count1 + " ms");
CountPojo countPojo2 = new CountPojo();
OptimisticTest optimisticTest = new OptimisticTest(countPojo2);
long start2 = System.currentTimeMillis();
for (int i = 0; i < 900000000; i ++) {
optimisticTest.getValue();
}
long count2 = System.currentTimeMillis() - start2;
System.out.println("OptimisticTest Use : " + count2 + " ms");
}
}
显示结果:
PessimisticTest Use : 17473 ms
OptimisticTest Use : 3 ms
同样是取值,很明显锁的开销是非常大的、
3、悲观锁的读写:
CallableLockTest 为了获取线程内部执行时间
MainTest 测试类做了修改
import java.util.concurrent.Callable;
public class CallableLockTest implements Callable<Integer> {
private PessimisticTest lockTest = null;
public CallableLockTest(PessimisticTest optimisticTest) {
this.lockTest = optimisticTest;
}
@Override
public Integer call() throws Exception {
Long start = System.currentTimeMillis();
int i = 0;
while (i ++ < 100) {
this.lockTest.updateValue(1);
}
Long count = System.currentTimeMillis() - start;
return count.intValue();
}
}
package com.busy.lock.test;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadMainTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CountPojo countPojo1 = new CountPojo();
countPojo1.setUuid(UUID.randomUUID().toString());
PessimisticTest pessimisticTest = new PessimisticTest(countPojo1);
ExecutorService pool = Executors.newFixedThreadPool(4);
CallableLockTest callableLockTest = new CallableLockTest(pessimisticTest);
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
for (int i = 0; i < 10; i ++) {
futureList.add(pool.submit(callableLockTest));
}
int count1 = 0;
for (Future<Integer> future : futureList) {
count1 += future.get();
}
System.out.println("PessimisticTest Use : " + count1 + " ms");
System.out.println("PessimisticTest Count : " + countPojo1.getCount());
}
}
执行结果:
PessimisticTest Use : 31 ms
PessimisticTest Count : 1000
说明是线程安全的。
4、乐观锁的读写:
修改一下 CallableLockTest:
import java.util.concurrent.Callable;
public class CallableLockTest implements Callable<Integer> {
private OptimisticTest lockTest = null;
public CallableLockTest(OptimisticTest optimisticTest) {
this.lockTest = optimisticTest;
}
@Override
public Integer call() throws Exception {
Long start = System.currentTimeMillis();
int i = 0;
while (i ++ < 100) {
this.lockTest.updateValue(1);
}
Long count = System.currentTimeMillis() - start;
return count.intValue();
}
}
测试类:
public class ThreadMainTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CountPojo countPojo1 = new CountPojo();
countPojo1.setUuid(UUID.randomUUID().toString());
OptimisticTest pessimisticTest = new OptimisticTest(countPojo1);
ExecutorService pool = Executors.newFixedThreadPool(4);
CallableLockTest callableLockTest = new CallableLockTest(pessimisticTest);
List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();
for (int i = 0; i < 10; i ++) {
futureList.add(pool.submit(callableLockTest));
}
int count1 = 0;
for (Future<Integer> future : futureList) {
count1 += future.get();
}
System.out.println("PessimisticTest Use : " + count1 + " ms");
System.out.println("PessimisticTest Count : " + countPojo1.getCount());
}
}
执行结果:
PessimisticTest Use : 22 ms
PessimisticTest Count : 999
结果说明这种乐观锁的写验证方式是不支持线程安全的,下面做出修改:
5、乐观锁的写修改:
将计数字段类型变为线程安全的:AtomicInteger
CountPojo 修改为:
import java.util.concurrent.atomic.AtomicInteger;
public class CountPojo {
private AtomicInteger count = new AtomicInteger(0);
private String uuid = "";
public AtomicInteger getCount() {
return count;
}
public void setCount(AtomicInteger count) {
this.count = count;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
OptimisticTest 修改为:
import java.util.UUID;
public class OptimisticTest {
public CountPojo countPojo = null;
public OptimisticTest(CountPojo countPojo) {
this.countPojo = countPojo;
}
public int getValue() {
return countPojo.getCount().get();
}
public void updateValue(int addValue) {
countPojo.getCount().addAndGet(addValue);
countPojo.setUuid(UUID.randomUUID().toString());
}
}
执行结果:
PessimisticTest Use : 30 ms
PessimisticTest Count : 1000
这样说明线程安全了
6、不可重入锁:
即若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。
public class UnReentrantLock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
测试类:
public class Count {
UnReentrantLock lock = new UnReentrantLock();
public void print() throws InterruptedException {
lock.lock();
doAdd();
lock.unlock();
}
public void doAdd() throws InterruptedException {
lock.lock();
System.out.println("第二次获得锁");
lock.unlock();
}
}
public class MainTest {
public static void main(String[] args) throws InterruptedException {
Count count = new Count();
count.print();
}
}
结果是发生线程阻塞
7、可重入锁:
意味着线程可以进入它已经拥有的锁的同步代码块
public class ReentrantLock {
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock() throws InterruptedException {
Thread thread = Thread.currentThread();
while (isLocked && lockedBy != thread) {
wait();
}
isLocked = true;
lockedCount++;
lockedBy = thread;
}
public synchronized void unlock() {
if (Thread.currentThread() == this.lockedBy) {
lockedCount--;
if (lockedCount == 0) {
isLocked = false;
notify();
}
}
}
}
修改6中的测试类:
public class Count {
ReentrantLock lock = new ReentrantLock();
public void print() throws InterruptedException {
lock.lock();
doAdd();
lock.unlock();
}
public void doAdd() throws InterruptedException {
lock.lock();
System.out.println("第二次获得锁");
lock.unlock();
}
}
结果不会发生阻塞
8、自旋锁:
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
private AtomicReference<Thread> cas = new AtomicReference<Thread>();
public void lock() {
Thread current = Thread.currentThread();
// 利用CAS
while (!cas.compareAndSet(null, current)) {
System.out.println(Thread.currentThread().getName()+"[线程等待获取锁]");
}
System.out.println(Thread.currentThread().getName()+"[线程获取到锁]");
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
其中 cas.compareAndSet(null, current) 使用的是Unsafe的方法,如果cas与null(期待值)相等,则把current(更新值)付给cas,否则什么都不做
9、排他锁(ReentrantLock)
ReentrantLock是一个排他锁,同一时间只允许一个线程访问
9.1、普通ReentrantLock
package com.busymonkey.threadfunc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest implements Runnable{
private int tickets = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (tickets > 0) {
try {
lock.lock();// 加锁
//lock.tryLock();
//lock.tryLock(time, unit);
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} finally {
lock.unlock();// 释放锁
}
}
}
}
package com.busymonkey.threadfunc;
public class SellTicket {
public static void main(String[] args) {
ReentrantLockTest str = new ReentrantLockTest();
Thread t1 = new Thread(str,"窗口1");
Thread t2 = new Thread(str,"窗口2");
Thread t3 = new Thread(str,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
9.2、带条件的ReenTrantLock
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockMain {
static class NumberWrapper {
public int value = 1;
}
public static void main(String[] args) {
//初始化可重入锁
final Lock lock = new ReentrantLock();
//第一个条件当屏幕上输出到3
final Condition reachThreeCondition = lock.newCondition();
//第二个条件当屏幕上输出到6
final Condition reachSixCondition = lock.newCondition();
//NumberWrapper只是为了封装一个数字,一边可以将数字对象共享,并可以设置为final
final NumberWrapper num = new NumberWrapper();
//初始化A线程
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
//需要先获得锁
lock.lock();
try {
//A线程先输出前3个数
while (num.value <= 3) {
System.out.println("threadA start write"+num.value);
num.value++;
}
//输出到3时要signal,告诉B线程可以开始了
reachThreeCondition.signal();
} finally {
lock.unlock();
}
lock.lock();
try {
//等待输出6的条件
reachSixCondition.await();
//输出剩余数字
while (num.value <= 9) {
System.out.println("threadA start write"+num.value);
num.value++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
while (num.value <= 3) {
//等待3输出完毕的信号
reachThreeCondition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
try {
lock.lock();
//已经收到信号,开始输出4,5,6
while (num.value <= 6) {
System.out.println("threadB start write"+num.value);
num.value++;
}
//4,5,6输出完毕,告诉A线程6输出完了
reachSixCondition.signal();
} finally {
lock.unlock();
}
}
});
//启动两个线程
threadB.start();
threadA.start();
}
}
总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:
1、Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍
但是ReetrantLock的性能能维持常态;
10、synchronized
synchronized 是一个可重入锁
重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
package com.busy.lock.test;
public class ThreadMainTest {
public static void main(String[] args) {
SynchronizeThread str = new SynchronizeThread();
Thread t1 = new Thread(str,"窗口1");
Thread t2 = new Thread(str,"窗口2");
Thread t3 = new Thread(str,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
10.1、同步方法
package com.busy.lock.test;
public class SynchronizeThread implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
try {
doMethod();
} finally {
}
}
}
private synchronized void doMethod() {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
10.2、代码块对象同步
package com.busy.lock.test;
public class SynchronizeThread implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
try {
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
} finally {
}
}
}
}
11、ReentrantReadWriteLock
https://www.cnblogs.com/zaizhoumo/p/7782941.html
ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
读写锁内部维护了两个锁,一个用于读操作,一个用于写操作。所有 ReadWriteLock实现都必须保证 writeLock操作的内存同步效果也要保持与相关 readLock的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
ReentrantReadWriteLock支持以下功能:
- 支持公平和非公平的获取锁的方式
- 支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁
- 还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的
- 读取锁和写入锁都支持锁获取期间的中断
- Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException
2、关于锁的内容:
对象锁(上面对map进行加锁就是对象锁):
Java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。
类锁:
对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。我们都知道,java类可能会有很多个对象,但是只有1个Class对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是MyClass.class的方式。
常量池锁(比如String的intern()方法):
使用String.inter()是这种思路的一种具体实现。类 String 维护一个字符串池。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。可见,当String相同时,String.intern()总是返回同一个对象,因此就实现了对同一用户加锁。由于锁的粒度局限于具体用户,使系统获得了最大程度的并发。如下:
package Test;
public class Test {
public static void main(String[] args) throws InterruptedException {
CustomThread uploadDetect1 = new CustomThread();
CustomThread uploadDetect2 = new CustomThread();
CustomThread uploadDetect3 = new CustomThread();
CustomThread uploadDetect4 = new CustomThread();
CustomThread uploadDetect5 = new CustomThread();
Thread t1 = new Thread(uploadDetect1);
Thread t2 = new Thread(uploadDetect2);
Thread t3 = new Thread(uploadDetect3);
Thread t4 = new Thread(uploadDetect4);
Thread t5 = new Thread(uploadDetect5);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class Stu1 {
static int count = 100;
public void func(String userId) {
synchronized ((userId).intern()) {
count --;
int out = count;
System.out.println("Count : "+out);
}
}
}
class CustomThread implements Runnable {
Stu1 stu1 = new Stu1();
public void run() {
while(Stu1.count > 0) {
stu1.func("user1");
try {
Thread.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3、关于String的intern()方法的缺陷:类 String 维护一个字符串池是放在JVM perm区的,如果用户数特别多,导致放入字符串池的String不可控,有可能导致OOM错误或者过多的Full GC。怎么样能控制锁的个数,同时减小粒度锁呢?直接使用Java ConcurrentHashMap?或者你想加入自己更精细的控制?那么可以借鉴ConcurrentHashMap的方式,将需要加锁的对象分为多个bucket,每个bucket加一个锁。伪代码如下:
Map locks = new Map();
List lockKeys = new List();
for(int number : 1 - 10000) {
Object lockKey = new Object();
lockKeys.add(lockKey);
locks.put(lockKey, new Object());
}
public void doSomeThing(String uid) {
Object lockKey = lockKeys.get(uid.hash() % lockKeys.size());
Object lock = locks.get(lockKey);
synchronized(lock) {
// do something
}
}
相关文章
- 暂无相关文章
用户点评