资讯专栏INFORMATION COLUMN

并发编程一

Xufc / 3471人阅读

摘要:一并发和并行并发是同一时间应对多件事情的能力并行是同一时间做多件事情的能力。用并发的目的,不仅仅是为了让程序并行运行从而发挥多核的优势。函数式编程函数式编程日渐重要的原因之一,是其对并发编程和并行编程提供了良好的支持。

一、并发和并行:
并发是同一时间应对(dealing with)多件事情的能力;
并行是同一时间做(doing)多件事情的能力。

二、并行架构:
位级并行,32位计算机的运行速度比8位计算机更快,因为并行,对于32位数的加法,8位计算机必须进行多次8位计算,而32位计算机可以一步完成,即并行的处理32位的4字节。
指令级(instruction-level)并行,程序员通常可以不关心处理器内部并行的细节,因为尽管处理器内部的并行度很高,但是经过精心设计,从外部看上去所有处理都像是串行的。
数据级(data)并行,数据级并行(也称为“单指令多数据”,SIMD)架构,可以并行地在大量数据上施加同一操作。这并不适合解决所有问题,但在适合的场景却可以大展身手。
任务级(task-level)并行,终于来到了大家所认为的并行形式——多处理器。从程序员的角度来看,多处理器架构最明 显的分类特征是其内存模型(共享内存模型或分布式内存模型)。

对于共享内存的多处理器系统,每个处理器都能访问整个内存,处理器之间的通信主要通过内存进行。
对于分布式内存的多处理器系统,每个处理器都有自己的内存,处理器之间的通信主要通过网络进行。
用并发的目的,不仅仅是为了让程序并行运行从而发挥多核的优势。若正确使用并发,程序还将获得以下优点:及时响应、高效、容错、简单。

注意:不应该在产品代码上,使用Thread类等底层服务。

三、七个模型

1、线程与锁:线程与锁模型有很多众所周知的不足,但仍是其他模型的技术基础,也是很多并 发软件开发的首选。
2、函数式编程:函数式编程日渐重要的原因之一,是其对并发编程和并行编程提供了良好的支 持。函数式编程消除了可变状态,所以从根本上是线程安全的,而且易于并行执行。
3、Clojure之道——分离标识与状态:编程语言Clojure是一种指令式编程和函数式编程的混搭方 案,在两种编程方式上取得了微妙的平衡来发挥两者的优势。
4、actor:actor模型是一种适用性很广的并发编程模型,适用于共享内存模型和分布式内存模型, 也适合解决地理分布型问题,能提供强大的容错性。
5、通信顺序进程(Communicating Sequential Processes,CSP):表面上看,CSP模型与actor模 型很相似,两者都基于消息传递。不过CSP模型侧重于传递信息的通道,而actor模型侧重于通道 两端的实体,使用CSP模型的代码会带有明显不同的风格。
6、数据级并行:每个笔记本电脑里都藏着一台超级计算机——GPU。GPU利用了数据级并行, 不仅可以快速进行图像处理,也可以用于更广阔的领域。如果要进行有限元分析、流体力学计算 或其他的大量数字计算,GPU的性能将是不二选择。
7、Lambda架构:大数据时代的到来离不开并行——现在我们只需要增加计算资源,就能具有 处理TB级数据的能力。Lambda架构综合了MapReduce和流式处理的特点,是一种可以处理多种大数据问题的架构。

四、线程与锁:

  class Counter {
    private int count = 0;
    public synchronized void increment() {
        ++count; 
    } 
    public int getCount() {
        return count; 
    }
}

毋庸置疑,对于增加了同步功能的代码,每次执行都将得到正确结果,但代码中仍隐藏了一个bug。
潜藏的bug是:
除了increment()之外,getCount()方法 也需要进行同步。
否则,当一个线程对值的修改没有及时更新到主内存,从而导致
调用getCount()的线程可能获得一个失效的值。
解释:
Java内存模型定义了何时一个线程对内存的修改对另一个线程可见。
基本原则是,如果读 线程和写线程不进行同步,就不能保证可见性。
然而两个线程都需要进行同步。只在其中一个线程进行同步是不够的,

竞态条件:
  计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。
    
    1、乱序执行。执行依赖于检测的结果,而检测结果依赖于多个线程的执行时序。
    乱序原因:
     编译器的静态优化可以打乱代码的执行顺序;
     JVM的动态优化也会打乱代码的执行顺序; 
     硬件可以通过乱序执行来优化其性能。
    

所以在多线程环境下,对一个文件的操作需要加锁。

2、延迟初始化:
线程A和线程B同时执行getInstance,可能会取到两个实例对象,主要看线程执行时序了。

public class ObjFactory {  
    private Obj instance;  
      
    public Obj getInstance(){  
        if(instance == null){  
            instance = new Obj();  
        }  
        return instance;  
    }  
}

五、来自外星方法的危害

规模较大的程序常用监听器模式(listener)来解耦模块。
在这里,我们构造一个类从一个URL 进行下载,并用ProgressListeners监听下载的进度。
 
 public class Downloader extends Thread {

private InputStream in;
private OutputStream out;
private ArrayList listeners;

public Downloader(URL url,String outputFilename) throws IOException {
    in=url.openConnection().getInputStream();
    out = new FileOutputStream(outputFilename);
    listeners=new ArrayList();
}

public synchronized void addListener(ProgressListener listener){
    listeners.add(listener);
}
public synchronized boolean remove(ProgressListener listener){
   return listeners.remove(listener);
}

/***
 * 来自外星方法的危害
 *
 * addListener()、removeListener()和updateProgress()都是同步方法,
 * 多线程可以安全地使用这些方法。尽管这段代码仅使用了一把锁,但仍隐藏着一个死锁陷阱。
 *
 * 陷阱在于updateProgress()调用了一个外星方法——但对于这个外星方法一无所知。外星方法可以做任何事情,
 * 例如持有另外一把锁。这样一来,我们就在对加锁顺序一无所知的情况下使用了两把锁。就像前面提到的,这就有可能发生死锁。
 *
 * @param n
 */
private synchronized void updateProgress(int n){
    for(ProgressListener listener:listeners){
        listener.onProgress(n);
    }
}

/***
 *  一种方法是在遍历之前对listeners进行保 护性复制(defensive copy),
 *  再针对这份副本进行遍历
 *  这是个一石多鸟的方法。不仅在调用外星方法时不用加锁,而且大大减少了代码持有锁的时间。
 *  长时间地持有锁将影响性能(降低了程序的并发度),也会增加死锁的可能。
 * @param n
 */
private void updateProgress2(int n){
        ArrayList listenersCopy=null;
        synchronized (this){
            listenersCopy=(ArrayList )listeners.clone();
        }
        for(ProgressListener listener:listenersCopy){
            listener.onProgress(n);
        }


    }

    @Override
    public void run(){
        int n = 0, total = 0;
        byte[] buffer = new byte[1024];
        try {
            while((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
                total += n;
                updateProgress(total);
            }
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}




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

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

相关文章

  • 使用 Python 进行并发编程系列 - 收藏集 - 掘金

    摘要:使用进行并发编程篇三掘金这是使用进行并发编程系列的最后一篇。所以我考虑启用一个本地使用进行并发编程篇二掘金我们今天继续深入学习。 使用 Python 进行并发编程 - asyncio 篇 (三) - 掘金 这是「使用Python进行并发编程」系列的最后一篇。我特意地把它安排在了16年最后一天。 重新实验上篇的效率对比的实现 在第一篇我们曾经对比并发执行的效率,但是请求的是httpb...

    MorePainMoreGain 评论0 收藏0
  • 并发编程 - 探索

    摘要:并发表示在一段时间内有多个动作存在。并发带来的问题在享受并发编程带来的高性能高吞吐量的同时,也会因为并发编程带来一些意想不到弊端。并发过程中多线程之间的切换调度,上下文的保存恢复等都会带来额外的线程切换开销。 0x01 什么是并发 要理解并发首选我们来区分下并发和并行的概念。 并发:表示在一段时间内有多个动作存在。 并行:表示在同一时间点有多个动作同时存在。 例如:此刻我正在写博客,但...

    pcChao 评论0 收藏0
  • 多线程编程完全指南

    摘要:在这个范围广大的并发技术领域当中多线程编程可以说是基础和核心,大多数抽象并发问题的构思与解决都是基于多线程模型来进行的。一般来说,多线程程序会面临三类问题正确性问题效率问题死锁问题。 多线程编程或者说范围更大的并发编程是一种非常复杂且容易出错的编程方式,但是我们为什么还要冒着风险艰辛地学习各种多线程编程技术、解决各种并发问题呢? 因为并发是整个分布式集群的基础,通过分布式集群不仅可以大...

    mengera88 评论0 收藏0
  • Java多线程学习(七)并发编程些问题

    摘要:因为多线程竞争锁时会引起上下文切换。减少线程的使用。举个例子如果说服务器的带宽只有,某个资源的下载速度是,系统启动个线程下载该资源并不会导致下载速度编程,所以在并发编程时,需要考虑这些资源的限制。 最近私下做一项目,一bug几日未解决,总惶恐。一日顿悟,bug不可怕,怕的是项目不存在bug,与其惧怕,何不与其刚正面。 系列文章传送门: Java多线程学习(一)Java多线程入门 Jav...

    yimo 评论0 收藏0
  • 并发编程并发编程的挑战

    摘要:关于并发编程,其目的就是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序更大限度的并发执行。对于软件资源限制考虑使用资源池将资源复用,例如数据库连接池等资源限制情况下进行并发编程根据不同的资源限制调整程序的并发度。 关于并发编程,其目的就是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序更大限度的并发执行。有哪些影响并发编程的因素呢? 一、文章导图 showImg(...

    LeanCloud 评论0 收藏0
  • Java多线程学习(七)并发编程些问题

    摘要:相比与其他操作系统包括其他类系统有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。因为多线程竞争锁时会引起上下文切换。减少线程的使用。很多编程语言中都有协程。所以如何避免死锁的产生,在我们使用并发编程时至关重要。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)syn...

    dingding199389 评论0 收藏0

发表评论

0条评论

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