代码:
wait线程放弃了cpu的执行权进入wait状态。
Optional.of:https://www.cnblogs.com/baidawei/p/9443402.html
看下这个英文的解释。
这个是一个虚的概念,因为你在它里面拿不出任何的东西,但是它确实是去存放我们的线程的。
线程里面执行了wait之后会把自己放在锁住得对象的wait set里面去的。任何一个对象都有一个
wait set。
public class WaitSet {
private static final Object LOCK = new Object();
public static void main(String[] args) {
IntStream.rangeClosed(1, 10).forEach(i -> {
new Thread(String.valueOf(i)) {
public void run() {
synchronized (LOCK){
try {
Optional.of(Thread.currentThread().getName()+" will be to wait set").ifPresent(System.out::println);
LOCK.wait();
Optional.of(Thread.currentThread().getName()+" will leave to wait set").ifPresent(System.out::println);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
});
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
IntStream.rangeClosed(1, 10).forEach(i -> {
synchronized (LOCK){
LOCK.notify();
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
wait和notify都是lock对象去完成的。和线程本身是没有关系的。
---
代码:
关键的总结:
我再次起来会拿到begin......吗?
public class WaitSet2 {
private static final Object LOCK = new Object();
public static void work() {
synchronized (LOCK) {
System.out.println("begin......");
try {
System.out.println("Thread will comming......");
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Thread will end......");
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(){
@Override
public void run() {
work();
}
}.start();
Thread.sleep(1_000);
synchronized (LOCK){
LOCK.notify();
}
}
}
有地址恢复顺着执行的单元去执行。
再次抢到锁的话是执行的end
--------------------------------------------------04-----------------------------------------------------------------------
volatile:
代码:
public class VolatileTest {
// private volatile static int INIT_VALUE = 0;
// private static int INIT_VALUE = 0;
private final static int MAX_LIMIT = 5;
public static void main(String[] args) {
new Thread(() -> {
int localValue = INIT_VALUE;
while (localValue < MAX_LIMIT) {
if (localValue != INIT_VALUE) {
System.out.println("The value READER to" + INIT_VALUE);
localValue = INIT_VALUE;
}
}
}, "READER").start();
new Thread(() -> {
int localValue = INIT_VALUE;
while (localValue < MAX_LIMIT) {
System.out.println("The value UPDATE to" + (++localValue));
INIT_VALUE = localValue;
try {
Thread.sleep(5_00);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "UPDATE").start();
}
}
不加volatile打印:
线程的可见行:https://blog.csdn.net/dyllove98/article/details/8580734
这个就是不主动去主存里面去刷新缓存的。看前面的链接有讲的。
-----------------------------------------05------------------------------------------------
线程的基本特性:原子性 有序性 可见行。
volatile不能保证原子性的。
volatile:内存可见性(读一定再写完之后的) 有序性。
内存RAM,计算是放在CPU的寄存器的。
cpu--------cpu的cache-------ROM
cpu快,但是RAM慢,所以则加个缓冲区就是内存条。
cpu1和cpu2指的是不同的cpu的核。
JMM模型:https://blog.csdn.net/lxm55913153/article/details/79208126
每个线程都是修改自己本地的缓存的数据再更新到主存。
public class VolatileTest2 {
//private volatile static int INIT_VALUE = 0;
private static int INIT_VALUE = 0;
private final static int MAX_LIMIT = 50;
public static void main(String[] args) {
new Thread(() -> {
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("T1---" + (++INIT_VALUE));
try {
Thread.sleep(1_00);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "ADDER-1").start();
new Thread(() -> {
while (INIT_VALUE < MAX_LIMIT) {
System.out.println("T2---" + (++INIT_VALUE));
try {
Thread.sleep(1_00);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "ADDER-2").start();
}
}
打印:
注意和第一个的区别。
++会触发去主内存拿去刷新,但是刷新到主存可能是不及时的。
new Thread(() -> {
int localValue = INIT_VALUE;
while (localValue < MAX_LIMIT) {
//System.out.println(INIT_VALUE+" "+localValue);
if (localValue != INIT_VALUE) {
System.out.println(INIT_VALUE+" "+localValue);
System.out.println("The value1 READER to" + INIT_VALUE);
localValue = INIT_VALUE;
}
}
}, "READER").start();
因为在这个线程里每次都是读,没有感知到写就不去主内存拿值。
当前线程有写才会感知。在线程没有写的操作。不需要再主内存去拿。
----------------------------------------------06---------------------------------------------------
内存cache出现会加快访问的速度。
加锁的解决效率是很底的。
1.给数据总线加锁。总线(数据总线 地址总线 控制总线),加锁就是不能访问总线的加锁地址。
2.cpu高速缓存一致性协议,Internet,MESI。保证缓存的副本是一样的。
核心思想:cpu写入数据发现该变量被内存共享,就发出一个信号通知其他的cpu该变量的缓存是无效的(这个过程是要加锁的)。
当其他cpu访问该变量重新到主存获取。
-----------------------------------------------07---------------------------------------------------------
并发的三个比较重要的概念:
原子性:https://www.cnblogs.com/deepalley/p/10795389.html
有序性:
不一定是什么顺序的。
1和2可能重排序的。
中间不一定是怎么执行的,保证的是最终的一致性。
重排序的概念:重排序是单线程只要求最终的一致性。
可见性:多个线程访问这个变量一个线程访问,其他的线程可以看见。
线程1改变了还没刷新到主存 线程2就读取了。
---
补充一个特性|一致性:https://www.cnblogs.com/stone94/p/10409669.html
--------------------------------------------------08------------------------------------------------------
1.原子性:对基本数据类型的读取和赋值事保证了原子性的,要么成功,要么失败。不可被中断。
2.可见性:使用volatile保证可见性。
3.有序性:重排序满足最终一致性,再在单线程是没问题的。
happends before关系:
- 在一个线程里面比如在run方法里面,代码的执行顺序,编写在前面的发生在编写在后面的,这个指的是逻辑前后。
- 锁unlock必须发生在lock之后。
- volatile修饰的变量,对一个变量的写操作必须发生在对变量的读操作之前。
- 传递规则。
- -----------------------------------以下事废话
- 线程的启动规则,start方法肯定先于run方法。
- 线程的中断规则,中断方法先于发生捕获动作之前。
- 对象的销毁规则,对象的初始化必须发生在finalize之前。
- 所有的操作发生在线程的死亡之前。
-----------------------------------------------09------------------------------------------------------
代码:
就是volatile之前的代码一定发生在volatie代码之后的代码。同时volatile之前的代码不能拿到volatile之后。
volatile修饰的变量在读之前已经写完了。
原子性举例:
可能会有问题,可能没有问题。sleep时间小一点就有问题了。
---
这样拆是错的
有序性举例:这里第二个步骤必须放在第一个步骤之后禁止重排序的。
volatile在汇编加lock前缀相当于内存屏障。
------------------------------------------10------------------------------------------------------
使用场景:
1.
2.线程屏障前后的一致性
总结:
8.使用场景。
---------------------------------------11------------------------------------------------------