`
href
  • 浏览: 7759 次
  • 性别: Icon_minigender_1
  • 来自: 福建
最近访客 更多访客>>
社区版块
存档分类
最新评论

线程

阅读更多
多线程程序设计比单线程程序设计要困难的多。所以如果一个库中的类能够帮助你从底层的多线程程序设计中解脱出来,那么一定要使用这个类。

第48条:对共享可变数据的同步访问
synchronized关键可以保证在同一个时刻,只有一个线程在执行一条语句,或者一段代码块。

同步是一种互斥的方式,即当一个对象被一个线程修改的时候,可以阻止另一个线程观察到内部不一致的状态。

同步不仅可以阻止一个线程看到对象处于不一致的状态中,还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另一种一致的状态,每一个线程进入到一个被同步的方法或者代码块的时候,它会看到由一个锁控制的以前所有状态转变的结果。当线程退出了这个被同步的区域之后,任何线程在进入到由这同一把锁同步区域时,它就可以看到由前面那个线程带来的状态转变。

java读写一个变量是原子的,除非这个变量的类型为long或者double。换句话说,读入一个非long或double类型的变量,可以保证返回的值一定是某个线程保存在该变量中,即使多个线程在没有同步的情况下并发修改这个变量,也是如此。

你可能听说过,为了提高性能,在读或者写原子数据的时候,你应该避免使用同步。这个建议是非常危险而错误的。为了线程之间可靠的通信,以及为了互斥访问,同步是需要的。

如果对一个共享变量的访问不能同步的话,其结果将是非常可怕的,即使这个变量是原子可读写的。

private static int nextSerialNumber=0;
public static int generateSerialNumber(){
  return nextSerialNumber++;
}
如果没有同步,这个方法并不正确的工作,递增操作++既要读nextSerialNumber域,也要写nextSerialNumber域,所以它不是原子的。读和写是相互独立的操作,按顺序执行。因此,多个并发的线程可能看到nextSerialNumber域中有同样的值,因而返回相同的序列号。如果没有同步机制的话,第二个线程可能根本看不到第一个线程所作的改变。

考虑如何终止一个线程。虽然java平台提供一些方法用来主动终止一个线程,但是这些方法不值得提倡使用,因为它们本质上都是不安全的,它们会导致对象被破坏。为了终止一个线程,一种推荐的做法非常简单,只要让线程轮询某个域,该域的值如果发生变化,就表明此线程应该终止自己,通常这个域是一个boolean或者一个对象引用,因为读或者写这样的域是原子操作。
pulbic class StoppableThread extends Tread{
  private boolean stopRequested=false;
  public void run(){
   boolean done=false;
   while(!stopRequested&&!done){

   }
  }
  public  void requestStop(){
   stopRequested=true;
  }
}
这段代码的问题在于,由于缺少同步,所以并不能保证这个可终止的线程将会看到其他线程对stopRequested的值所做的改变,其结果是stopRequested方法有可能完全无效。修改这个问题最直接的办法是,对stopRequested域的所有访问都加上同步特性。
pulbic class StoppableThread extends Tread{
  private boolean stopRequested=false;
  public void run(){
   boolean done=false;
   while(!stopRequeste()d&&!done){

   }
  }
  public  synchronized void requestStop(){
   stopRequested=true;
  }
  public  synchronized void stopRequeste(){
   return stopRequested;
  }
}

注意,这里每一个被同步的方法中的动作都是原子的,使用同步的唯一目的是为了通信,而不是为了通信。如果stopRequeste被声明为volatile的话,则同步可以被省略。volatile修饰符可以保证任何一个线程在读取一个域的时候将会看到最近刚刚被写入的值。
private static Foo foo=null;
public static Foo getFoo(){
  if(foo==null){
   synchronized(Foo.class){
   if(foo==null)
    foo=new Foo();
  }
  return foo;
}
}
一般情况下,双重检查模式并不能正确的工作。
private static final Foo foo=new Foo();
public static Foo getFoo(){
  return foo;
}

另外一种是使用正确的同步方法来执行迟缓的初始化。
private static Foo foo=null;
public static synchronizd Foo getFoo(){
   if(foo==null)
    foo=new Foo();
  }
  return foo;
}
这个方法可以保证正常工作,但是它会招致在每个调用上的同步开销。

如果一个静态域的初始化非常昂贵,并且它见得会被使用到,但一旦需要则会被充分使用,那么,在这样的情况下,按需初始化容器类模式非常合适的。下面是这种模式:
private static class FooHolder{
  static final Foo foo=new Foo();
}

public static Foo getFoo(){return FooHolder.foo}
该模式充分利用了只有当一个类被用到的时候它才被初始化。并且优美之处在于,getFoo并没有同步,它只执行一次域访问,所以迟缓初始化并没有引入实际的访问开销。这种模式的缺点在于,它不能用于实例域,只能用于静态域。

简而言之,无论何时当多个线程共享可变数据的时候,每个读或者写数据的线程并须获得一把锁。不要由于原子读和写而妨碍你执行正确的同步。如果没有同步,则一个线程所做的修改就无法保证被另一个线程观察到。

在某些特定的条件下,使用volatile修饰符可以提供另一种不同于普通同步机制的选择,但这是高级技术,而且内存模型尚未完成,所以适用范围不得而知。

第49条:避免过多的同步
过多的同步可能会导致性能降低,死锁,甚至不确定的行为。

为了避免死锁的危险,在一个被同步的方法或者代码块中,永远不要放弃对客户的控制。换句话说,在一个被同步的区域内部,不要调用一个可被改写的公有或者受保护的方法。

通常,在同步区域内你应该做尽可能少的工作、获得锁,检查共享数据,根据需要转换数据,然后放掉锁。如果你必须要执行某个很耗时的动作,则应该设法把这个动作移动到同步区域的外面。

要在一个类的内部进行同步,一个很好的理由是因为它将被人量的并发使用,而且通过执行内部细粒度的同步操作你可以获得很好的并发性。

如果你正在编写的类将主要被用于要求同步的环境中,同时也被用于不要同步的环境中,那么,一个合理的方式,同时提供同步的版本和未同步的版本。


如果一个类或者一个静态方法依赖于一个可变的静态域,那么它必须要在内部进行同步,即使它往往只用于单个线程。与共享实例不同,这种情况下对于客户也会执行外部同步。

简而言之,为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。更为一般的,请尽量限制同步区域内部的工作量,当你在设计一个可变类的时候,请考虑一下他们是否需要子完成同步操作,因省去同步而节省下来的开销不会很大,但也是可测量的。


第50条:永远不要在循环的外面调用wait
object.wait方法的作用是使一个线程等待某个条件,它一定是一个同步区域中被调用的,而且该同步区域锁住了被调用的对象。下面湿使用wait方法的使用标准模式:
synchronized(obj){
while(){
   obj.wait();
}
}
总是使用wait循环模式调用wait方法,永远不要在循环的外面调用wait,循环被用在等待的前后测试条件。

在等待之前测试条件,如果条件已经成立的话则跳过等待。这对于确保活性是必要的。如果条件已经成立,并且在等待之前notify方法已经被调用过,则无法保证该线程将总会从等待中醒过来。

在等待之后测试条件,如果条件不成立的话继续等待,这对于确保安全性是必要的。当条件不成立的时候,如果线程执行则可能会破坏被锁保护的约束关系。当条件不成立时,有下面理由可使一个线程醒过来:
1.另一个线程可能得到了锁,并且在一个线程调用notify的时刻,到等待线程醒过来的时刻之间,得到所的线程已经改变了被保护的状态。
2.条件并没有成立,但是另一个线程可能意外的或者恶意的调用notify。在公有可访问的对象上等待,这些类实际上把自己暴露在危险的境地中。在一个公有可访问对象的同步方法中包含的wait都会出现这样的问题。
3.通知线程在唤醒等待线程时可能会过的大方。例如,即使只有某一些等待线程的条件已经被满足,但是通知线程仍然必须调用notifyall。
4。在没有被通知的情况下等待线程也可能会醒过来。

简而言之,总是在一个while循环中调用wait,并且使用标准的模式。你没有理由不这样做,一般情况下你应该使用notifyAll优于notify。然而,在有些情况下这样会导致实质性的性能负担。如果使用notify,请一定要小心,以确保程序的活性。

第51条:不要依赖于线程调度器
任何依赖于线程调度器而达到正确性或者性能要求的程序,很有可能是不可移植的。

如果一个程序因为某些线程无法像其他的线程那样获得足够的cpu时间,而不能正常,那么不要企图通过调用Thread.yield来修正该程序。

线程优先级是java平台上最不可移植的特征了。

对于大多数程序员来说,Thread.yield的唯一用途是在测试期间认为的增加一个程序的并发性。

简而言之,不要让应用程序的正确性依赖于线程调度器。否则,结果得到的应用程序既不健壮,也不具有可移植性。作为一个推论,不要依赖Thread.yield或者线程优先级。这些设施都只是影响到调度器,它们可以被用来提高一个已经能够正常工作的系统的服务质量,但远永不应该用来修正一个原本并不能工作的程序。

第52条:线程安全性的文档化
在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是到处api的一部分。出现了synchronized修饰符并不一定表明这个方法是线程安全的,它有可能随着版本不同而发生变化。

而且,出现了synchronized关键字就足以将线程安全文档化了,这种说法隐含了一个错误观念,即认为线程安全性是一种要么全有要么全无的属性,实际上,一个类支持的线程安全性有很多级别。一个类为了可被多个线程安全的使用,必须在文档中清除的说明它所支持的线程安全性级别。

常见的线程安全级别:
1.非可变的
2、线程安全的
3.有条件的线程安全
4.线程兼容的
5.线程对立的

简而言之,每一个类都应该清楚的在文档中说明它的线程安全属性。每一个类都应该清楚的在文档中说明它的线程安全属性。要做到这一点,唯一的办法是提供子句确凿的描述。synchronized修饰符并不能成为一个类的线程安全性文档。然而,对于有条件的线程安全类,在文档中指明为了允许方法调用序列以原子方式执行,哪一个对象应被锁住,这是非常重要的。一个类的线程安全性描述通常属于这个类的文档注释,但是对于具有特殊线程安全属性的方法来说,它们应该在自己的文档注释中描述这些线程安全的属性。

第53条:避免使用线程组
线程组基本上已经过时了

总之,线程组并没有提供太多有用的功能,而且它们提供的许多功能还都是有缺陷的,我们最好把线程组看做一个不成功的实验,你可以忽略它们,就当它们不存在一样,如果你正在设计的一个类需要处理线程的逻辑组,那么,你只要把每个逻辑组中的所有Thread引用保存到一个数组或者集合中就可以了。
分享到:
评论

相关推荐

    MFC多线程 工作者线程 用户界面线程

    在Windows编程领域,MFC(Microsoft Foundation Classes)是微软提供的一套C++库,用于简化Windows应用程序的开发,包括创建用户界面和实现多线程功能。MFC中的多线程技术使得程序能够同时执行多个任务,提高应用的...

    C# 跨线程访问UI线程控件

    在C#中,由于使用线程和调用UI的线程属于两个不同的线程,如果在线程中直接设置UI元素的属性,此时就会出现跨线程错误。    下面介绍两种解决方案  第一种:使用控件自带的Invoke或者BeginInvoke方法。 Task....

    基于SpringBoot和POI实现单线程和多线程导出Excel.zip

    基于SpringBoot和POI实现单线程和多线程导出Excel.zip基于SpringBoot和POI实现单线程和多线程导出Excel.zip基于SpringBoot和POI实现单线程和多线程导出Excel.zip基于SpringBoot和POI实现单线程和多线程导出Excel.zip...

    vc++中的线程锁(线程锁保持线程同步)

    在VC++编程环境中,线程同步是一个至关重要的概念,特别是在多线程程序设计中,以确保并发执行的线程能够安全地访问共享资源,避免数据竞争和其他潜在的问题。本篇文章将详细探讨线程锁在VC++中的应用,以及如何通过...

    线程中创建子线程

    在计算机编程中,多线程是一种并发执行任务的机制,它可以极大地提高程序的执行效率,尤其是在处理大量数据或需要同时进行多个操作时。本主题主要关注如何在已有的线程中创建子线程,实现更复杂的并发执行模式。 ...

    C# 如何挂起线程、休眠线程和终止线程(源码例)

    本文将深入探讨如何挂起线程、休眠线程以及终止线程,这些都是多线程编程中的关键概念。 首先,让我们了解线程的基本概念。线程是程序执行的流程,每个进程至少有一个线程。在多线程环境中,多个线程可以共享同一...

    系统线程(内核线程)和用户线程区别 - 简书.pdf

    "系统线程(内核线程)和用户线程区别" 系统线程(内核线程)和用户线程是两种不同的线程模式,它们在实现和应用方面有很大的区别。 系统线程(内核线程)是由操作系统内核创建和撤销的线程,内核维护进程及线程的...

    MFC多线程的创建,包括工作线程和用户界面线程

    ### MFC多线程的创建详解 #### 一、MFC多线程概述 MFC (Microsoft Foundation Classes) 是微软为简化Windows程序开发提供的一套类库,它封装了Win32 API,使得开发者能够更加方便地进行Windows应用程序的开发。在...

    CVI 线程锁、线程安全变量实例

    在计算机编程领域,尤其是涉及到实时系统和并发编程时,线程锁和线程安全变量是至关重要的概念。LabWindows/CVI是一种流行的交互式C开发环境,特别适合于开发科学和工程应用。本实例将深入探讨如何在LabWindows/CVI...

    单线程与多线程的区别

    单线程和多线程是计算机程序执行时的两种不同模型,它们在处理并发任务、资源管理和性能上有着显著的差异。理解这两种模型是编程尤其是服务器端开发的基础,尤其是在Java、C#等支持多线程的编程语言中。 首先,让...

    C#内存释放-线程控制-线程启动-线程暂停

    本文将深入探讨“C#内存释放-线程控制-线程启动-线程暂停”这一主题,结合提供的WFormsThread文件,我们可以假设这是一个关于Windows Forms应用程序中线程管理的实例。 首先,让我们关注线程控制。在C#中,我们通常...

    线程异步工作,当一个线程结束时异步通知另一线程

    在多线程编程中,线程间的协作是关键任务之一,尤其当需要一个线程在完成特定工作后通知另一个线程继续执行时。这个过程通常涉及到线程同步和异步的概念。本文将深入探讨线程异步工作以及如何在C++中实现一个线程在...

    MFC创建多线程(工作者线程)demo

    本篇文章将深入探讨如何在MFC中创建多线程,特别是工作者线程,并且会针对`AfxBeginThread`和`BeginThread`两种方法进行比较。 首先,我们来看标题所提到的“MFC创建多线程(工作者线程)demo”。工作者线程通常...

    delphi7 多线程测试(40个线程)

    在编程领域,多线程是一种常见且强大的技术,它允许程序同时执行多个任务,从而提高效率和响应性。本主题聚焦于Delphi 7中实现的多线程测试,特别是涉及40个并发线程的情况。Delphi是Embarcadero开发的一款集成开发...

    大漠多线程模板_大漠_大漠多线程_

    在IT行业中,多线程是程序设计中的一个重要概念,特别是在C#编程中。"大漠多线程模板"是一个专门针对C#开发的多线程处理框架,它为开发者提供了便捷的方式来管理和优化多线程应用。这个框架由知名开发者"大漠"创建,...

    TCP-接收线程和发送线程

    在这个项目中,“TCP-接收线程和发送线程”是一个C/C++实现的多线程编程示例,旨在展示如何在服务器端和客户端之间有效地管理数据的接收和发送。以下将详细介绍相关的知识点。 首先,我们要理解TCP的基本原理。TCP...

    C#.NET多线程实例6个(包括多线程基本使用,多线程互斥等全部多线程使用实例),可直接运行

    在.NET框架中,C#语言提供了强大的多线程支持,使得开发者可以充分利用现代多核处理器的优势,实现并行处理和高效能编程。本资源包含六个C#.NET多线程的实例,涵盖了多线程的基本使用到更高级的概念,如线程互斥。...

    易语言正确退出线程

    在编程领域,线程是程序执行的基本单元,特别是在多任务操作系统中。易语言是一种中文编程环境,它提供了方便的线程操作接口。本篇将详细探讨如何在易语言中实现“正确退出线程”这一重要知识点。 首先,理解线程的...

    线程编程 四个线程...

    "多线程编程基础知识" 多线程编程是指在一个程序中同时执行多个线程的技术。每个线程都是一个独立的执行路径,拥有自己的程序计数器、寄存器和堆栈空间。多线程编程可以提高程序的执行效率和响应速度,但也增加了...

    12.1 Qt5多线程:多线程及简单实例

    在编程领域,尤其是在开发高效、响应迅速的应用程序时,多线程技术扮演着至关重要的角色。Qt5框架提供了一种方便的方式来实现多线程,它允许开发者在不同的线程中执行任务,从而避免主线程(GUI线程)因处理耗时操作...

Global site tag (gtag.js) - Google Analytics