资讯专栏INFORMATION COLUMN

并发学习笔记(1)

objc94 / 3293人阅读

摘要:共享数据使线程之间的通信比进程之间的通信更有效。并发模型和的区别说明的作用是启动一个新线程操作系统级别,有一个方法启动新线程,新线程会执行相应的方法。多带带调用会在当前线程中执行并不会启动新线程创建一个线程即可但是这个线程没有执行任何代码段。

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 来判断是否代码访问的资源是线程安全的。

  

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65302.html

相关文章

  • Java 并发学习笔记(二)

    摘要:请参看前一篇文章并发学习笔记一原子性可见性有序性问题六等待通知机制什么是等待通知机制当线程不满足某个条件,则进入等待状态如果线程满足要求的某个条件后,则通知等待的线程重新执行。经极客时间并发编程实战专栏内容学习整理 请参看前一篇文章:Java 并发学习笔记(一)——原子性、可见性、有序性问题 六、等待—通知机制 什么是等待通知—机制?当线程不满足某个条件,则进入等待状态;如果线程满足要...

    zgbgx 评论0 收藏0
  • Java 并发学习笔记(一)——原子性、可见性、有序性问题

    摘要:最后,总结一下,导致并发问题的三个源头分别是原子性一个线程在执行的过程当中不被中断。可见性一个线程修改了共享变量,另一个线程能够马上看到,就叫做可见性。 计算机的 CPU、内存、I/O 设备的速度一直存在较大的差异,依次是 CPU > 内存 > I/O 设备,为了权衡这三者的速度差异,主要提出了三种解决办法: CPU 增加了缓存,均衡和内存的速度差异 发明了进程、线程,分时复用 CP...

    Chao 评论0 收藏0
  • 并发学习笔记 (4)

    摘要:不剥夺条件进程已获得的资源,在末使用完之前,不能强行剥夺。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。按照顺序加锁是一种有效的死锁预防机制。这种机制存在一个问题,在中不能对同步块设置超时时间。 [tutorial site][1] 死锁 deadlock 死锁是指两个或两个以上的进程在执行过程中,因竞争资源而造成的一种互相等待的现在,若无外力作用,它们都无法推...

    shiguibiao 评论0 收藏0
  • 并发学习笔记 (6)

    摘要:每个通过网络到达服务器的连接都被包装成一个任务并且传递给线程池。线程池的线程会并发的处理连接上的请求。用线程池控制线程数量,其他线程排队等候。实现包,线程池顶级接口是但是严格意义讲并不是一个线程。此线程池支持定时以及周期性执行任务的需求。 tutorial site1tutorial site2 一个问题: 每启动一个新线程都会有相应的性能开销(涉及到OS的交互:创建线程,销毁线程...

    superw 评论0 收藏0
  • Go语言核心36讲(Go语言实战与应用十二)--学习笔记

    摘要:除此之外,把并发安全字典封装在一个结构体类型中,往往是一个很好的选择。请看下面的代码如上所示,我编写了一个名为的结构体类型,它代表了键类型为值类型为的并发安全字典。在这个结构体类型中,只有一个类型的字段。34 | 并发安全字典sync.Map (上)我们今天再来讲一个并发安全的高级数据结构:sync.Map。众所周知,Go 语言自带的字典类型map并不是并发安全的。前导知识:并发安全字典诞生...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<