wait / notify
线程调度是随机的,但是我们可以使用wait/notify进行规划。
join是控制线程结束顺序,而wait/notify是控制详细的代码块,例如:
线程1执行完一段代码,让线程2继续执行,此时线程2就通过wait进行阻塞,等到线程1执行结束后,再通过notify唤醒线程2。
线程饿死:线程1释放锁资源后,其他线程和线程1要进行竞争锁,但是由于其他线程还要进行唤醒操作,可能会出现线程1一直拿到锁资源,导致其他线程拿不到锁的情况。
wait/notify可以解决线程饿死问题
wait / notify 的使用
wait / notify 是Object类中的方法,而Object又是所有类的“祖宗类”,所以我们可以随便创建一个类进行调用wait / notify。
此时调用wait的线程执行到wait时就会触发阻塞,当别的线程调用notify时,wait线程才会解除阻塞。
wait / notify都要再synchronized中使用(wait方法会释放锁对象,因此要先拿到锁),而且两个锁的锁对象要相同,同时调用wait / notify的对象也要是锁对象,这四个地方要同时满足。
wait()方法
1.wait()方法主要进行的是三件事:
1)释放锁对象
2)使当前代码的线程进行等待(将线程添加到等待队列)。
3)满足某个条件时,将重新尝试获取到这个锁,这样重新获取锁才可以继续synchronized保护的代码。
当wait进行阻塞时,会有两个阶段:
1)WAITING阻塞,wait进行阻塞。
2)BOLOCK阻塞,这个阻塞是执行完notify后,wait会重新尝试获取锁,但是可能会遇到锁竞争导致阻塞。
2.默认情况下,wait()方法进行的是死等,没有notify就会一直阻塞,但是wait()提供了有参数版本。
3.如果是多个wait一个notify,此时唤醒的就是就是随机的wait,可以使用natifyAll,但是这个随机并不是数学上的随机,而是取决于调度器。
sleep和wait两者的区别
1.wait的设计就是为了唤醒操作,而带参版本是后手;sleep的设计是为了到时间唤醒,虽然可以通过interrupt()提前唤醒,但是会抛出异常。
2.wait会释放锁,所以wait要搭配synchronized使用;sleep可以在锁中使用,也不会释放锁,也可以在锁外使用。
设计模式
设计模式是解决一些固定场景的特定套路。
1.单例模式
单个实例(对象),指在设计类时只单独创建一个实例,将构造方法设为private,这样在new时就会报错。
1)饿汉模式
只进行读操作,线程安全
2)懒汉模式
线程不安全
解决方法:加锁
这里的线程安全问题其实是第一次创建时才有线程安全问题,所以我们要进行判断,当不为空时就不去加锁,因为加锁也要开销,第一层if两个线程都进入,但是加上锁后,一个线程阻塞一个执行,当第二个线程开始后,就会判断不成立,此时就直接返回。
在单线程中判断时往往是不变的,但是多线程就可能会有在判断时有别的线程影响。
2.指令重排序触发的线程安全问题
指令重排序是一种优化机制。
上面懒汉模式new一个对象涉及到很多指令,可以抽象三步:
1.申请内存
2,在内存中进行初始化
3.将内存地址,存到变量应用中
编译器可能会优化成1 3 2.
如果在线程1中进行的是1 3 2 的顺序,此时当进行到3时,线程2判断引用不为空,此时就会返回值,但是此时线程1并没有进行初始化操作,所以由线程2返回的值就是错误的。
因此要将这个变量加上volatile来告诉编译器不要进行这样的优化。
阻塞队列
阻塞队列是一种特殊的队列,也是先进先出。
阻塞队列的规则是:
队列为空,尝试出队列,触发阻塞,直到队列不为空。
队列满了,尝试进队列,触发阻塞,直到队列不满。
阻塞队列可以运用在生产者消费者模型中,生产者消费者之间的速度不同,所以引入阻塞队列进行协调作用。
阻塞队列的优点一:减少资源竞争,提高效率。
优点二:可以更好做到代码块之间解耦合。
优点三:可以降低服务器之间的压力,当前面的服务器压力大时,阻塞队列(消息队列)后面的服务器将不会收到太大压力。
阻塞队列的实现
JAVA标准库中就有现成的阻塞队列的实现BlockingQueue。
一种是基于链表实现的,一种基于数组实现,唯一不同的是数组实现的一定要指定最大长度。
模拟实现阻塞队列
class BlockQueue{
public String [] arr = null;
public int head = 0;
public int tail = 0;
public int size = 0;
public Object object = new Object();
public BlockQueue(int m) {
arr = new String[m];
}
public void put(String newele) throws InterruptedException {
synchronized (object) {
while(size >= arr.length) {
object.wait();
}
arr[tail] = newele;
tail++;
size++;
object.notify();
if (tail >= arr.length) {
tail = 0;
}
}
}
public String take() throws InterruptedException {
synchronized (object) {
while(size == 0) {
object.wait();
}
String n = arr[head];
head++;
size--;
object.notify();
if (head >= arr.length) {
head = 0;
}
return n;
}
}
}
public class dome12 {
public static void main(String[] args) {
BlockQueue n = new BlockQueue(100);
Thread thread = new Thread(()->{
int count = 0;
for (int i = 0; i < 100; i++) {
try {
n.put(" "+count);
System.out.println("进入"+count);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread1 = new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
String k = n.take();
System.out.println("取出"+k);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();;
thread.start();
}
}