网站建设项目验收方案,互联网舆情监测中心,淄博学校网站建设定制,微信网站搭建价格1、对象的状态#xff1a;对象的状态是指存储在状态变量中的数据#xff0c;对象的状态可能包括其他依赖对象的域。在对象的状态中包含了任何可能影响其外部可见行为的数据。 2、一个对象是否是线程安全的#xff0c;取决于它是否被多个线程访问。这指的是在程序中访问对象的…
1、对象的状态对象的状态是指存储在状态变量中的数据对象的状态可能包括其他依赖对象的域。在对象的状态中包含了任何可能影响其外部可见行为的数据。 2、一个对象是否是线程安全的取决于它是否被多个线程访问。这指的是在程序中访问对象的方式而不是对象要实现的功能。当多个线程访问某个状态变量并且其中有一个线程执行写入操作时必须采用同步机制来协同这些线程对变量的访问。同步机制包括synchronized、volatile变量、显式锁、原子变量。 3、有三种方式可以修复线程安全问题
1不在线程之间共享该状态变量
2将状态变量修改为不可变的变量
3在访问状态变量时使用同步 4、线程安全性的定义当多个线程访问某个类时不管运行时环境采用何种调度方式或者这些线程将如何交替执行并且在主调代码中不需要任何额外的同步这个类都能表现出正确的行为那么就称这个类是线程安全的。 5、无状态变量一定是线程安全的比如局部变量。 6、读取-修改-写入操作序列如果是后续操作是依赖于之前读取的值那么这个序列必须是串行执行的。在并发编程中由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况它称为竞态条件Race Condition。最常见的竞态条件类型就是先检查后执行的操作通过一个可能失效的观测结果来决定下一步的操作。 7、复合操作要避免竞态条件问题就必须在某个线程修改该变量时通过某种方式防止其他线程使用这个变量从而确保其他线程只能在修改操作完成之前或之后读取和修改状态而不是在修改状态的过程中。假定有两个操作A和B如果从执行A的线程看当另一个线程执行B时要么将B全部执行完要么完全不执行B那么A和B对彼此来说就是原子的。原子操作是指对于访问同一个状态的所有操作来说这个操作是一个以原子方式执行的操作。
为了确保线程安全性读取-修改-写入序列必须是原子的将其称为复合操作。复合操作包含了一组必须以原子方式执行的接口以确保线程安全性。 8、在无状态的类中添加一个状态时如果这个状态完全由线程安全的对象来管理那么这个类仍然是线程安全的。比如原子变量 9、如果多个状态是相关的需要同时被修改那么对多个状态的操作必须是串行的需要进行同步。要保持状态的一致性就需要在单个原子操作中更新所有相关的状态变量。 10、内置锁synchronized(object){同步块}
Java的内置锁相当于一种互斥体这意味着最多只有一个线程能持有这种锁当线程A尝试获取一个由线程B持有的锁时线程A必须等待或阻塞直到线程B释放这个锁。如果B永远不释放锁那么A也将永远地等待下去。 11、重入当某个线程请求一个由其他线程持有的锁时发出请求的线程就会阻塞。然而由于内置锁是可重入的因此如果某个线程试图获得一个已经由它自己持有的锁那么这个请求就会成功。重入意味着获取锁的操作的粒度是线程而不是调用。重入的一种实现方法是为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时JVM将记下锁的持有者并且将获取计数值置1。如果一个线程再次获取这个锁计数值将递增而当线程退出同步代码块时计数值会相应递减。当计数值为0时这个锁将被释放。 12、对于可能被多个线程同时访问的可变状态变量在访问它时都需要持有同一个锁在这种情况下我们称状态变量是由这个锁保护的。
每个共享的和可变的变量都应该只由一个锁来保护从而使维护人员知道是哪一个锁。
一种常见的加锁约定是将所有的可变状态都封装在对象内部并提供对象的内置锁this对所有访问可变状态的代码路径进行同步。在这种情况下对象状态中的所有变量都由对象的内置锁保护起来。 13、不良并发要保证同步代码块不能过小并且不要将本应是原子的操作拆分到多个同步代码块中。应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去从而在这些操作的执行过程中其他线程可以访问共享状态。 14、可见性为了确保多个线程之间对内存写入操作的可见性必须使用同步机制。 15、加锁与可见性当线程B执行由锁保护的同步代码块时可以看到线程A之前在同一个同步代码块中的所有操作结果。如果没有同步那么就无法实现上述保证。加锁的含义不仅仅局限于互斥行为还包括内存可见性。为了确保所有线程都能看到共享变量的最新值所有执行读操作或写操作的线程都必须在同一个锁上同步。 16、volatile变量当把变量声明为volatile类型后编译器与运行时都会注意到这个变量是共享的因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或其他对处理器不可见的地方因此在读取volatile类型的变量时总会返回最新写入的值。volatile的语义不足以确保递增操作的原子性除非你能确保只有一个线程对变量执行写操作。原子变量提供了“读-改-写”的原子操作并且常常用做一种更好的volatile变量。 17、加锁机制既可以确保可见性又可以确保原子性而volatile变量只能确保可见性。 18、当且仅当满足以下的所有条件时才应该使用volatile变量
1对变量的写入操作不依赖变量的当前值不存在读取-判断-写入序列或者你能确保只有单个线程更新变量的值。
2该变量不会与其他状态变量一起纳入不可变条件中
3在访问变量时不需要加锁 19、栈封闭在栈封闭中只能通过局部变量才能访问对象。维护线程封闭性的一种更规范的方法是使用ThreadLocal这个类能使线程的某个值与保存值的对象关联起来ThreadLocal通过了get和set等访问接口或方法这些方法为每个使用该变量的线程都存有一份独立的副本因此get总是返回由当前执行线程在调用set时设置的最新值。 20、在并发程序中使用和共享对象时可以使用一些使用的策略包括
1线程封闭线程封闭的对象只能由一个线程拥有对象被封闭在该线程中并且只能由这个线程修改。
2只读共享在没有额外同步的情况下共享的只读对象可以由多个线程并发访问但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象从技术上来说是可变的但其状态在发布之后不会再改变。
3线程安全共享。线程安全的对象在其内部实现同步因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
4保护对象。被保护的对象只能通过持有对象的锁来访问。保护对象包括封装在其他线程安全对象中的对象以及已发布并且由某个特定锁保护的对象。 21、饥饿当线程由于无法访问它所需要的资源而不能继续执行时就发生了饥饿某线程永远等待。引发饥饿的最常见资源就是CPU时钟周期。比如线程的优先级问题。在Thread API中定义的线程优先级只是作为线程调度的参考。在Thread API中定义了10个优先级JVM根据需要将它们映射到操作系统的调度优先级。这种映射是与特定平台相关的因此在某个操作系统中两个不同的Java优先级可能被映射到同一优先级而在另一个操作系统中则可能被映射到另一个不同的优先级。
当提高某个线程的优先级时可能不会起到任何作用或者也可能使得某个线程的调度优先级高于其他线程从而导致饥饿。
通常我们尽量不要改变线程的优先级只要改变了线程的优先级程序的行为就将与平台相关并且会导致发生饥饿问题的风险。 事务T1封锁了数据R,事务T2又请求封锁R于是T2等待。T3也请求封锁R当T1释放了R上的封锁后系统首先批准了T3的请求T2仍然等待。然后T4又请求封锁R当T3释放了R上的封锁之后系统又批准了T的请求......T2可能永远等待 22、活锁
活锁是另一种形式的活跃性问题该问题尽管不会阻塞线程但也不能继续执行因为线程将不断重复执行相同的操作而且总会失败。活锁通常发生在处理事务消息的应用程序中。如果不能成功处理某个消息那么消息处理机制将回滚整个事务并将它重新放到队列的开头。虽然处理消息的线程并没有阻塞但也无法继续执行下去。这种形式的活锁通常是由过度的错误恢复代码造成的因为它错误地将不可修复的错误作为可修复的错误。 当多个相互协作的线程都对彼此进行响从而修改各自的状态并使得任何一个线程都无法继续执行时就发生了活锁。要解决这种活锁问题需要在重试机制中引入随机性。在并发应用程序中通过等待随机长度的时间和回退可以有效地避免活锁的发生。 23、当在锁上发生竞争时竞争失败的线程肯定会阻塞。JVM在实现阻塞行为时可以采用自旋等待Spin-Waiting,指通过循环不断地尝试获取锁直到成功或者通过操作系统挂起被阻塞的线程。这两种方式的效率高低取决于上下文切换的开销以及在成功获取锁之前需要等待的时间。如果等待时间较短则适合采用自旋等待的方式而如果等待时间较长则适合采用线程挂起方式。 24、有两个因素将影响在锁上发生竞争的可能性锁的请求频率以及每次持有该锁的时间。如果二者的乘积很小那么大多数获取锁的操作都不会发生竞争会因此在该锁上的竞争不会对可伸缩性造成严重影响。然而如果在锁上的请求量很高那么需要获取该锁的线程将被阻塞并等待。在极端情况下即使仍有大量工作等待完成处理器也会被闲置。
有3种方式可以降低锁的竞争程度
1减少锁的持有时间
①缩小锁的范围将与锁无关的代码移出同步代码块尤其是开销较大的操作以及可能被阻塞的操作IO操作。
当把一个同步代码块分解为多个同步代码块时反而会对性能提升产生负面影响。在分解同步代码块时理想的平衡点将与平台相关但在实际情况中仅可以将一些大量的计算或阻塞操作从同步代码块移出时才应该考虑同步代码块的大小。
②减小锁的粒度锁分解和锁分段
锁分解是采用多个相互独立的锁来保护独立的状态变量从而改变这些变量在之前由单个锁来保护的情况。这些技术能减小锁操作的粒度并能实现更高的可伸缩性然而使用的锁越多那么发生死锁的风险也就越高。
锁分段比如JDK1.7及之前的ConcurrentHashMap采用的方式就是分段锁的方式。
2降低锁的请求频率
3使用带有协调机制的独占锁这些机制允许更高的并发性
比如读写锁并发容器等