Java中的锁
1.公平锁和非公平锁
public class test02 {
public static void main(String[] args) {
lock lock = new ReentrantLock();
}
}
源码:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
当设置为true时:
public static void main(String[] args) {
lock lock = new ReentrantLock(true);
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁
是指多个线程按照申请锁的顺序来获取锁类似排队打饭 先来后到
非公平锁
是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象
公平锁/非公平锁
并发包ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁 默认是非公平锁
Java ReentrantLock而言, 通过构造哈数指定该锁是否是公平锁 默认是非公平锁 非公平锁的优点在于吞吐量必公平锁大.
对于synchronized而言 也是一种非公平锁.
2.可重入锁(递归锁)
可重入锁(也叫做递归锁)
指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
ReentrantLock/synchronized就是一个典型的可重入锁
可重入锁最大的作用就是避免死锁
参考一:
package cn.atguigu.interview.study.thread;
class Phone{
public synchronized void sendSms() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendSms");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendEmail");
}
}
/**
* Description:
* 可重入锁(也叫做递归锁)
* 指的是同一先生外层函数获得锁后,内层敌对函数任然能获取该锁的代码
* 在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
*
* 也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
*
* @author veliger@163.com
* @date 2019-04-12 23:36
**/
public class ReenterLockDemo {
/**
* t1 sendSms
* t1 sendEmail
* t2 sendSms
* t2 sendEmail
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
参考二:
package cn.atguigu.interview.study.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tget");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tset");
} finally {
lock.unlock();
}
}
}
/**
* Description:
* 可重入锁(也叫做递归锁)
* 指的是同一先生外层函数获得锁后,内层敌对函数任然能获取该锁的代码
* 在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
*
* 也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
*
* @author veliger@163.com
* @date 2019-04-12 23:36
**/
public class ReenterLockDemo {
/**
* Thread-0 get
* Thread-0 set
* Thread-1 get
* Thread-1 set
*
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
Thread t3 = new Thread(phone);
Thread t4 = new Thread(phone);
t3.start();
t4.start();
}
}
3.自旋锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺
点是循环会消耗CPU
自旋锁代码验证:
package test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
public class SpinLockDemo {
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in 哈哈!!");
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t invoked myUnlock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
spinLockDemo.myUnlock();
},"AA").start();
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
new Thread(() -> {
spinLockDemo.myLock();
spinLockDemo.myUnlock();
},"BB").start();
}
}
4.读写锁
独占锁:指该锁一次只能被―个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可被多个线程所持有。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
package test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCaChe{
private volatile Map<String,Object> map = new HashMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,Object value){
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key);
try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"\t 写入完成:"+key);
}catch (Exception e){
e.printStackTrace();
}finally {
rwLock.writeLock().unlock();
}
}
public void get(String key){
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"\t 正在读取:");
try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
}catch (Exception e){
e.printStackTrace();
}finally {
rwLock.readLock().unlock();
}
}
public void clearMap(){
map.clear();
}
}
/**
* Description:
* 多个线程同时操作 一个资源类没有任何问题 所以为了满足并发量
* 读取共享资源应该可以同时进行
* 但是
* 如果有一个线程想去写共享资源来 就不应该有其他线程可以对资源进行读或写
*
* 小总结:
* 读 读能共存
* 读 写不能共存
* 写 写不能共存
* 写操作 原子+独占 整个过程必须是一个完成的统一整体 中间不允许被分割 被打断
**/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCaChe myCaChe = new MyCaChe();
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCaChe.put(temp + "", temp);
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
myCaChe.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
运行效果:
1 正在写入:1
1 写入完成:1
2 正在写入:2
2 写入完成:2
3 正在写入:3
3 写入完成:3
4 正在写入:4
4 写入完成:4
5 正在写入:5
5 写入完成:5
3 正在读取:
1 正在读取:
2 正在读取:
4 正在读取:
5 正在读取:
3 读取完成:3
5 读取完成:5
2 读取完成:2
4 读取完成:4
1 读取完成:1
5.CountDownLatch
让一些线程阻塞直到另外一些完成后才被唤醒,CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞.其他线程调用countDown方法计数器减1(调用countDown方法时线程不会阻塞),当计数器的值变为0,因调用await方法被阻塞的线程会被唤醒,继续执行。
Demo01
package test;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t上晚自习,离开教室!!");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t********班长最后关门走人!!!");
}
}
Demo02
CountDownLatchDemo
package test;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t,国,灭亡");
countDownLatch.countDown();
},CountryEnum.forEach(i).getName()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t********秦国一统华夏!!");
}
}
CountryEnum
package test;
/**
* Description
* 枚举的使用
**/
public enum CountryEnum {
/**
*
*/
ONE(1, "齐"),
/**
*
*/
TWO(2, "楚"),
/**
*
*/
THREE(3, "燕"),
/**
*
*/
FOUR(4, "赵"),
/**
*
*/
FIVE(5, "魏"),
/**
*
*/
SIX(6, "韩");
CountryEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
private Integer code;
private String name;
public Integer getCode() {
return code;
}
public String getName() {
return name;
}
public void setCode(Integer code) {
this.code = code;
}
public void setName(String name) {
this.name = name;
}
public static CountryEnum forEach(int index) {
CountryEnum[] countryEnums = CountryEnum.values();
for (CountryEnum countryEnum : countryEnums) {
if (index == countryEnum.getCode()) {
return countryEnum;
}
}
return null;
}
}
6.CyclicBarrier
CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法.
集齐7颗龙珠就能召唤神龙
package test;
import org.w3c.dom.ls.LSOutput;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
System.out.println(Thread.currentThread().getName()+"****召唤神龙!!");
});
for (int i = 0; i <= 7; i++) {
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t收集到"+tempInt+"龙族!");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
7.Semaphore
信号量的主要用户两个目的,一个是用于多喝共享资源的相互排斥使用,另一个用于并发资源数的控制。
抢车位
package test;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); //模拟三个停车位
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t抢到车位");
try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t停车三秒后离开停车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}