并发学习笔记(1)

tutorials site

并发Concurrency发展的历史

     单CPU,一次只能运行一个程序 -- 多任务,一次同时运行多个任务 (问题是每个任务不能永远占有资源或者CPU,不再使用资源或者CPU的程序需要释放掉自己的资源) -- 多线程,每个任务都有多线程进行处理,每个执行着的线程都可以当做是一个CPU。(问题是 每个线程都执行相同的任务,因此同时读和写同一段内存,这会导致单线程不能出现的问题)
     举个例子: 如果一个线程读了一块内存 同时发生了另一个线程写到了这块内存。那么第一个线程读到的是什么? old value or the value just wriiten or a value mid between the two. 如果更多的线程同时写到了这个内存,第一个线程读到什么。
     Therefore it is important as a developer to know how to take the right precautions - meaning learning to control how threads access shared resources like memory, files, databases etc. That is one of the topics this Java concurrency tutorial addresses.

多进程和多线程的区别?
本质的区别在于每个进程拥有自己的一整套变量,而线程则共享数据。共享数据使线程之间的通信比进程之间的通信更有效。

此外在有些操作系统中,与进程相比较,线程更加轻量级,创建,撤销一个线程比启动线程开销要小很多。

栈的组成

JVM 的内存模型 可以分为调用栈,堆,方法区,寄存器,本地方法栈;其中主要组成是前二。
图片描述
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)

每一个在JVM中运行的线程都有自己的调用栈call stack, 调用栈存储着该线程调用了哪些方法以及这些方法的局部变量。每个栈只能查看自己的局部变量,无法查看其它栈的局部变量。

Objects on the heap can be accessed by all threads that have a reference to the object. When a thread has access to an object, it can also get access to that object's member variables. If two threads call a method on the same object at the same time, they will both have access to the object's member variables, but each thread will have its own copy of the local variables.

图片描述
图片描述

 advantage and disadvantage

使用并发的好处:

* 更好的资源利用率
* 更精简的程序设计(一个线程负责读,一个线程负责写,一个线程只做一个功能,这样程序设计精简多了)
* 更多响应的程序 (服务器监听线程,处理线程的例子)

使用并发的代价:

* 设计更复杂

Code executed by multiple threads accessing shared data need special attention. Thread interaction is far from always simple. Errors arising from incorrect thread synchronization can be very hard to detect, reproduce and fix.

* 上下文切换

When a CPU switches from executing one thread to executing another, the CPU needs to save the local data, program pointer etc. of the current thread, and load the local data, program pointer etc. of the next thread to execute. This switch is called a "context switch".

* 消耗资源

CPU需要额外的空间时间去维护一个线程。

并发模型

Creating and Starting java threads

start 和 run的区别说明

* start() 的作用是 启动一个新线程(操作系统级别,有一个native方法start0() 启动新线程),新线程会执行相应的run方法。
* run() 和普通的成员方法一样,可以被重复调用。 单独调用run() 会在当前线程中执行run() 并不会启动新线程

创建一个线程Thread thread = new Thread(); thread.start() 即可
但是这个线程没有执行任何代码段。

有两种方式可以指定哪段代码 一个线程会执行。

  • 继承Thread - Thread Subclass
java  public class MyThread extends Thread {

    public void run(){
       System.out.println("MyThread running");
    }
  }

To create and start the above thread you can do like this:

java  MyThread myThread = new MyThread();
  myTread.start();
  • 覆盖run方法 - Runnable Interface Implemention
    第二种方式指定线程应该运行那端代码 是创建一个类执行java.lang.Runnable接口
  public class MyRunnable implements Runnable {

    public void run(){
       System.out.println("MyRunnable running");
    }
  }

To have the run() method executed by a thread, pass an instance of MyRunnable to a Thread in its constructor. Here is how that is done:

Thread thread = new Thread(new MyRunnable());
thread.start();

Race Conditions and Critical Sections

The problems arise when multiple threads access the same resources.For instance the same memory (variables, arrays, or objects), systems (databases, web services etc.) or files. In fact, problems only arise if one or more of the threads write to these resources. It is safe to let multiple threads read the same resources, as long as the resources do not change.

The situation where two threads compete for the same resource, where the sequence in which the resource is accessed is significant, is called race conditions. A code section that leads to race conditions is called a critical section. In the previous example the method add() is a critical section, leading to race conditions. Race conditions can be avoided by proper thread synchronization in critical sections.

Thread Safety and Shared Resources

Code that is safe to call by multiple threads simultanously is called thread safe. If a piece of code is thread safe, then it contains no race conditions. Race condition only occur when multiple threads update shared resources. Therefore it is important to know what resources Java threads share when executing.

Java 不共享的资源有:

  • 局部变量 Local varables
    局部变量(方法内部变量)存储在每个线程自己的栈里面,这意味着局部变量不会被多个线程共享。这也意味着所有局部变量都是线程安全的thread safe.
    比如:
public void someMethod(){
  long threadSafeInt = 0;
  threadSafeInt++;
}

Java 共享的资源有:

  • 局部对象引用 Local Object References
    引用本身是线程安全的,因为他的局部变量。但是引用所☞指的对象object并非存储在线程的局部栈的,而是存储在共享堆里 shared heap。 每个线程在各自的方法内创建的局部对象,只要不作为返回值返回,其他线程访问不到,不产生竞争就不会有安全问题

比如如下的例子,因为localObject没有作为返回值返回,其他的线程获取不到这个对象的

javapublic void someMethod(){
  LocalObject localObject = new LocalObject();
  localObject.callMethod();
  method2(localObject);
}

public void method2(LocalObject localObject){
  localObject.setValue("value");
}

  • 对象成员变量
    对象成员与对象一起存储在堆里面,对象成员是属于类的,局部对象引用是属于方法的,不同的线程访问或者修改一个类的成员时就会存在竞争.

比如这个例子中,方法add就是线程不安全的(因为build是对象成员变量,多线程都对它同时操作)。

public class NotThreadSafe{
  StringBuilder builder = new StringBuilder();
  public add(String text){
    this.builder.append(text);
  }
}

  如果两个线程同时调用同一个 NotThreadSafe 实例的 add() 方法就会引起race condition。比如:

NotThreadSafe sharedInstance = new NotThreadSafe();
new Thread(new MyRunnable(sharedInstance)).start(); // 线程1
new Thread(new MyRunnable(sharedInstance)).start(); // 线程2
public class MyRunnable implements Runnable{
  NotThreadSafe instance = null;
  public MyRunnable(NotThreadSafe instance){
    this.instance = instance;
  }
  public void run(){
    this.instance.add("some text");
  }
}

然而如果两个线程在不同的实例上面同时调用 add() 方法并不会引起静态条件。下面是稍微修改之后的例子:

new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();

  现在这两个线程都有自己的 NotThreadSafe 实例,所以它们对 add 方法的调用并不会妨碍对方,这段代码没有竞态条件。所以即使一个对象不是线程安全的,仍可以找到一个方式来消除竞态条件。 
可以使用线程逃逸准则 Thread Control Escape Rule 来判断是否代码访问的资源是线程安全的。

如果一个资源在一个线程的控制下被创建、使用和销毁并且永远不会逃脱线程的控制,则该资源的使用是线程安全的。

阅读 2.5k

推荐阅读
我们俩
用户专栏

学习笔记 ,分享与反思。互联网搬砖工

86 人关注
74 篇文章
专栏主页