设计模式
设计模式中的代理和装饰器主要区别:
- 代理不增加别的功能,只是优化了内部处理
- 装饰器增加了别的功能,需要一个大的类来包含这个装饰器,这样总的功能就丰富了
同步与竞争
以前我对java中或者说所有语言中的锁和同步问题有很多模糊的地方,比如我不知道什么时候该用锁,什么时候该用通知:notify wait 这种操作,今天又一次浏览博客的时候好像理解了一点:
锁的目的是啥?
首先我们要明白一个问题,锁的出现是为了解决什么问题的,我之前理解是为了解决同步问题,大错特错,这就是理解不到位,锁的出现是为了 解决竞争的问题,因为多个线程想做同一件事情,只能让一个线程去做,同时做这件事情就有问题,比如存钱。
那么锁解决了竞争的问题,在并发编程中还有啥问题需要解决呢,我们可以想一下多个线程如果想要做一件事情的不同部分,举个例子,在最常见的 生产者消费者模型中,如果多个线程同时写入(锁实现),与此同时同个线程进行消费也就是取出的时候,虽然取出动作也可以看成多线程竞争,所以需要加锁, 这没问题。
但是生产者消费者模型是一个事情,我们加锁只解决了多个工人(线程)同时工作的问题,但是没有解决如何协调生产和消费的问题,比如没有货物的时候,消费者 一群工人应该怎么做,如果映射现实就是“等待”,这个等待的操作对应java里面就是this.wait()操作。
聪明的同学可能会问,如果我不等待(wait)只是一直取这个队列里面的内容,没有就返回空,不行吗?bingo!你说的很对,这一点问题没有,可是“工人也是人啊”,工人要一直查看 有没有货物到来,有没有货物到来,工人经历是有限的,工人这种不间断的查看是要耗费更多“粮食”(cpu计算资源)的,所以这里就提现了wait 的强大之处,我先wait住,然后等待放货物的工人给我打一个电话,说有货物到了(这里应该是给消费货物的工人集体广播,竞争才有效率嘛)
所以自然而然引申出生产工人的一个操作,this.notifyAll() 通知所有的工人(广播),这样一来就能够协同工作了,双方都节省了粮食和精力,岂不妙哉!!!(生产者是主动型的,所以不会说浪费粮食,毕竟有货物才干活)
总结:现在我们理解了锁是用来解决竞争问题,而wait 与 notify 是为了解决协作问题(当然你也可以理解成同步问题)这与go的协程是有区别的,协程是语言层面的调度合作,go也是通过chan 实现了这种协作关系,我个人认为go的更优雅,不用记住java中千奇百怪的名称。当然这是一家之言,自行评判!
wait 和 notify + synchronized 真的就没问题了吗?| ReentrantLock
too young! 如果你了解过一点现代的语言,可能就会发现java的历史包袱有多大,不过考虑当时的时代背景,也算情有可原,所以java为了满足现代开发的需要,一点点的打补丁,变得越来越臃肿 老的class不能删除,为了兼容,又不断的增加新的类,来实现各种现代化的功能。
所以有问题,wait 和 notify 因为都要用到synchronized,而synchronized的不当使用极易造成死锁,需要注意上锁顺序,这样一来不就平白加大RD的难度了,业务代码本来就很烦了,还要注意这个,得不偿失 所以java赶紧打了补丁,从java5开始,推出了juc.ReentrantLock来代替synchronized,那么它有啥好处呢
看下面这段代码:
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock();
try {
count += n;
} finally {
lock.unlock();
}
}
}
synchronized是语言层面提供的锁,所以不用考虑异常,jvm会为我们处理,而ReentrantLock 是代码层面实现的,那么就需要我们自己考虑锁的释放问题了,这不就需要自己在finally中调用unlock() 方法,话说回来,我们是要解决死锁问题的,所以它提供了下面这样一个功能:
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
看到了吗,等不到我就不等了,这样会有效避免死锁,因为程序可以自己选择去做一些额外的事情,不会阻塞在这里,好,到现在死锁的问题解决了,那么聪明的你可能会想,如果我想实现一个阻塞队列,这个要如何操作, 如果直接返回了,那么也不能阻塞了啊!对,所以还有一个配套设施!
Condition 使用
上面我们说ReentrantLock解决了一部分问题,比如死锁的问题,但是如果使用了ReentrantLock,多个任务的协作怎么办呢,当然已经配套了相应的解决措施,Condition。
class TaskQueue {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Queue<String> queue = new LinkedList<>();
public void addTask(String s) {
lock.lock();
try {
queue.add(s);
condition.signalAll();
} finally {
lock.unlock();
}
}
public String getTask() {
lock.lock();
try {
while (queue.isEmpty()) {
condition.await();
}
return queue.remove();
} finally {
lock.unlock();
}
}
}
看到这里你可能会问,这不是脱了裤子放屁吗,既然没有使用tryLock的语法,那么和synchronized的解决方案没有不同,是的,不同的地方在于condition还有别的使用方式 比如
if (Condition.await(1,TimeUnit.SECOND)) {
// 被其他线程唤醒,执行
return queue.remove()
} else {
// 自己生成一个假的数据
return 1;
}
所以看到了吗,由于可以自己控制,能扩展的功能就多了,其实synchronized也是可以实现这样,但你需要自己加一个计时器,判断时间,一旦时间超了,就主动结束当前线程,生成一个假的数据 个人觉得并不好用,应用的场景不多,一般如果不阻塞的话,也不需要等一个具体的时间,http中有超时控制,只需要报一个错误就行了。
区别sleep、yield、wait、join
sleep 和 wait 相似都是会进行阻塞等待,区别是sleep是进入真的阻塞状态,结束之后一般运行剩余代码后就直接结束了,进入runnable状态,但是wait后需要sign或者signAll进行通知,进入锁定blocked,来重新争取锁,获取到之后才会和 正常的sleep一样,进入runnable状态,如果获取不到还会是进入wait的blocked状态,这个可以参考经典的图:

«««< HEAD
#
=======
深入一点AQS
AQS全称:AbstractQueuedSynchronized,在面试的时候是一个非常常见的考点,所以有必要理解其原理
9a7250c85685dd9a369841282a9f76415b17327a
