笔记《Java并发编程实战》[1]

Java并发编程实践》笔记

1)父线程VS子线程:JVM要等到所有用户线程都结束后才会终止,或者调用System.exit()方法强制终止JVM。如果只有守护线程在运行,则JVM将会自动终止。java中,父线程和子线程只是在运行时谁创建谁的关系。一旦线程被启动,这2个线程平等的没有父子关系,父线程一般结束不会影响子线程的运行。

 

2)线程会共享进程范围内的资源

======================================================================

线程的优势:

1)发挥多处理器的强大功能

2)建模的简单性

如果在程序中只包含一种类型的任务,那么比包含多种不同类型任务的程序要更易于变现,错误更少,也更容易测试。如果为模型中每种类型的任务都分配一个专门的线程,那么可以形成一种串行执行的假象,并将程序的执行逻辑与[调度机制的细节、交替执行的操作、异步I/O、资源等待等问题]分离开来。通过使用线程,可以将负责并且异步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。【典型例子:Servlet

3)异步事件的简化处理

4)响应更灵敏的用户界面。【典型例子:Java里的AWTSwing工具,采用一个事件分发线程代替主事件循环】

 

线程带来的风险:

1)安全性问题:多个线程共享相同的内存地址空间

2)活跃性问题:如何避免死锁、饥饿、活锁。。。,依赖不同线程的事件发生时序

3)性能问题:线程调度、同步机制带来的性能开销

 

线程无处不在:

       每个Java应用程序都会使用线程。当JVM启动时,它将为JVM的内部任务(例如:垃圾收集、终结操作等)创建后台线程,并创建一个主线程来运行main方法。AWTSwingUI框架将创建线程来管理用户界面事件。Timer将创建线程来执行延迟任务。一些组件框架,例如ServletRMI,都会创建线程并调用这些线程中的方法。

======================================================================

线程安全性:

1)要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变状态的访问。(共享且可变)【三种方法:①不在线程之间共享该状态变量;②将状态变量修改为不可变的变量;③在访问状态变量时使用同步】

2Java中的同步机制(多个线程中指令执行前后顺序)synchronized(内置互斥独占加锁)|Object的wait|notifyvolatile类型的变量、显式锁|Condition、原子变量(java.util.concurrent.atomic实现在数值和对象引用上的原子状态转换|CAS)、(静态初始化器由JVM在类的初始化阶段执行,由于在JVM内部存在同步机制)。

3类是线程安全的:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协助,这个类都能表现出正确地行为。——保证不变性条件不被破坏。

======================================================================

原子、加锁、可见(提交)

3.5)共享数据可见性:多线程安全根本问题所在,多个线程指令执行顺序和多级缓存造成各处变量不一致。所以①从执行顺序开始处理:Java同步机制synchronized、Object的wait|notify、volatile、AbstractQueueSynchronized,其底层是Java内存模型lock—read—load—use—assign—store—write—unlock的各种约定和原则。②是CAS非阻塞,类似一种网络协议[忘了],一直测试直到不产生碰撞冲突后才继续操作。③是ThreadLocal造成不共享。④不变性,使变量不可变就不存在可见性问题。

4竞态条件:由于不恰当的执行时序而出现不正确的结果。【典型例子:先检查后执行】

5复合操作:将“先检查后执行”以及“读取-修改-写入”等操作的统称:包含一组必须以原子方式执行的操作以确保线程安全性。  原子性。

6)要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。

8重入:获取锁的操作的粒度是“线程”,而不是“调用”——如果某个线程试图获得一个意见由它自己持有的锁,那么这个请求就会成功。【内置锁synchronized是可重入的】

9)虽然synchronized方法可以确保单个操作的原子性,但如果要把多个操作合并为一个复合操作,还是需要额外的加锁机制。

10加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。——对象的最新状态被安全地发布。【加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性】

11volatile变量:确保将变量的更新操作马上通知到其他线程。【典型例子:检查某个状态标记以判断是否退出循环】。Volatile变量通常用作某个操作完成、发生中断或者状态的标志。但是volatile的语义不足以确保递增操作++的原子性。(原子变量提供了“读--写”的原子操作,并且常常用做一种“更好的volatile变量”)

12“发布”一个对象:是对象能够在当前作用域之外的代码中使用。可以是以下几种情况:①将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看到该对象;②发布某个对象的时候,在该对象的非私有域中引用的所有对象都会被发布;③发布一个内部的类实例,内部类实例关联一个外部类引用。

13逸出:某个不应该发布的对象被公布的时候。某个对象逸出后,你必须假设有某个类或线程可能会误用该对象,所以要封装。

14)不要在构造过程中使this引用逸出。【常见错误:在构造函数中启动一个线程。】

======================================================================

15线程封闭:不共享。仅在单线程内访问数据,就不需要同步。【典型应用:①Swing的可视化组件和数据模型对象都不是线程安全的,Swing通过将它们封闭到Swing的实际分发线程中来实现线程安全;②JDBCConnection对象】

16线程封闭技术:①Ad-hoc线程封闭:维护线程封闭性的职责完全由程序实现来承担。I单线程子系统。在volatile变量上存在一个特殊的线程封闭:能确保只有单个线程对共享的volatile变量执行写入操作(其他线程有读取volatile变量),那么就可以安全地在这些共享的volatile变量上执行“读取-修改-写入”的操作,而其他读取volatile变量的线程也能看到最新的值。②栈封闭:通过线程的局部变量,但也要注意不能使之逸出。③ThreadLocal

17不变性:如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。线程安全性是不可变对象的固有属性之一,他们的不变性条件是有构造函数创建的。当满足以下所有条件的时候,对象才是不可变的:①对象创建以后起状态就是不能修改;②对象的所有域都是final类型;③对象是正确创建的(在对象的创建期间,this引用没有移除)。

18)任何线程都可以再不需要额外同步的情况下安全的访问不可变对象,即使在发布这些对象时没有使用同步。

18安全发布:常用模式:要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见:①在静态初始化函数中初始化一个对象引用(静态初始化器由JVM在类的初始化阶段执行,由于在JVM内部存在同步机制,所以可以安全地发布);②将对象的引用保存到volatile类型的域或者AtomicReferance对象中;③将对象的引用保存到某个正确构造对象的final类型域中;④将对象的引用保存到一个由锁保存的域中(线程安全容器)。

19)事实不可变对象

20)如果对象在构建以后可以修改,那么安全发布只能确保“发布当时”状态的可见性。对于可变对象,不仅在发布对象时需要使用同步,而且在每次对象访问时都需要使用同步来确保后续修改操作的可见性。

======================================================================

21安全的共享对象

线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。

只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。

线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口进行访问而不需要进一步的同步。

保护对象:被保护的对象只能通过持有特定的锁来访问。包含对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。

 

======================================================================

22设计线程安全的类

在设计线程安全类的过程中,需要包含以下3个基本要素

①找出构成对象状态的所有变量

②找出约束状态变量的不变性条件

③建立对象状态的并发访问管理策略(同步策略定义如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同)

 

23)①由于不变性条件以及后验条件在状态及状态转换上施加了各种约束,因此需要额外的同步与封装。如果某些状态是无效的,那么必须对底层的状态变量进行封装,否则客户代码可能会使对象处于无效状态。如果在某个操作中存在无效的状态转换,那么该操作必须是原子的。②在类中也可能包含多个状态变量的不变条件:这些相关的变量必须在单个原子操作中进行读取或更新。③依赖状态的操作:先验条件

 

======================================================================

24)阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量Semaphore、栅栏Barrierjava.util.concurrent.Exchanger<V>一种双向栅栏,各方在栅栏位置上交换数据,java.util.concurrent.CyclicBarrier,)、闭锁Latchjava.util.concurrent.CountDownLatchjava.util.concurrent.FutureTask<V>

栅栏:所有线程必须同时到达栅栏位置,才能继续执行;闭锁:用于等待事件,栅栏用于等待其他线程。

 

你可能感兴趣的:(java,多线程,并发)