参考文章:http://ifeve.com/java-concurrency-thread-directory/

其中的竞态,线程安全,内存模型,线程间的通信,java ThreadLocal类小节部分内容。

  • 1.目录略览
     线程的基本概念:介绍线程的优点,代价,并发编程的模型。如何创建运行java 线程。
     线程间通讯,共享内存的机制:竞态条件与临界区,线程安全和共享资源与不可变性。java内存模型,线程间的通信,java ThreadLocal类,线程信号
     死锁相关,资源竞争相关:死锁,如何避免死锁,饥饿和公平,嵌套管程锁死,Slipped conditions(从一个线程检查某一特定条件到该线程操作此条件期间,这个条件已经被其它线程改变,导致第一个线程在该条件上执行了错误的操作),锁,读锁和写锁,重入写死,信号量,阻塞队列,线程池,CAS(compare and swap 理论),同步器,无阻塞算法,阿姆达尔定律(计算处理器平行运算之后效率提升的能力)。 
 

  • 2.竞态条件与临界区
     当多个线程访问了相同的资源,并且对这些资源做了写操作的时候,是不安全的。资源可以代表:同一内存区(变量,数组或者对象),系统(数据库,web services)或文件。
     对于一个简单的加法操作     this.count = this.count + value,JVM执行指令的顺序应该是:
     从内存获取 this.count 值放到寄存器
     将寄存器的值添加value    
     将寄存器的值写会内存
     如果两个线程 交叉执行,一个线程加2 一个线程加3,可能最后的结果不是5,而是2 或者3.
     竞态条件:两个线程竞争同一个资源时,如果对资源访问顺序敏感,就存在竞态条件。
     临界区:导致竞态条件发生的代码区成为临界区。
     在临界区适当的同步可以避免竞态条件。  
  • 3.线程安全与共享资源
     允许被多个线程同时执行的代码称为线程安全的代码。线程安全的代码不包含竞态条件。
     1.局部变量
     1.1局部基本类型变量 是存储在线程自己的栈中的,所以基础类型的局部变量是线程安全的。
     1.2.局部对象引用 引用所指向的对象没有存储到线程的栈内。所有的对象都在共享堆中。
     两段代码,不管是基础类型还是引用对象,它们都是局部变量,由于都没有被其他线程获取,是线程安全的。
public void someMethod(){
long threadSafeInt = ;
threadSafeInt++;
}
public void someMethod(){
LocalObject localObject = new LocalObject();
localObject.callMethod();
method2(localObject);
} public void method2(LocalObject localObject){
localObject.setValue("value");
}
     2.对象成员
     对象成员是存储在堆上。如果两个线程同时更新同一个对象的同一成员,这个代码就是线程不安全的。
public class NotThreadSage{
StringBuilder builder = New StringBuilder();
public add(String text) {
this.builder.append(text);
}
}

      线程控制逃逸判断

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

           即使对象本身线程安全,但是该对象中包含的其他的资源,也许整体的应用不是线程安全的。
      3.线程安全及不可变性
     immutable 和 read only 的差别:当一个变量是只读的时候,变量的值不可改变,但是可以在其他变量发生改变的时候发生改变。而不变 是不会改变的。
  • 4.java 内存模型
     java内存模型规范了java虚拟机与计算机内存如何协同工作的。
      

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

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

public class MyRunnable implements Runnable() {

    public void run() {
methodOne();
} public void methodOne() {
int localVariable1 = ; MySharedObject localVariable2 =
MySharedObject.sharedInstance; //... do more with local variables. methodTwo();
} public void methodTwo() {
Integer localVariable1 = new Integer(); //... do more with local variable.
}
} public class MySharedObject { //static variable pointing to instance of MySharedObject public static final MySharedObject sharedInstance =
new MySharedObject(); //member variables pointing to two objects on the heap public Integer object2 = new Integer();
public Integer object4 = new Integer(); public long member1 = ;
public long member1 = ;
}
 
两个线程启动后,Object3就是 MySharedObject,而Object2,Object4 是    MySharedObject中的 object2 和 Object4.
     现代硬件内存架构
     

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

          硬件内存架构中没有区分线程栈和堆。对于硬件所有线程栈和堆都是分布在主存中。部分线程栈和堆可能出现在CPU缓存和CPU内部的寄存器中。
          当对象和变量被存放在计算机不同的内存区域中时,会有一些问题:
          1.线程对共享变量修改的可见性。— 两个线程分布运行在不同的CPU上时,线程的部分变量没有刷新回主存,此时可能会导致不同步。可以使用 volatile 来避免。
          2.当读,写和检查共享变量时出现race conditions。多个线程同时修改共享内存的值,如下图:
         

          可以使用java同步块,这样同一时刻只能有一个线程可以进入代码的临界区。同步块还可以保证代码块中所有被访问的变量从主存中读入,当线程退出同步块时,所有被更新的变量也会被刷新回主存中,无论该变量是否被声明为volatile.
          5.java 同步块
          java同步块 (synchronized block) 用来标记方法或者代码块是同步的。用来避免竞争。
          java同步关键字:synchronized 所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块的线程退出。     
          四种不同的同步块:
          实例方法;静态方法;实例方法中的同步块;静态方法中的同步块。——都是方法上的同步块。     
          实例方法同步:
  public synchronized void add(int value){
this.count += value;
}
       每个实例其方法同步都是同步在不同的对象上。这样每个实例方法同步都同步在不同的对象上,即该方法所属的实例,只有一个线程可以在实例方法同步块中运行。一个实例一个线程。
          静态方法同步:
public static synchronized void add(int value){
count += value;
}
          静态方法同步是指同步在该方法上所在的类对象上的。java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。不管类中的哪个静态同步方法被调用,一个类只能由一个线程同时执行。          
          实例方法中同步块:
  public void add(int value){
synchronized(this){
this.count += value;
}
}

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

          静态方法中同步块:
public class MyClass {

    public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
} public static void log2(String msg1, String msg2){
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
          两个方法不允许同时被线程访问。
          如果第二个同步块不是同步在MyClass.class这个同步器上,这两个方法可以同时被线程访问。
 
          java同步示例:

 public class Counter{

     long count = ;

     public synchronized void add(long value){
this.count += value;
}
} public class CounterThread extends Thread{ protected Counter counter = null; public CounterThread(Counter counter){
this.counter = counter;
} public void run() {
for(int i=; i<; i++){
counter.add(i);
}
}
}
public class Example { public static void main(String[] args){
Counter counter = new Counter();
Thread threadA = new CounterThread(counter);
Thread threadB = new CounterThread(counter); threadA.start();
threadB.start();
}
}
 
          由于两个线程都是共用一个counter实例,所以add()被调用的时候是同步的,只有一个线程可以调用,另外一个需要等待。
  public class Example {

    public static void main(String[] args){
Counter counterA = new Counter();
Counter counterB = new Counter();
Thread threadA = new CounterThread(counterA);
Thread threadB = new CounterThread(counterB); threadA.start();
threadB.start();
}
}
   这个时候两个线程就可以同时调用add()方法,因为它们分别在不同的实例中。
 
  • 6.线程通信
     线程通信的目的是使线程间可以互相发送信号。
     方式:
     1.通过共享对象通信

public class MySignal{

  protected boolean hasDataToProcess = false;

  public synchronized boolean hasDataToProcess(){
return this.hasDataToProcess;
} public synchronized void setHasDataToProcess(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 class MonitorObject{
} public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){
synchronized(myMonitorObject){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
} public void doNotify(){
synchronized(myMonitorObject){
myMonitorObject.notify();
}
}
}
     调用这个对象的notify() 的时候,有一个wait的线程会被随机唤醒,同时也有一个notifyAll()方法来唤醒所有线程。
     一旦线程调用了wait()方法,就释放了所持有的监视器对象上的锁,就允许了其他线程也可以调用wait()或者notify().同时一个线程被唤醒不是立刻就退出wait()的方法,直到调用notify()的线程退出了自己的同步块。
     4.丢失信号
          由于notify()和notifyAll()不会保存调用它们的方法,他们发送的信号如果在wait()之前就有可能丢失,这个时候必须把他们保存在信号类里。
  

public class MyWaitNotify2{

  MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false; public void doWait(){
synchronized(myMonitorObject){
if(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
} public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
     应该就是借助一个变量来记录是否调用过Notify()。
 
  5.假唤醒 
          有时由于莫名其妙的原因,线程可能在没有掉用过notify()和 notifyAll()的情况下醒来。防止假唤醒,保存信号的成员变量会检查是否是自己的信号,如果不是的话,就继续wait()。

public class MyWaitNotify3{

  MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false; public void doWait(){
synchronized(myMonitorObject){
while(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
} public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
  6.多个线程等待相同信号
              while 循环也可以解决当多线程在等待时,只需要唤醒一个线程,并且是使用nitifyAll()来唤醒的情况。
  7.不要在字符串常量或全局对象中调用wait()
               就是导致假唤醒的原因之一,并且可能会导致信号没有接收到。  
          管程 (Monitor)是对多个工作线程实现互斥访问共享资源的对象和模块。管程实现了在一个时间点,最多只有一个线程在执行他的某个子程序。
 
  • 6 Java ThreadLocal
          java中的ThreadLocal 可以让变量只被同一个线程进行读和写操作。
          创建:
           private ThreadLocal myThreadLocal = new ThreadLocal()
           访问:
            myThreadLocal.set(“local value”);
            String threadLocalValue = (String) myThreadLocal.get();
          如果不想用强制类型转换,可以创建一个泛型化的ThreadLocal对象。
          private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();

java 并发性和多线程 -- 读感 (二 线程间通讯,共享内存的机制)的更多相关文章

  1. 【转】JAVA 并发性和多线程 -- 读感 (二 线程间通讯,共享内存的机制)

    原文地址:https://www.cnblogs.com/edenpans/p/6020113.html 参考文章:http://ifeve.com/java-concurrency-thread-d ...

  2. java 并发性和多线程 -- 读感 (一 线程的基本概念部分)

    1.目录略览      线程的基本概念:介绍线程的优点,代价,并发编程的模型.如何创建运行java 线程.      线程间通讯的机制:竞态条件与临界区,线程安全和共享资源与不可变性.java内存模型 ...

  3. Java 并发和多线程(一) Java并发性和多线程介绍[转]

    作者:Jakob Jenkov 译者:Simon-SZ  校对:方腾飞 http://tutorials.jenkov.com/java-concurrency/index.html 在过去单CPU时 ...

  4. Java并发性和多线程

    Java并发性和多线程介绍   java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题, ...

  5. Java并发性和多线程介绍

    java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题,多开线程就好: 快速响应,异步式 ...

  6. Java高级教程:Java并发性和多线程

    Java并发性和多线程: (中文,属于人工翻译,高质量):http://ifeve.com/java-concurrency-thread-directory/ (英文):http://tutoria ...

  7. 29、Java并发性和多线程-非阻塞算法

    以下内容转自http://ifeve.com/non-blocking-algorithms/: 在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法.在绝大多数项目中,在 ...

  8. Java:多线程<三>死锁、线程间通讯

    死锁: 同步嵌套同步,而且使用的锁不是同一把锁时就可能出现死锁 class Test implements Runnable { private boolean flag; Test(boolean ...

  9. 22、Java并发性和多线程-Java中的读/写锁

    以下内容转自http://ifeve.com/read-write-locks/: 相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些.假设你的程序中涉及到对一些共享资源 ...

随机推荐

  1. 16.10.16学到的JAVA知识

    1. 每个字节就是八位,所以每个字节的取值范围是  -128~127,它可以保存一个英文字符,包括字母,数字和英文标点.而汉字的的数量很多,一个字节没法把所有的汉字表达出来,所以汉字就是用两个字节来存 ...

  2. Hishop网站迁移后出现DataProtectionConfigurationProvider错误

    错误代码如下: 配置错误 说明: 在处理向该请求提供服务所需的配置文件时出错.请检查下面的特定错误详细信息并适当地修改配置文件. 分析器错误信息: 未能使用提供程序“DataProtectionCon ...

  3. C# log4net输出发生错误的行号

    别人调用我写的接口,总是报错,但我这测试是没问题的,就想着用log4net来跟踪一下. 跟踪后,发现接口确实有出错的日志,但是没有具体出错的地方. 通过输出日志的方式,跟踪不是很方便,就想着log4n ...

  4. android BluetoothLE 多个 setCharacteristicNotification writeCharacteristic 失效

    如果在搜索完服务后,执行多个 setCharacteristicNotification 或 writeCharacteristic 操作,某些操作可能会无效.需要在中间等待一些时间,真是一个大坑! ...

  5. java虚拟机运行时乱码问题

    问题: Android端通过socket发送文本到windows,windows调用系统剪切板进行粘贴的操作,java服务端在eclipse下直接运行粘贴的文本无乱码,打包jar后粘贴的文本乱码. 解 ...

  6. C++的隐式类型转换

    C++是一种复杂的语言,其中有许多“好玩”的特性,学习C++的过程就像在海边捡一颗颗石头,只要坚持不懈,也许一颗颗小石头也能建起你自己小小的城堡. 废话完后,讲讲自己捡到的石头:隐式类型转换 学习出处 ...

  7. 您试图在此 Web 服务器上访问的 Web 应用程序当前不可用

    错误提示: 服务器应用程序不可用 您试图在此 Web 服务器上访问的 Web 应用程序当前不可用.请点击 Web 浏览器中的“刷新”按钮重试您的请求. 管理员注意事项: 详述此特定请求失败原因的错误信 ...

  8. css 3 动画

    1.transition,其作用是:平滑的改变CSS的值.无论是点击事件,焦点事件,还是鼠标hover,只要值改变了,就是平滑的,就是动画. transition 属性介绍: transition-p ...

  9. Ajax Step By Step5

    第五.[表单序列化] Ajax 用的最多的地方莫过于表单操作,而传统的表单操作是通过 submit 提交将数据传 输到服务器端.如果使用 Ajax 异步处理的话,我们需要将每个表单元素逐个获取才方能提 ...

  10. TCP/IP入门(3) --传输层

    原文:http://blog.csdn.net/zjf280441589/article/category/1854365 传输层的主要功能 1)传输层为应用进程之间提供端到端的逻辑通信(网络层是为主 ...