引入
在学习过程中,我们可能听过关于”线程安全“的问题,就StringBuffer和StringBuilder而言,StringBuffer是线程安全的,而StringBuilder不是,,
”敢问为何如此“,,
大多数回答都是:
”StringBuffer里对应的方法有synchronized 修饰,而StringBuilder里没有。“
然后就没了。
So,,此时我们只能靠自己了。What is the synchronized?
synchronized 是Java中的 关键字,是一种同步锁,被其修饰过的代码块、方法、静态方法 或 类 中的 对象 ,在某一时刻只能被一个线程访问。
生产者——消费者
在这之前先说明两个概念, 等待池 and 锁池
锁池:现存在一个对象(O对象),有一个线程(a线程)正在执行 O对象 中被 synchronized 修饰的同步方法,,那么,此时的 a线程 正持有着 此对象的 锁,其他线程在 请求该 锁 的时候就会被阻塞,然后进入该对象的 锁池 中。
等待池:如果一个线程(线程A)调用了某个对象的 wait()方法,那么线程A就会释放 该对象的所有锁,然后进入该对象的 等待池。
首先设置 缓冲区
缓冲区的作用:为了缓解 生产 和 消费 速度不匹配 的问题。
- 这里用一个 栈 SynStack (后进先出)来做缓冲区
/**
* @ClassName SynStack
* @Description buffer——缓冲区 的大小 为 3
* @Author SkySong
* @Date 2020-04-06 19:26
*/
public class SynStack {
private int index = 0;
private char[] buffer = new char[3];
public synchronized void push(char c){
while (index == buffer.length){
try {
System.out.println("生产阻塞,下一条Produced被阻塞");
this.wait();//生产线程进入等待池
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();//从等待池 中 唤醒被阻塞的那一条线程(消费进程)
//被唤醒的消费线程会开始消费
buffer[index] = c;
index++;
}
public synchronized char pop(){
while (index == 0){
try {
System.out.println("消费阻塞");
this.wait();//消费线程进入等待池
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();//从等待池 中 唤醒被阻塞的那一条线程(生产进程)
//被唤醒的生产线程会继续完成 当条 生产
index--;
return buffer[index];
}
}
- 生产者 Producer
/**
* @ClassName Producer
* @Description
* @Author SkySong
* @Date 2020-04-06 19:41
*/
public class Producer implements Runnable{
SynStack stack;
public Producer(SynStack stack) {
this.stack = stack;
}
@Override
public void run() {
char c;
for (int i = 0;i < 10;i++){
c = (char) (Math.random()*26+'A');
stack.push(c);
System.out.println("Produced: "+c);
try {
//让生产者睡的短一点
Thread.sleep((long) (Math.random()*100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 消费者 Consumer
/**
* @ClassName Consumer
* @Description
* @Author SkySong
* @Date 2020-04-06 20:54
*/
public class Consumer implements Runnable{
SynStack stack;
public Consumer(SynStack stack) {
this.stack = stack;
}
@Override
public void run() {
char c;
for (int i = 0;i < 10;i++){
c = stack.pop();
System.out.println("Consumer: "+c);
try {
//多睡会,让生产者先生产
Thread.sleep((long) (Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 测试 Test
SynStack synStack = new SynStack();//缓存区
Runnable add = new Producer(synStack);
Runnable minus = new Consumer(synStack);
new Thread(add).start();
new Thread(minus).start();
- 运行结果 Result
消费阻塞 开始缓冲区里没有产品,消费线程执行wait()方法,进入等待池中
Produced: K 生产出了一个K,同时唤醒消费线程(从等待池中拽到锁池中)
Consumer: K 消费了一个K
Produced: V
Produced: P
Produced: N 最外层的产品
生产阻塞,下一条被阻塞 前面已经生产了3个了,缓冲区满了
Produced: R 此时该线程应当执行wait()方法,进入等待池
Consumer: N 消费了栈(缓冲区)中最外面的一个产品N,同时执行了notify()方法,将生产线程唤醒
生产阻塞,下一条被阻塞
Produced: K
Consumer: R 这里为什么是R,因为被唤醒的生产线程会继续生产完当初被阻塞的产品
生产阻塞,下一条被阻塞
Produced: L
Consumer: K
生产阻塞,下一条被阻塞
Consumer: L
Produced: J
生产阻塞,下一条被阻塞
Produced: B
Consumer: J
生产阻塞,下一条被阻塞
Consumer: B
Produced: Q
Consumer: Q
Consumer: P
Consumer: V
最后补充一下notify()的作用:
notify():随机在等待池中 唤醒一个线程,将线程 从 等待池———>锁池,又有了和其他线程争夺 对象锁 的权利了
notifyAll(): 将等待池里的所有线程 都叫 ”醒“。