参考文章: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. python学习之——eclipse+pydev 环境搭建

    最终选用 eclipse+pydev,网上相关资料也是极多的~~~ 1.安装python: 2.安装eclipse: 3.eclipse中安装pydev,eclipse中help—>eclipl ...

  2. Python模块:struct

    各个编程语言都有自己的数据类型,当python需要接受其他语言或者网络传输来交互数据的时候,需要考虑到python的数据类型与其他平台之间交互问题.而python的struct就是解决这个问题的. s ...

  3. C# NPOI 导入与导出Excel文档 兼容xlsx, xls

    之前写了个小程序,导出一些数据成Excel,程序使用的是Microsoft.Office.Interop.Excel类来操作Excel. 在本机测试的时候都好好的,但是将生成文件放到其他电脑上却怎样也 ...

  4. java 学习写架构必会几大技术点

    java 学习写架构必会几大技术点 关于学习架构,必须会的几点技术 1. java反射技术 2. xml文件处理 3. properties属性文件处理 4. 线程安全机制 5. annocation ...

  5. ue4 Worldmachine 结合使用

    最近项目需求制作一个场景的远景部分.正好可以尝试使用一下UE4的 Landscape.不过直接在 Editor 里刷地形工作量太大,刷出的地形也不真实,最关键的是 Landscape 的工具并不是那么 ...

  6. 解决canvas转base64/jpeg时透明区域变成黑色背景的方法

    最近在工作遇到一个问题,在将png图片转jpeg时,透明区域被填充成黑色,通过网上的介绍找到了解决的方法,现在总结下分享给同样遇到这个问题的朋友们,感兴趣的可以通过本文详细学习下. 在用canvas将 ...

  7. axure设置变量值

    以登录框为例设置axure变量值 1.打开axure,打开新页面命名为login,拖入一个矩形背景,命名:登录背景图 2.拖入标签控件和输入框控件分别命名为用户名:.userName.密码:.pass ...

  8. Android安全开发之UXSS漏洞分析

    0X01 前言 XSS是我们比较熟悉的一种攻击方式,包括存储型XSS.反射型XSS.DOM XSS等,但UXSS(通用型XSS)另外一种不同的漏洞类型,主要体现在漏洞的载体和影响范围上. XSS问题源 ...

  9. 主机使用代理上网,虚拟机Linux如何连外网

    VMware虚拟机的三种联网方法及原理 一.Brigde--桥接  :默认使用VMnet0 1.原理: Bridge  桥"就是一个主机,这个机器拥有两块网卡,分别处于两个局域网中,同时在& ...

  10. 设计模型MVC和JavaBean

    六.设计模型1和设计模型2(MVC)1.模型1:JSP+JavaBean2.模型2:MVC M:Model模型 JavaBean V:视图 JSP C:控制器 Servlet 七.模型1开发一个简单的 ...