11.栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。他们位于执行线程的栈中,其他线程无法访问这个栈。栈封闭(也被称为线程内部使用或者线程局部使用,不要与核心类库中的ThreadLocal混淆)比Ad-hoc线程封闭更易于维护,也更加健壮。
如果在线程内部(Within-Thread)上下文中使用非线程安全的对象,那么该对象仍然是线程安全的。然而,要小心的是,只有编写代码的开发人员才知道哪些对象需要被封闭到执行线程中,以及被封闭的对象是否是线程安全的。如果没有明确地说明这些需求,那么后续的维护人员很容易错误地使对象逸出。
12.维持线程封闭性的一种更规范方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。
private static ThreadLocal<Connection> connectionHolder=new ThreadLocal<Connection>() {
public Connection initialValue()
{
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection()
{
return connectionHodler.get();
}
当某个线程初次调用ThreadLocal.get方法时,就会调用initialValue来获取初始值。从概念上看,你可以将ThreadLocal<T>视为包含了Map<Thread,T>对象,其中保存了特定于该线程的值,但ThreadLocal的实现并非如此。这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会作为垃圾回收。
ThreadLocal变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合性,因此在使用时要格外小心。
13.满足同步需求的另一种方法是使用不可变对象(Immutable Object)。
如果某个对象在被创建后其状态就不能被改变,那么这个对象就称为不可变的对象。线程安全性是不可变对象的固有属性之一,它们的不变性条件是由构造函数创建的,只要它们的状态不改变,那么这些不变性条件就能得以维持。
虽然在Java语言规范和Java内存模型中都没有给出不可变性的正式定义,但不可变性并不等于将对象中所有的域都声明为final类型,即使对象中所有的域都是final类型的,这个对象也仍然是可变的,因为在final类型的域中可以保存对可变对象的引用。
当满足以下条件时,对象才是不可变的:
- 对象创建以后其状态就不能改变。
- 对象的所有域都是final类型。
- 对象是正确创建的(在对象的创建期间,this引用没有逸出)。
14.关键字final可以视为C++中const机制的一种受限版本,用于构造不可变性对象。final类型的域是不能修改的(但如果final域所引用的对象是可变的,那么这些被引用的对象是可以修改的)。然而,在Java内存模型中,final域还有这特殊的语义。final能确保初始化过程的安全性,从而可以不受限制地访问不可变对象,并在共享这些对象时无须同步。
正如“除非需要更高的可见性,否则应将所有的域都声明为私有域”是一个良好的编程习惯,“除非需要某个域是可变的,否则应将其声明为final域”也是一个良好的编程习惯。
尽管在构造函数中设置的域值似乎是第一次向这些域中写入的值,因此不会有“更旧的”值被视为失效值,但Object的构造函数会在子类构造函数运行之前先将默认值写入所有的域。因此,某个域的默认值可能被视为失效值。
任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。
15.要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:
- 在静态初始化函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
- 将对象的引用保存到某个正确构造对象的final类型域中。
- 将对象的引用保存到一个由锁保护的域中。
所有的安全发布机制都能确保,当对象饿引用对所有访问该对象的线程可见时,对象发布时的状态对于所有线程也将是可见的,并且如果对象状态不再改变,那么就足以确保任何访问都是安全的。
对象的发布需求取决于它的可变性:
- 不可变对象可以通过任意机制来发布。
- 事实不可变对象必须通过安全方式来发布。
- 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。
16.在并发程序中使用和共享对象时,可以使用一些使用的策略,包括:
线程封闭。线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
只读共享。在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
线程安全共享。线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
保护对象。被保护的对象只能通过持有特定的锁在访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。
17.在设计线程安全类的过程中,需要包含以下三个基本要素:
- 找出构成对象状态的所有变量。
- 找出约束状态变量的不变性条件。
- 建立对象状态的并发访问管理策略。
同步策略(Synchronization Policy)定义了如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同。同步策略规定了如何将不可变性、线程封闭与加锁机制等结合起来以维护线程的安全性,并且还规定了哪些变量由哪些锁来保护。
18.如果不了解对象的不变性条件与后验条件,那么就不能确保线程安全性。要满足在状态变量的有效值或状态转换上的各种约束条件,就需要借助原子性与封装性。
在Java中,等待某个条件为真的各种内置机制(包括等待和通知等机制)都与内置加锁机制紧密关联,要想正确地使用它们并不容易。
在定义哪些变量将构成对象的状态时,只考虑对象拥有的数据。所有权(Ownership)在Java中并没有得到充分的体现,而是属于类设计中的一个要素。
19.封装简化了线程安全类的实现国产,它提供了一种实例封闭机制(Instance Confinement),通常也简称为“封闭”。当一个对象被封装到另一个对象中时,能够访问被封装对象的所有代码路径都是已知的。
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
一些基本的容器类并发线程安全的,例如ArrayList和HashMao,但类库提供了包装器工厂方法(例如Collections.synchronizedList及其类似方法),使得这些非线程安全的类可以在多线程环境中安全地使用。这些工厂方法通过“装饰器(Decorator)”模式将容器类封装在一个同步的包装器对象中,而包装器能将接口中的每个方法都实现为同步方法,并将调用请求转发到底层的容器对象上。只要包装器对象拥有对底层容器对象的唯一引用(即把底层容器对象封闭在包装器中),那么它就是线程安全的。
封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无须检查整个程序。
20.虽然Java监视器模式来自于Hoare对监视器机制的研究工作,但这种模式与真正的监视器类之间存在一些重要的差异。进入和退出同步代码块的字节指令也称为monitorenter和monitorexit,而Java的内置锁也称为监视器锁或监视器。
Java监视器模式仅仅是一种编写代码的约定,对于任何一种锁对象,只要自始至终都是用该锁对象,都可以用来保护对象的状态。
public class PrivateLock{
private final Object myLock=new Object();
@GuardedBy("myLock") Widget widget;
void someMethod()
{
synchronized(myLock)
{
//修改或访问Widget的状态
}
}
}
如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。
相关推荐
这本"Java并发编程学习笔记"可能是作者在深入研究Java并发特性、工具和最佳实践过程中积累的心得体会。下面,我们将根据这个主题,探讨一些关键的Java并发编程知识点。 1. **线程与进程**:在多任务环境中,线程是...
### Java并发编程实践笔记知识点详解 #### 一、保证线程安全的方法 1. **不要跨线程访问共享变量:** 当多个线程共享某个变量时,若其中一个线程修改了该变量,其他线程若没有正确同步,则可能读取到错误的数据。...
《Java并发编程实战》是一本深入探讨Java多线程和并发编程的经典著作,它为开发者提供了全面、实用的指导,帮助他们理解和解决并发问题。这本书的中文版使得更多的中国开发者能够受益于其丰富的知识和实践经验。 ...
这份"Java并发实践-学习笔记"涵盖了这个关键主题,旨在帮助开发者深入理解和掌握Java中的并发机制。以下是对这份笔记可能包含的一些核心知识点的详细阐述: 1. **Java并发基础**:首先,笔记可能会介绍Java并发的...
"java高并发.txt"可能是一份文档或笔记,涵盖了Java并发编程的核心概念和技术。它可能详细解释了线程的生命周期、线程安全问题(如数据竞争、活锁、死锁)、并发工具类(如CountDownLatch、CyclicBarrier、Semaphore...
Java并发编程学习笔记 本项目整理自《Java7并发编程实战手册》,感兴趣的话推荐阅读原著 本章内容包括: 线程的创建和运行 线程信息的获取和设置 线程的中断 线程中断的控制 线程的Hibernate和恢复 等待线程的终止 ...
这篇“Java线程编程学习笔记(二)”很可能是对Java并发编程深入探讨的一部分,特别是涉及多线程示例的实践应用。我们将从标题、描述以及标签来推测可能涵盖的知识点,并结合"Multi-Threads Demo"这一压缩包文件名来...
Java并发编程是Java开发中的重要领域,特别是在大型分布式系统或者高并发应用中,对线程安全和性能优化的理解与实践至关重要。"JUC并发编程学习笔记(硅谷)"很可能包含了关于Java并发工具集(Java Util Concurrency, ...
综上所述,这一系列学习笔记涵盖了并发编程的关键概念和实战技巧,包括Java内存模型、线程池、并发容器的使用以及常见数据结构的线程安全问题。通过深入学习这些内容,开发者可以更好地理解和解决多线程环境下的编程...
Java并发编程是编程领域中的重要组成部分,特别是在大型系统和服务器端开发中不可或缺。Java自诞生以来就内置了对多线程的支持,使得开发者能够轻松创建并行运行的任务,提升程序性能。然而,随着并发编程实践的深入...
在本Java项目学习笔记中,我们关注的是SSM(Spring、SpringMVC、MyBatis)框架下的高并发秒杀API实现。这是一个典型的电商场景,其中涉及到的技术点广泛且实用,对于提升Java开发者处理高并发问题的能力至关重要。...
Java中的多线程编程是Java开发中的重要组成部分,它允许应用程序同时执行...以上就是Java多线程编程中关于死锁的原理、示例以及并发编程的一些最佳实践。理解并掌握这些知识点对于编写高效、稳定的多线程程序至关重要。
Java并发编程中的Synchronized是Java实现线程同步的关键机制,其在JDK1.6之后进行了大量的优化,包括引入了轻量级锁和偏向锁,以提升并发性能。以下是关于这些优化的详细解释: **一、重量级锁** 重量级锁是基于...
10. **多线程**:Java提供了丰富的API支持并发编程,如Thread、Runnable接口,以及synchronized关键字,笔记可能涉及到线程的创建、同步和通信。 11. **枚举与注解**:枚举是Java中的特殊数据类型,而注解则是一种...
Java学习笔记是由知名IT教育专家林信良编著的一本深受好评的编程教材,特别适合初学者入门。这本书深入浅出地介绍了Java编程语言的基础知识和核心概念,旨在帮助读者快速掌握这一强大的开发工具。 首先,书中从Java...
7. **多线程**:讨论并发编程,包括线程的创建、同步、通信和线程池的使用。 8. **网络编程**:涉及Socket编程,以及客户端和服务器端的交互。 9. **高级特性**:可能包括反射、动态代理、注解、JavaFX等。 10. *...
这个Java实战项目提供了详细的步骤和学习笔记,涵盖了从需求分析、架构设计到代码实现的全过程,是提升Java并发编程和分布式系统设计能力的宝贵资源。通过实际操作,你可以深入了解SSM框架的使用以及在高并发场景下...
在Java学习的过程中,SSM(Spring、SpringMVC、...通过这个SSM实战项目,开发者可以实践以上知识点,加深对Java高并发编程的理解,提高解决问题的能力。同时,结合学习笔记,有助于巩固理论知识,形成完整的知识体系。
2. **Java SE进阶**:这部分可能深入到Java的高级特性,如反射、动态代理、注解、枚举、NIO(New IO)和并发编程。这些知识对于理解和编写高效、灵活的Java代码至关重要。 3. **J2EE组件**:J2EE包含了许多服务器端...
这份"个人Java学习过程中所有学习笔记"包含了作者在学习Java时积累的宝贵经验,旨在帮助初学者或有经验的程序员巩固基础,提升技能。 笔记内容可能涵盖以下几个主要部分: 1. **Java基础**:这部分通常包括Java的...