java线程池通讯_java 并发性和多线程 -- 读感 (二 线程间通讯,共享内存的机制)...

2.竞态条件与临界区

当多个线程访问了相同的资源,并且对这些资源做了写操作的时候,是不安全的。资源可以代表:同一内存区(变量,数组或者对象),系统(数据库,web services)或文件。

对于一个简单的加法操作     this.count = this.count + value,JVM执行指令的顺序应该是:

从内存获取 this.count 值放到寄存器

将寄存器的值添加value

将寄存器的值写会内存

如果两个线程 交叉执行,一个线程加2 一个线程加3,可能最后的结果不是5,而是2 或者3.

竞态条件:两个线程竞争同一个资源时,如果对资源访问顺序敏感,就存在竞态条件。

临界区:导致竞态条件发生的代码区成为临界区。

在临界区适当的同步可以避免竞态条件。

3.线程安全与共享资源

允许被多个线程同时执行的代码称为线程安全的代码。线程安全的代码不包含竞态条件。

1.局部变量

1.1局部基本类型变量 是存储在线程自己的栈中的,所以基础类型的局部变量是线程安全的。

1.2.局部对象引用 引用所指向的对象没有存储到线程的栈内。所有的对象都在共享堆中。

两段代码,不管是基础类型还是引用对象,它们都是局部变量,由于都没有被其他线程获取,是线程安全的。

public voidsomeMethod(){long threadSafeInt = 0;

threadSafeInt++;

}

public voidsomeMethod(){

LocalObject localObject= newLocalObject();

localObject.callMethod();

method2(localObject);

}public voidmethod2(LocalObject localObject){

localObject.setValue("value");

}

2.对象成员

对象成员是存储在堆上。如果两个线程同时更新同一个对象的同一成员,这个代码就是线程不安全的。

public classNotThreadSage{

StringBuilder builder=New StringBuilder();publicadd(String text) {this.builder.append(text);

}

}

线程控制逃逸判断

一个资源的创建,使用销毁都在同一个线程内完成,且永远不会脱离该线程的控制。

即使对象本身线程安全,但是该对象中包含的其他的资源,也许整体的应用不是线程安全的。

3.线程安全及不可变性

immutable 和 read only 的差别:当一个变量是只读的时候,变量的值不可改变,但是可以在其他变量发生改变的时候发生改变。而不变 是不会改变的。

4.java 内存模型

java内存模型规范了java虚拟机与计算机内存如何协同工作的。

cfdbaaf163041c1b651cff43d2af4015.png

每个java虚拟机的线程都拥有自己的线程栈,包括了这个线程调用的方法当前执行点的相关信息。一个线程只能访问自己的线程栈。本地变量只对当前线程可见。

3388126ee918272d48c9e930a26e9b35.png

对象是放在堆上。

每个线程都有自己的线程栈,如果是基本类型的变量,直接存放在线程栈中,如果是对象的引用,那么引用地址会放在线程栈中,而对象会在堆中,这样有可能存在两个线程同时引用相同的对象。

public classMyRunnable implements Runnable() {public voidrun() {

methodOne();

}public voidmethodOne() {int localVariable1 = 45;

MySharedObject localVariable2=MySharedObject.sharedInstance;//... do more with local variables.

methodTwo();

}public voidmethodTwo() {

Integer localVariable1= new Integer(99);//... do more with local variable.

}

}public classMySharedObject {//static variable pointing to instance of MySharedObject

public static final MySharedObject sharedInstance =

newMySharedObject();//member variables pointing to two objects on the heap

public Integer object2 = new Integer(22);public Integer object4 = new Integer(44);public long member1 = 12345;public long member1 = 67890;

}

ede2e9ab6df83069b9519d38f713ea8d.png

两个线程启动后,Object3就是 MySharedObject,而Object2,Object4 是    MySharedObject中的 object2 和 Object4.

现代硬件内存架构

0d701e92517bd1fe0b5cb6eaa109432d.png

Java内存模型和硬件内存架构之间的桥接

630e37bf15d58a17a891e5c69f6b2404.png

硬件内存架构中没有区分线程栈和堆。对于硬件所有线程栈和堆都是分布在主存中。部分线程栈和堆可能出现在CPU缓存和CPU内部的寄存器中。

当对象和变量被存放在计算机不同的内存区域中时,会有一些问题:

1.线程对共享变量修改的可见性。— 两个线程分布运行在不同的CPU上时,线程的部分变量没有刷新回主存,此时可能会导致不同步。可以使用 volatile 来避免。

2.当读,写和检查共享变量时出现race conditions。多个线程同时修改共享内存的值,如下图:

708a8e1195599e6652ced0385d0e2259.png

可以使用java同步块,这样同一时刻只能有一个线程可以进入代码的临界区。同步块还可以保证代码块中所有被访问的变量从主存中读入,当线程退出同步块时,所有被更新的变量也会被刷新回主存中,无论该变量是否被声明为volatile.

5.java 同步块

java同步块 (synchronized block) 用来标记方法或者代码块是同步的。用来避免竞争。

java同步关键字:synchronized 所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块的线程退出。

四种不同的同步块:

实例方法;静态方法;实例方法中的同步块;静态方法中的同步块。——都是方法上的同步块。

实例方法同步:

public synchronized void add(intvalue){this.count +=value;

}

每个实例其方法同步都是同步在不同的对象上。这样每个实例方法同步都同步在不同的对象上,即该方法所属的实例,只有一个线程可以在实例方法同步块中运行。一个实例一个线程。

静态方法同步:

public static synchronized void add(intvalue){

count+=value;

}

静态方法同步是指同步在该方法上所在的类对象上的。java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。不管类中的哪个静态同步方法被调用,一个类只能由一个线程同时执行。

实例方法中同步块:

public void add(intvalue){

synchronized(this){this.count +=value;

}

}

示例中使用的this 是代表的调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。

静态方法中同步块:

public classMyClass {public static synchronized voidlog1(String msg1, String msg2){

log.writeln(msg1);

log.writeln(msg2);

}public static voidlog2(String msg1, String msg2){

synchronized(MyClass.class){

log.writeln(msg1);

log.writeln(msg2);

}

}

}

两个方法不允许同时被线程访问。

如果第二个同步块不是同步在MyClass.class这个同步器上,这两个方法可以同时被线程访问。

java同步示例:

public classCounter{long count = 0;public synchronized void add(longvalue){this.count +=value;

}

}public classCounterThread extends Thread{protected Counter counter = null;publicCounterThread(Counter counter){this.counter =counter;

}public voidrun() {for(int i=0; i<10; i++){

counter.add(i);

}

}

}public classExample {public static voidmain(String[] args){

Counter counter= newCounter();

Thread threadA= newCounterThread(counter);

Thread threadB= newCounterThread(counter);

threadA.start();

threadB.start();

}

}

由于两个线程都是共用一个counter实例,所以add()被调用的时候是同步的,只有一个线程可以调用,另外一个需要等待。

public classExample {public static voidmain(String[] args){

Counter counterA= newCounter();

Counter counterB= newCounter();

Thread threadA= newCounterThread(counterA);

Thread threadB= newCounterThread(counterB);

threadA.start();

threadB.start();

}

}

这个时候两个线程就可以同时调用add()方法,因为它们分别在不同的实例中。

6.线程通信

线程通信的目的是使线程间可以互相发送信号。

方式:

1.通过共享对象通信

public classMySignal{protected boolean hasDataToProcess = false;publicsynchronized boolean hasDataToProcess(){return this.hasDataToProcess;

}public synchronized voidsetHasDataToProcess(boolean hasData){this.hasDataToProcess =hasData;

}

}

两个线程获得指向一个MySingal共享实例的引用,以便通信。同时获取变量的方法设置为同步方法,防止线程不一致。

2.忙等待(Busy Wait)

protected MySignal sharedSignal =...

...while(!sharedSignal.hasDataToProcess()){//do nothing... busy waiting

}

线程B一直在等待数据。但是感觉这里和前面获取共享变量是一个原理。

3.wait(),notify()和 notifyAll()

wait()调用后就处于非运行状态,直到另外一个线程调用了同一个对象的notify()方法。同时线程必须获取这个对象的锁才能调用。

public classMonitorObject{

}public classMyWaitNotify{

MonitorObject myMonitorObject= newMonitorObject();public voiddoWait(){

synchronized(myMonitorObject){try{

myMonitorObject.wait();

}catch(InterruptedException e){...}

}

}public voiddoNotify(){

synchronized(myMonitorObject){

myMonitorObject.notify();

}

}

}

调用这个对象的notify() 的时候,有一个wait的线程会被随机唤醒,同时也有一个notifyAll()方法来唤醒所有线程。

一旦线程调用了wait()方法,就释放了所持有的监视器对象上的锁,就允许了其他线程也可以调用wait()或者notify().同时一个线程被唤醒不是立刻就退出wait()的方法,直到调用notify()的线程退出了自己的同步块。

4.丢失信号

由于notify()和notifyAll()不会保存调用它们的方法,他们发送的信号如果在wait()之前就有可能丢失,这个时候必须把他们保存在信号类里。

public classMyWaitNotify2{

MonitorObject myMonitorObject= newMonitorObject();

boolean wasSignalled= false;public voiddoWait(){

synchronized(myMonitorObject){if(!wasSignalled){try{

myMonitorObject.wait();

}catch(InterruptedException e){...}

}//clear signal and continue running.

wasSignalled = false;

}

}public voiddoNotify(){

synchronized(myMonitorObject){

wasSignalled= true;

myMonitorObject.notify();

}

}

}

应该就是借助一个变量来记录是否调用过Notify()。

5.假唤醒

有时由于莫名其妙的原因,线程可能在没有掉用过notify()和 notifyAll()的情况下醒来。防止假唤醒,保存信号的成员变量会检查是否是自己的信号,如果不是的话,就继续wait()。

public classMyWaitNotify3{

MonitorObject myMonitorObject= newMonitorObject();

boolean wasSignalled= false;public voiddoWait(){

synchronized(myMonitorObject){while(!wasSignalled){try{

myMonitorObject.wait();

}catch(InterruptedException e){...}

}//clear signal and continue running.

wasSignalled = false;

}

}public voiddoNotify(){

synchronized(myMonitorObject){

wasSignalled= true;

myMonitorObject.notify();

}

}

}

6.多个线程等待相同信号

while 循环也可以解决当多线程在等待时,只需要唤醒一个线程,并且是使用nitifyAll()来唤醒的情况。

7.不要在字符串常量或全局对象中调用wait()

就是导致假唤醒的原因之一,并且可能会导致信号没有接收到。

管程 (Monitor)是对多个工作线程实现互斥访问共享资源的对象和模块。管程实现了在一个时间点,最多只有一个线程在执行他的某个子程序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值