百年传承-接力棒-为什么我们在一起会更好?
友谊的本质,
其实是一种关系。
亚里士多德
两个人总比一个人好,
因为二人劳碌同得美好的果效。
所罗门 - 传道书 4:9
百年传承-接力棒-百年之后会怎样?
ed28f66e8a6a336c8c5ac38ade1d902a6532a97a25ca6edf26ba4effb8e1e8aa05fadafb80c4b64702550dba114a9b2e4c3f5bf78d966d07b45c39075babc4f020b2f8d0890aae90c93379488faf481e0095cca26055c44ba4678e3edfa74de69aaf08d4dddf1d674f224aca595d0aee476ba1807c89843504fa004e28f650952e7597d65a60b764fce17d44f39028549e446bceff72b6fee940c4f878d6ed56795012ec7b69a911bbaef914c81b0d11a330e744a7700179d63e8e2c3d5ee536c8f5713fd7f7118e0e1a6b0d000cfb19ea854f1f1ff1bae967267b05aaffe8c5861672d480cbfcd230e8ab59e2c7e94d7bfe1d90d4a45ceda ...
Java并发实现原理-多线程基础-JMM与happen-before(6)
1.为什么会存在“内存可见性”问题?要解释这个问题,就涉及现代CPU的架构。如下是x86架构下CPU缓存的布局,即在一个CPU4核心下,L1、L2、L3三级缓存与主内存的布局。每个核上面有L1、L2缓存,L3为所有核共用。
因为存在CPU缓存一致性协议,例如MESI,多个CPU之间的缓存不会出现不同步的问题,不会有“内存可见性”问题。
但是,缓存一致性协议对性能有很大损耗,为了解决这个问题,CPU的设计者们在这个基础上有进行了各种优化。例如,在计算单算和L1之间家林Store Buffer、Load Buffer(还有其他各种Buffer),如下图所示。
L1、L2、L3和主内存之间是同步的,有缓存一致性协议的保证,但是Store Buffer、Load Buffer和L1之间确实异步的。也就是说,往内存中写入一个变量,这个变量会保存在Store Buffer里面,稍后才异步地写入L1中,同时同步写入主内存中。
注意这里只是简要花了x86的CPU缓存体系,还没有进一步讨论SMP架构和NUMA的区别,还有其他CPU架构体系,例如PowerPC、MIPS、ARM等,不同CPU的缓存体系会 ...
Java并发实现原理-多线程基础-volatile关键字(5)
volatile这个关键字很不起眼,其使用场景和语义不像synchronized、wait()和notify()那么明显。正因为其隐晦,volatile关键字可能是多线程编程中被误解最多的一个。而关键字越隐晦,背后隐含的往往越复杂、越深刻。接下来将进一步由浅入深地从使用场景讨论到其底层实现。
1. 64位写入的原子性(Half Write)举一个简答的例子,对于一个long类型变量的赋值和取值操作而言,在多线程场景下,线程A调用set(100),线程调用B调用get(),在某些场景下,返回值可能不是100.
public class Example1{
private long a = 0;
// 线程A调用set(100)
public void set(long a){
this.a = a;
}
// 线程B调用get(),返回值是不是一定为100?
public void get(){
return this.a;
}
}
这有点反直觉,如此简单的一个 ...
Java并发实现原理-多线程基础-wait()与notify()(4)
1.生产者-消费者模型生产者-消费者模型是一个常见的多线程编程模型,如图所示。
一个内存队列,多个生产者线程往内存队列中放数据;多个消费者线程从内存中取数据。要实现一个这样的编程模型,需要做以下几件事。
1.内存队列本身要加锁,才能实现线程安全。
2.阻塞。当内存队列满了,生产者放不进去时,会被阻塞;当内存队列是空的时候,消费者无事可做,会被阻塞。
3.双向通知。消费者被阻塞之后,生产者放入新数据,要notify()消费者;反之,生产者被阻塞之后,消费者消费了数据,要notity()生产者。
第1件事必须做,第2和第3件事不一定要做。eg,可以采取一个简单的办法,生产者放不进去时,睡眠几百毫秒再重试,消费者取不到数据时,睡眠几百毫秒再重试。但这个办法效率低下,也不实时。所以,我们只讨论如何阻塞、如何通知的问题。
1-1.如何阻塞?方法1:线程自己阻塞自己,也就是生产者、消费者各自调用wait()和notify()。
方法2:用一个阻塞队列,当取不到或者放不进去数据的时候,入队/出队函数本身就是阻塞的。这也就是BlockingQueue的实现,后面详细讲述。
1-2.如何双向通知?方法 ...
Java并发实现原理-多线程基础-synchronized关键字(3)
1.锁的对象是什么?对于不熟悉多线程原理的人来说,很容易误解synchronized关键字:它通常加在所有的静态成员函数和非静态成员函数之前,表面看好像是“函数之间的互斥”,其实不是。synchronized关键字其实是“给某个对象加了把锁”,这个锁究竟加在了什么对象上面?如下所示,给f1()、f2()加上synchronized关键字。
class A{
public void synchronized fl(){...}
public static void synchronized f2(){...}
}
等价于如下代码:
class A{
public void f1(){
synchronized(this){...}
}
public static void f2(){
synchronized(A.class){...}
}
}
A a = new A();
a.f ...
Java并发实现原理-多线程基础-InterruptedException函数与interrupt函数(2)
1.什么情况下会考出InterruptedException?Interrupt从字面意思看貌似是说,当一个线程运行到一半,将它中断,然后会抛出InterruptedException异常,然而并不是这样。
只有声明了会抛出InterruptedException的函数才会抛出异常。如下:
public static native void sleep(long millis) throws InterruptedException{...}
public final void wait() throws InterruptedException{...}
public final void join() throws InterruptedException{...}
2.轻量级阻塞与重量级阻塞轻量级阻塞:能够被中断的阻塞,对应的线程状态是WAITTING或者TIMED_WAITTING。
重量级阻塞:不能被中断的阻塞,对应状态是BLOCKED。像synchronized这种。
如下图,是调用不同函数之后,一个线程 ...
Java并发实现原理-多线程基础-线程的优雅关闭(1)
1.stop()与destory()函数线程是“一种运行中的代码或者函数”。既然是运行中就会涉及到是一个问题,在运行中的线程是否可以强制结束/关闭?
答案是肯定不可以的。在Java中,stop()和destory()之类的函数是官方明确不建议使用的。原因是如果强制杀死线程,那么线程执行所占用的资源,eg:文件描述符、网络连接不能正常关闭。
因此,一个线程一旦运行,就不要强制去打断,合理的关闭办法是让它自然运行完(函数执行结束),干净的释放掉所有资源,然后退出。如果是一个不断循环的运行的线程,就需要用到线程的通讯机制,让主线程通知它退出。
2.守护线程在下面代码中:在main()函数中开启一个线程,不断的循环打印。请问:当main()函数退出之后,线程是否会被强制退出?整个进程是否会强制退出?
public static void main(String[] args){
System.out.println("main start...");
Thread thread1 = new Thread(()->{
while(true){
...