1.volatile
这个单词的意思是易变的。
(1)volatile主要的作用是保护变量的内存可见性问题(90%)
内存可见性问题就是有多个线程同时读取同一变量,当其中任意一个线程修改其变量的值时,其他线程都无法及时得到最新值。
而被volatile修饰后,将从主内存读取又立即写回主内存。
package cn.tan.vola;
import java.util.concurrent.TimeUnit;
//volatile 保护内存可见性
public class Main {
volatile static boolean quit = false;
static class MyThread extends Thread {
@Override
public void run() {
int r = 0;
while (quit == false) { // 线程自己的工作内存
r++;
}
System.out.println(r);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
TimeUnit.SECONDS.sleep(5);
quit = true;
}
}
比如这个例子,如果quit变量不加volatile的话,子线程仍然会进行不会终止,因为它未读到quit被主线程修改了,可是加上的话,子线程读到了quit为false就退出了。
(2)后面这两条当作拓展
volatile保证的原子性,一次写入。
(3)
建一个对象的时候,一般都是按照这样123的流程来执行的,而第三条只有一部,所以这个流程可能会变成132,。
加上了volatile禁止了重排序,强制按照123的流程来走。
2.单例模式
(1)饿汉模式
package cn.tan.singleton;
//饿汉模式
public class StarveMode {
private StarveMode(){}
private static final StarveMode instance=new StarveMode();
public static StarveMode getInstance(){
return instance;
}
}
饿汉模式比较简单,在类加载的时候就创建了这个对象,日后需要的时候就用get方法来获取。
(2)懒汉模式
public class LazyModeV1 {
private static LazyModeV1 instance = null;
public synchronized static LazyModeV1 getInstance() {
if (instance == null) {
instance = new LazyModeV1();
}
return instance;
}
private LazyModeV1() {
}
}
这种模式就是在调用get方法的时候,先判断有没有对象,如果没有就创建,有就直接返回,显然这里是check-update的模式,线程不安全,需要加锁,所以在这个get方法这里加锁保证安全性。
然而上述给方法加锁性能不太好,所以应该给破坏原子性的操作进行加锁
public class LazyModeV2 {
private static volatile LazyModeV2 instance = null;
public static LazyModeV2 getInstance() {
if (instance == null) {
// A\B\C
synchronized (LazyModeV2.class) { // 只有 instance 为 null,才有必要用锁保护
instance=new LazyModeV2();
}
}
return instance;
}
private LazyModeV2() {
}
}
这样加锁还有问题吗?
二次判断的问题,比如两个线程A、B,A线程进行if判断,B线程也进行了第一次if判断,然后假设没有第二次判断,A成功加了锁,然后进程了赋值,释放锁,这时候B线程拿到锁,如果没有二次判断的话就又进行了一次初始化操作,
重排序问题,对象的赋值分为了三步,A线程比如拿到锁进行了1 和3步,完成了赋值但是第二部初始化操作完成了才算真正意义完成,此时切换到B线程,判断instance不为空直接返回这个对象,但实际上还没有初始化完毕,所以给 这个属性加上volatile关键字
public class LazyModeV2 {
private static volatile LazyModeV2 instance = null;
public static LazyModeV2 getInstance() {
if (instance == null) {
// A\B\C
synchronized (LazyModeV2.class) { // 只有 instance 为 null,才有必要用锁保护
if (instance == null) { // 这里有没有线程可能遇到 instance 不为 null 的情况
instance = new LazyModeV2(); // 1->3->2
}
}
}
return instance;
}
private LazyModeV2() {
}
}
(3)阻塞队列
这里用基于数组的形式来实现阻塞队列。
package cn.tan.blocking_queue_impl;
public class MyArrayBlockingQueue {
private long[] array;
private int frontIndex;
private int rearIndex;
private int size;
public MyArrayBlockingQueue(int capacity) {
array = new long[capacity];
frontIndex = 0;
rearIndex = 0;
size = 0;
}
// put 放入
// take 取
// 只有 P 会调用 put
//生产者调用put
public synchronized void put(long e) throws InterruptedException {
// 是不是满了
while (size == array.length) {
wait();// 作为 P,在等 C
}
// 队列不是满的
array[rearIndex] = e;
rearIndex++;
if (rearIndex == array.length) {
rearIndex = 0;
}
size++;
notify(); // 生产者唤醒消费者P -> C
}
//消费者才可take
public synchronized long take() throws InterruptedException {
while (size == 0) {
wait(); // 作为 C,在等 P
}
// 队列一定不是空的
long e = array[frontIndex];
frontIndex++;
if (frontIndex == array.length) {
frontIndex = 0;
}
size--;
notify(); //消费者唤醒生产者 C -> P
return e;
}
}
对这个代码总结以下几点:
(1)实现原理是生产者消费者模式。
(2)只有生产者才能进行put操作,只有消费者才能进行take操作。
(3)边界情况,如果阻塞队列满了那么生产者就需要等待,如果阻塞队列为空那么消费者就需要等待,那么他们需要通过谁来唤醒的,看take和put的代码,里面都有一个notify唤醒的操作,但肯定不是他们自己唤醒自己的,不合常理,put方法也就是生产者的方法里面如果生产了产品调用了notify,说明是要告诉消费者有产品了,唤醒了消费者,而take那里就是相反的,消费者消费了再notify就会打破生产者的wait状态。
关于wait和notify说上几句话。
package cn.tan.wait_notify;
import java.util.concurrent.TimeUnit;
public class Demo02 {
// 针对同一个对象进行 notify,才能进行唤醒
static Object o = new Object();
static class MyThread extends Thread {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o) {
System.out.println("试图唤醒主线程");
o.notify();
}
}
}
public static void main(String[] args) throws InterruptedException {
synchronized (o) {
MyThread t = new MyThread();
t.start();
System.out.println("进入等待");
o.wait();
System.out.println("我被唤醒了");
}
}
}
这段代码就是主线程等待,子线程休眠5秒后进行唤醒操作,以下是结果
有人可能会有疑问,主线程明明加锁了,那么子线程可以获取到这把锁吗,那不就永远不会执行子线程的那部分代码了吗。以下解答你的疑惑