针对数字的类型的原子类
package com.njupt.charapter05;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/25/14:31
* @Description:
*/
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(100);
//自增1
System.out.println(integer.getAndIncrement());
//增加固定的数,先增加后获取
System.out.println(integer.addAndGet(2));
//增加可变的数,先获取之后在加类似于i++
System.out.println(integer.getAndAdd(2));
//设置乘法和除法
System.out.println(integer.updateAndGet(value -> value * 3));
//根据原理自己写的upAndGet方法
upAndGet(integer,p->p*3);
System.out.println(integer.get());
int i = upAndGet(new AtomicInteger(5), p -> p * 2);
System.out.println(i);
}
public static int upAndGet(AtomicInteger integer, IntUnaryOperator operator){
int next;
while(true){
int pres=integer.get();
//int next=pres*3;
//把旧值传到方法接口里面,
next=operator.applyAsInt(pres);
if(integer.compareAndSet(pres,next)){
break;
}
}
return next;
}
}
AtomicReference针对引用类型JUC包下的原子引用类
package com.njupt.charapter05;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/25/15:03
* @Description:对于引用数据类型的保护,原子引用
*/
public class ReferenceTest {
public static void main(String[] args) {
Account.demo(new DecimalAccount(new BigDecimal("1000")));
}
}
class DecimalAccount implements Account{
//使用原子包装类来保证引用的对象的安全性
private AtomicReference<BigDecimal> balance;
//构造方法来传入余额
public DecimalAccount(BigDecimal balance){
this.balance=new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
//取款方法保证原子性,
@Override
public void withDraw(BigDecimal bigDecimal) {
//思路很重要
while(true){
BigDecimal prev=balance.get();
BigDecimal next=prev.subtract(bigDecimal);
//当前线程获取的值和共享变量的最新值是否一致。
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface Account{
BigDecimal getBalance();
void withDraw(BigDecimal bigDecimal);
static void demo(Account account){
List<Thread> ts=new ArrayList<>();
for(int i=0;i<1000;i++){
ts.add(new Thread(()->{
account.withDraw(BigDecimal.TEN);
}));
}
ts.forEach(Thread::start);
ts.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(account.getBalance());
}
}
AtomicStampedReference用原子引用来解决ABA问题
package com.njupt.charapter05;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/25/15:52
* @Description:使用这个AtomicStampedReference来解决ABA问题,除了比较值,还比较版本号
*/
@Slf4j(topic = "ABATest")
public class ABATest {
static AtomicStampedReference<String> ref=new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
log.debug("main线程");
String pre=ref.getReference();
int stamp=ref.getStamp();
log.debug("{}",stamp);
other();
//睡眠一秒后。开始执行变换
Thread.sleep(1000);
log.debug("{}",stamp);
log.debug("把A变成C",ref.compareAndSet(pre,"C",stamp,stamp+1));
}
public static void other(){
new Thread(()->{
int temp=ref.getStamp();
log.debug("{}",temp);
log.debug("把A改成B",ref.compareAndSet(ref.getReference(),"B",temp,temp+1));
}).start();
new Thread(()->{
int temp=ref.getStamp();
log.debug("{}",temp);
log.debug("把B改成A",ref.compareAndSet(ref.getReference(),"A",temp,temp+1));
}).start();
}
}
AtomicMarkableReference针对有某些引用类型,不需要根据更改了多少次来判断,只需要布尔标记判断是否变化就可以
package com.njupt.charapter05;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/25/16:25
* @Description:
*/
@Slf4j(topic = "c.ABATestBoolean")
public class ABATestBoolean {
public static void main(String[] args) throws InterruptedException {
GarbageBag bag = new GarbageBag("装满了垃圾");
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
log.debug("主线程开始");
GarbageBag pre = ref.getReference();
log.debug(pre.toString());
//其他的线程
new Thread(()->{
log.debug("线程开始处理垃圾,垃圾带开始不满" );
bag.setDesc("空垃圾带");
//并没有更改对象,知识把垃圾袋的属性更改一下,并且更改了状态
ref.compareAndSet(bag,bag,true,false);
}).start();
Thread.sleep(1000);
log.debug("换一只垃圾袋");
boolean b = ref.compareAndSet(pre, new GarbageBag("空垃圾袋"), true, false);
log.debug("换了吗?"+b);
log.debug(ref.getReference().toString());
}
}
class GarbageBag{
private String desc;
public GarbageBag(String desc){
this.desc=desc;
}
@Override
public String toString() {
return "GarbageBag{" +
"desc='" + desc + '\'' +
'}';
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
原子数组工具,来保护数组中的数据
//创建一个长度为10的数组。
AtomicIntegerArray array = new AtomicIntegerArray(10);
字段更新器,可以针对对象的某个域/属性Field进行原子的操作,只能配合volatile修饰的字段使用,并且修饰的属性不能是private否则会出现异常。
有三种字段更新器,针对于类中不同属性的,分别使用不同的字段更新器
AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(类名.class,"属性名");
AtomicLongFieldUpdater updater = AtomicLongFieldUpdater.newUpdater(类名.class, "属性名");
AtomicReferenceFieldUpdater updater1 = AtomicReferenceFieldUpdater.newUpdater(类名.class, 类型.class,"属性名");
package com.njupt.charapter05;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/25/17:37
* @Description:字段更新器,作用是可以对对象的属性进行保证读写操作的线程安全性
*/
public class Fieldtest {
public static void main(String[] args) {
Student student = new Student();
AtomicReferenceFieldUpdater fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
//对象名,原始参数和期待值
fieldUpdater.compareAndSet(student,null,"李四");
System.out.println(student);
}
}
class Student{
///属性要配合使用volatile关键字
volatile String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
原子累加器
思想是:每一个线程单独的一个计数单元,最后在进行汇总。
package com.njupt.charapter05;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/25/17:54
* @Description:原子累加器
*/
public class AtomicAddTest {
public static void main(String[] args) {
demo(()->new AtomicInteger(0)
,(adder)->adder.getAndIncrement()
);
demo(()->new LongAdder(),
adder->adder.increment());
}
public static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
ArrayList<Thread> ts = new ArrayList<>();
for(int i=0;i<4;i++){
ts.add(new Thread(()->{
for(int j=0;j<500000;j++){
action.accept(adder);
}
}));
}
long start = System.nanoTime();
ts.forEach(t -> t.start());
long end=System.nanoTime();
System.out.println("花费纳秒数"+(end-start));
}
}
Unsafe类
根据名字并不是指是一个线程不安全的类,而是指此类中涉及到对内存和线程的操作,如果擅自使用的话,可能会造成不安全。因为此类中有此对象属性,想要获取此对象需要用到反射机制来获取对象。
前提小知识:反射机制中getdeclaredfield和getfield的区别
- getField 只能获取public的,包括从父类继承来的字段。
- getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。
针对不可变的对象的线程安全性问题、
日期类
DateTimeFormatter在多线程下代替SimpleDateFormat
package com.njupt.charapter06;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/26/10:25
* @Description:针对日期的安全类,
*/
public class SafeDateTest {
public static void main(String[] args) {
//经典的SimpleDateFormat在多线程下具有线程安全问题
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
for(int i=0;i<10;i++){
new Thread(()->{
synchronized (format){
try {
Date parse = format.parse("2021-10-26");
System.out.println(parse);
} catch (ParseException e) {
e.printStackTrace();
}
}
}).start();
}
//多线程下的日期安全类
DateTimeFormatter formatter =DateTimeFormatter.ofPattern("yyyy-MM-dd");
for(int i=0;i<10;i++){
new Thread(()->{
TemporalAccessor parse = formatter.parse("2021-10-26");
System.out.println(parse);
}).start();
}
}
}
线程池问题
写一个自己的线程池
享元模式
在jdk中Boolean,Byte,Short,Integer,Long,Character等包装类都提供了valueOf方法,例如Long的valueOf会缓存-128-127之间的Long对象,在这个范围内会重用对象,大于这个范围,才会创建Long对象
注意Byte,Short,Long的存储范围都是-128-127
Character缓存的范围是0-127
Boolean缓存了TRUE和FALSE
Integer的默认范围是-128-127,最小值是不变的但是可以通过调整虚拟机的参数来改变最大值的范围,参数为-Djava.lang.Integer.IntegerCache.high
package com.njupt.charapter06.pool;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/26/16:04
* @Description:
*/
@Slf4j(topic = "c.PoolTest")
public class PoolTest {
public static void main(String[] args) {
Pool pool = new Pool(4);
//十个线程来争夺4个线程池中的资源
for(int i=0;i<10;i++){
new Thread(()->{
Connection connection = pool.getConnection();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用完之后进行释放
pool.returnConnection(connection);
}).start();
}
}
}
@Slf4j(topic = "c.Pool")
class Pool{
//表示线程池大小
private final int poolSize;
//连接对象数组,用来存放创建好的对象
private Connection[] connections;
//创建一个数组用来表示每一个对象的状态是否被使用,如果被使用则是1,没有被使用为0
private AtomicIntegerArray states;
//构造方法,用来给线程池的属性赋初值
public Pool(int poolSize){
this.poolSize=poolSize;
this.connections=new Connection[poolSize];
this.states=new AtomicIntegerArray(new int[poolSize]);
for(int i=0;i<poolSize;i++){
connections[i]=new MockConnection("连接"+i);
}
}
//提供借连接的方法
public Connection getConnection(){
while(true){
for(int i=0;i<poolSize;i++){
//说明此线程没有用到,是空闲的
if (states.get(i)==0) {
if (states.compareAndSet(i,0,1)) {
log.debug("使用这个连接{}",connections[i]);
return connections[i];
}
}
}
//遍历完,发现没有空闲连接,则让当前线程等待‘
synchronized (this){
try {
log.debug("等待~");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//提供归还链接的方法
public void returnConnection(Connection connection){
//先判断到底是归还的哪一个连接
for(int i=0;i<poolSize;i++){
if(connections[i]==connection){
states.set(i,0);
synchronized (this){
log.debug("归还线程{}",connection);
this.notifyAll();
}
break;
}
}
}
}
//虚假连接测试类,这个类要重写很多方法,我先删掉了,测试时需要重写接口的方法
class MockConnection implements Connection{
private String name;
public MockConnection(String name) {
this.name = name;
}
@Override
public String toString() {
return "MockConnection{" +
"name='" + name + '\'' +
'}';
}
}
JDK自带的线程池知识
线程池的五种状态,用int类型的高3位来表示状态,后面29位表示线程的数量
- Running 111
- ShutDown 000 处理完阻塞队列中的任务后停止接收新任务
- Stop 001 全部停止,即使队列中还有任务
- Tidying 010 任务结束,即将进入终结状态的状态
- Terminated 011 进入终结状态
用高三位和29位组合代替信息,而不是用两个int类型的数代替的原因:可以减少一次CAS操作
线程池的构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
}
各大参数的含义
corePoolSize:代表线程池中核心的处理任务的数量
maximumPoolSize:代表最多的线程数,max-core为救急线程,当任务的数量超过核心线程数,并且队列任务也满了之后,就会开启max-core个救急线程
keepAliveTime:代表当不需要救急线程之后,救急线程存活的时间
unit:代表救急线程存活的时间单位
workQueue:存放任务的队列
ThreadFactory threadFactory:线程工厂,可以为线程起名
handler:当所来的任务超过最大线程数和阻塞队列中的数时,针对额外的任务采取的拒绝策略
线程池的三大方法
1 固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
2单一线程池
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
适用于多个任务需要排队执行的情况,可处理的核心任务数固定为1任务多的时候会放到无界队列中排队,和单一线程处理任务的区别是,当有任务出现异常时,此线程池会创建出新的线程来处理任务。
3缓冲的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
根据构造方法可以看出创建的全部都是救急线程,队列使用的是SynchronousQueue,此队列的特点是没有容量,当没有取任务的时候,是放不进去任务的,类似于一手交钱一手交货。
线程池中提交任务和终结任务的几个方法
package com.njupt.charapter07;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/28/10:09
* @Description:
*/
@Slf4j(topic = "c.JDKThreadPool00")
public class JDKThreadPool00 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//这个构造方法就是可以给线程起一个好名字
ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
private AtomicInteger c=new AtomicInteger(1);
//只是给线程起名字
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable,"海"+c.getAndIncrement());
}
});
//执行提交任务方法一:execute
pool.execute(()->{
System.out.println("当先任务执行"+Thread.currentThread().getName());
});
pool.execute(()->{
System.out.println("当先任务执行"+Thread.currentThread().getName());
});
pool.execute(()->{
System.out.println("当先任务执行"+Thread.currentThread().getName());
});
//不在接收新的任务,但是会把队列中的任务执行完
// pool.shutdown();
//会把任务全部终止不在执行队列中的任务
// pool.shutdownNow();
//执行提交任务方法2:submit
//submitTest(pool);
//执行提交任务方法3
//invokeAllTest(pool);
String re= pool.invokeAny(Arrays.asList(
() -> {
log.debug("开始");
Thread.sleep(1000);
return "1";
}, () -> {
log.debug("开始");
Thread.sleep(500);
return "2";
}, () -> {
log.debug("开始");
Thread.sleep(2000);
return "4";
}));
//因为休眠时间最小,最先获取到结果,其他任务就不执行了
log.debug("{}",re);
/* ExecutorService pool1 = Executors.newCachedThreadPool();
ExecutorService service = Executors.newSingleThreadExecutor();*/
}
private static void invokeAllTest(ExecutorService pool) throws InterruptedException {
List<Future<String>> futures = pool.invokeAll(Arrays.asList(
() -> {
log.debug("开始");
Thread.sleep(1000);
return "1";
}, () -> {
log.debug("开始");
Thread.sleep(500);
return "2";
}, () -> {
log.debug("开始");
Thread.sleep(2000);
return "4";
}));
futures.forEach(f->{
try {
log.debug("{}",f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
private static void submitTest(ExecutorService pool) throws InterruptedException, ExecutionException {
//但方法的接口,可以用lambda表达式来代替
Future<String > result=pool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "线程执行完毕,请future接收参数";
}
});
System.out.println(result.get());
}
}
按照特定顺序执行
有时候需要线程执行过程中设置执行的间隔时间,可以用到下面几个方法。
package com.njupt.charapter07;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/11:06
* @Description:
*/
@Slf4j(topic = "c.TestTimer")
public class TestTimer {
public static void main(String[] args) {
log.debug("主线程开始执行");
//延时执行
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
pool.schedule(()->{
log.debug("副线程开始执行");
},1, TimeUnit.SECONDS);
//设置每次执行时间间隔
pool.scheduleAtFixedRate(()->{
log.debug("按照一定时间间隔开始执行");
},1,1,TimeUnit.SECONDS);
//也是按照一定时间开始执行,但是此方法是当上次任务结束之后开始算起
pool.scheduleWithFixedDelay(()->{
try {
Thread.sleep(1000);
log.debug("并不是每隔1秒哦");
} catch (InterruptedException e) {
e.printStackTrace();
}
},1,1,TimeUnit.SECONDS);
}
}
在线程池中执行任务时有异常不上报解决办法
- 加入try/catch语句块包裹
- 使用submit的带返回值的方法,实现callable接口,然后使用Future接收
package com.njupt.charapter07;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/11:06
* @Description:
*/
@Slf4j(topic = "c.TestTimer")
public class TestTimer {
public static void main(String[] args) throws ExecutionException, InterruptedException {
log.debug("主线程开始执行");
//延时执行
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
pool.schedule(()->{
//不打印异常信息
try {
int i=1/0;
} catch (Exception e) {
e.printStackTrace();
}
log.debug("副线程开始执行");
},1, TimeUnit.SECONDS);
ExecutorService threadPool = Executors.newFixedThreadPool(1);
Future<Boolean> submit = threadPool.submit(() -> {
int i = 1 / 0;
return true;
});
log.debug(String.valueOf(submit.get()));
}
}
如何设置让线程每周周四18:00开始定时执行任务
package com.njupt.charapter07;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/12:07
* @Description:任务调度线程池的应用,实现每一周的晚上6点开始定时执行
*/
public class ScheduleTest {
public static void main(String[] args) {
//获取当前时间
LocalDateTime now = LocalDateTime.now();
//获取下周四的时间
LocalDateTime dateTime = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
//判断出距离下一个周四的时间
if(now.compareTo(dateTime)>0){
dateTime=dateTime.plusWeeks(1);
}
long initailDelay= Duration.between(now,dateTime).toMillis();
long peroid=1000*60*60*24*7;
ScheduledExecutorService poll= Executors.newScheduledThreadPool(1);
poll.scheduleAtFixedRate(()->{
System.out.println("时间到了,开始执行!");
},initailDelay,peroid, TimeUnit.MILLISECONDS);
}
}
Fork/Join线程池实现
fork/join是jdk1.7加入的新的线程池的实现,它体现额是一种分治思想,适用于能够进行任务拆分的cup密集型运算,在分治的基础上加入了多线程,可以把每一个任务的分解和合并交给不同的线程来完成,进一步提高了运算效率
package com.njupt.charapter07;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/12:31
* @Description:使用forkjoin线程池来实现任务的拆分,求1-n的和,第一步先创建一个任务对象,2利用forkjoin来执行任务对象,
*/
public class ForkJionThreadPool {
public static void main(String[] args) {
ForkJoinPool joinPool = new ForkJoinPool(4);
System.out.println(joinPool.invoke(new MyTask(5)));
}
}
//当有返回值时,继承RecursiveTask,当没有返回值时继承RecursiveAction
class MyTask extends RecursiveTask<Integer>{
private int n;
public MyTask(int n){
this.n=n;
}
@Override
protected Integer compute() {
if(n==1){
return 1;
}
MyTask t1=new MyTask(n-1);
t1.fork();//让另一个线程执行
//join方法是返回值
Integer result = n+t1.join();
return result;
}
}
AQS
自定义一个锁
package com.njupt.charapter07.juc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/15:32
* @Description:自己实现的锁,但是是一个不可重入锁
*/
@Slf4j(topic = "c.MyLock")
public class MyLock {
public static void main(String[] args) {
YueLock lock=new YueLock();
new Thread(()->{
lock.lock();
log.debug("上锁了");
//不可重入锁
lock.lock();
try{
log.debug("上锁了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
log.debug("解锁了");
lock.unlock();
}
},"t1").start();
new Thread(()->{
lock.lock();
try{
log.debug("上锁了");
}finally {
log.debug("解锁了");
lock.unlock();
}
},"t2").start();
}
}
//自定义一个不可重入锁
class YueLock implements Lock{
//同步器类,大部分功能都在此处实现。希望做一个独占锁
class MySync extends AbstractQueuedSynchronizer{
@Override//尝试获取锁
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
//把state可以改变成功,说明获取锁成功
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override//尝试释放锁
protected boolean tryRelease(int arg) {
//把当前资源所有者设为null,释放锁
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override//是否出有独占锁
protected boolean isHeldExclusively() {
//如果状态是1,则说明正在使用当前所
return getState()==1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private MySync sync=new MySync();
@Override//加锁,不成功的话会加入到阻塞队列中
public void lock() {
//不用tryAcquire是因为改方法只会尝试获取一次
sync.acquire(1);
}
@Override//可打断的锁
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override//尝试获取锁
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override//尝试加锁,带有超时时间
public boolean tryLock(long l, TimeUnit timeUnit) throws InterruptedException {
return sync.tryAcquireNanos(1,timeUnit.toNanos(l));
}
@Override//解锁
public void unlock() {
sync.release(1);
}
@Override//条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
ReentrantLock的内部实现原理
公平锁原理
公平性体现在在竞争之前,先检查队列中的节点有没有其他的等待线程,如果还有就优先让队列中的线程先执行。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = this.getState();
if (c == 0) {
if (!this.hasQueuedPredecessors() && this.compareAndSetState(0, acquires)) {
this.setExclusiveOwnerThread(current);
return true;
}
} else if (current == this.getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
this.setState(nextc);
return true;
}
return false;
}
非公平锁原理
如果监测到锁的状态为空,任何线程都可以来竞争锁,不会把等待队列中的元素优先级设的很高。
![在这里插入图片描述](https://img-blog.csdnimg.cn/edb83fca2bc64dda92974119776b4e81.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5YCU5by655qE5Yqg55Om,size_20,color_FFFFFF,t_70,g_se,x_16
waitState-1表示当前节点有责任去唤醒后继节点,来竞争锁,0表示是最后一个,如果当前线程结束之后,把锁释放开后,如果没有外界来新的线程竞争,就从阻塞队列中找,找到队列中从头开始找,并且利用哨兵准备唤醒下一个阻塞的线程即后继节点
final boolean nonfairTryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = this.getState();
if (c == 0) {
if (this.compareAndSetState(0, acquires)) {
this.setExclusiveOwnerThread(current);
return true;
}
} else if (current == this.getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
this.setState(nextc);
return true;
}
return false;
}
可重入锁原理
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = this.getState();
if (c == 0) {
if (!this.hasQueuedPredecessors() && this.compareAndSetState(0, acquires)) {
//获取之后把当前线程设为锁的拥有者
this.setExclusiveOwnerThread(current);
return true;
}
} else if (current == this.getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
this.setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = this.getState() - releases;
if (Thread.currentThread() != this.getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
} else {
boolean free = false;
if (c == 0) {
free = true;
this.setExclusiveOwnerThread((Thread)null);
}
this.setState(c);
return free;
}
}
条件变量的实现原理
和fairSyn对象的不同在于虽然Condition对象也维护了双向链表,但是没有头节点了,并且在等待中的状态设为了-2
为什么要进入fullRelease呢,因为当前这个锁有可能发生了锁重入,所以要全部释放掉,接下来开始唤醒等待队列中的节点,来获取锁
public final void await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
} else {
AbstractQueuedSynchronizer.Node node = this.addConditionWaiter();
int savedState = AbstractQueuedSynchronizer.this.fullyRelease(node);
int interruptMode = 0;
while(!AbstractQueuedSynchronizer.this.isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = this.checkInterruptWhileWaiting(node)) != 0) {
break;
}
}
if (AbstractQueuedSynchronizer.this.acquireQueued(node, savedState) && interruptMode != -1) {
interruptMode = 1;
}
if (node.nextWaiter != null) {
this.unlinkCancelledWaiters();
}
if (interruptMode != 0) {
this.reportInterruptAfterWait(interruptMode);
}
}
}
signal,在条件变量里await状态为-2,然后把一个线程从await状态到signal,就要加入到锁的竞争中,因为是加到竞争状态的最后一个,于是将状态设为0,因为后面没有要通知的线程了。
public final void signal() {
if (!AbstractQueuedSynchronizer.this.isHeldExclusively()) {
throw new IllegalMonitorStateException();
} else {
AbstractQueuedSynchronizer.Node first = this.firstWaiter;
if (first != null) {
this.doSignal(first);
}
}
}
//如果某一个线程被唤醒,要做两件事,1是要从条件变量的wait对列中先断开,然后加入锁竞争的队列中
private void doSignal(AbstractQueuedSynchronizer.Node first) {
do {
if ((this.firstWaiter = first.nextWaiter) == null) {
this.lastWaiter = null;
}
first.nextWaiter = null;
} while(!AbstractQueuedSynchronizer.this.transferForSignal(first) && (first = this.firstWaiter) != null);
}
//改变状态,从-2变成0,加入到锁竞争的队列中
final boolean transferForSignal(AbstractQueuedSynchronizer.Node node) {
if (!node.compareAndSetWaitStatus(-2, 0)) {
return false;
} else {
//这个enq方法是加入队列的方法
AbstractQueuedSynchronizer.Node p = this.enq(node);
int ws = p.waitStatus;
if (ws > 0 || !p.compareAndSetWaitStatus(ws, -1)) {
LockSupport.unpark(node.thread);
}
return true;
}
}
读写锁ReentrantReadWriteLock
当读操作远远高于写操作时,这时候使用读写锁可以使读-读可以并发执行
package com.njupt.charapter07.juc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/18:38
* @Description:对读不加锁,对读写和写写是互斥的
*/
public class ReadWriteTest {
public static void main(String[] args) throws InterruptedException {
Content content = new Content();
new Thread(()->{
content.read();
},"t1").start();
new Thread(()->{
content.read();
},"t2").start();
Thread.sleep(1000);
new Thread(()->{
content.write();
},"t3").start();
new Thread(()->{
content.write();
},"t4").start();
}
}
@Slf4j(topic = "c.Content")
class Content{
private Object object;
private ReentrantReadWriteLock rw=new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r =rw.readLock();
private ReentrantReadWriteLock.WriteLock w=rw.writeLock();
public Object read(){
r.lock();
try{
log.debug("获取到读锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
r.unlock();
}
return object;
}
public void write(){
w.lock();
try {
log.debug("获取到写锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
w.unlock();
}
}
}
结果
2021-10-29 18:49:50.426 [t2] c.Content -获取到读锁
2021-10-29 18:49:50.427 [t1] c.Content -获取到读锁
2021-10-29 18:49:51.439 [t3] c.Content -获取到写锁
2021-10-29 18:49:52.439 [t4] c.Content -获取到写锁
Process finished with exit code 0
注意事项:在获取读写时,向在获取写锁是不行的,必须要把读锁释放掉才可以获取写锁,但是可以获得写锁时,在获得读锁
读写锁的应用(在查询表的缓存)
package com.njupt.charapter07.juc;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/29/19:28
* @Description:基于查找和修改数据库的过程中,借助读写锁来实现缓存机制,适合读多写少的问题
* 可以改进的点:
* 1没有考虑缓存容量
* 2没有考虑缓存过期,长久不通用的应该删掉
* 3只适合单机
* 4并发性低,目前只用了一把锁
* 5更新缓存太粗暴
*
*/
public class ReentrantLockReadWriteApplication {
}
class Catch{
//用于存放不同的sql语句和id参数值的查询结果,缓存在map集合中
private Map<Result,Object> results=new HashMap();
private ReentrantReadWriteLock rw=new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock read=rw.readLock();
private ReentrantReadWriteLock.WriteLock write=rw.writeLock();
//查询单条语句的方法
public <T> T queryOne(Class<T> beanClass,String sql,String id){
read.lock();
Result key;
T result;
try {
key = new Result(sql,id);
result = (T)results.get(key);
if(result!=null){
return result;
}
} finally {
read.unlock();
}
//开始进行如果缓存中没有,则需要到数据库中去查,并且把查到的结果写入到缓存中,所以需要加入写锁
write.lock();
try {
//此处,如果不再次进行判断,其他线程有可能再次进入,所以需要二次检查
result=(T)results.get(key) ;
if(result==null){
//如果为空,则说明缓存中没有,需要取数据库里查询,如果查询到,将结果写入到集合中,在返回
String end="这里调用dao方法,查询新的结果";
result= (T) end;
results.put(key,result);
}
return result;
} finally {
write.unlock();
}
}
public int update(String sql, String id) {
write.lock();
try {
//先到数据库里去修改结果,假设修改成功
int updateResult = 1;
//查询成功之后需要先清空缓存,然后在把结果写到缓存中
results.clear();
return updateResult;
} finally {
write.unlock();
}
}
class Result{
private String sql;
private String id;
public Result(String sql, String id) {
this.sql = sql;
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Result result = (Result) o;
return Objects.equals(sql, result.sql) &&
Objects.equals(id, result.id);
}
@Override
public int hashCode() {
return Objects.hash(sql, id);
}
}
}