JAVA多线程并发编程———基础(上)

并发的优势

    公平性:不同的用户和进程对于计算机的资源有着同等的使用权。
    便利性:在计算多个任务的时候,应该编写多个程序,每个程序执行一个任务并在不要的时候进行相互通信,这比只编写一个程序来实现计算机的所有任务更容易实现。
   资源利用率:当a程序执行X事件时,b程序无需等待a程序执行完成去执行Y事件。

线程的优势

    1.线程可以有效的降低程序的开发和维护成本,提升复杂程序的性能,线程能够将大部分的异步工作流转换成串行工作流,因此能更好的模拟人类的工作方式和交互方式。
    2.发挥多处理器的强大能力。
    3.建模的简单性
    4.异步事件的简化处理
    5.响应更灵敏的用户界面

线程的安全性

    含义:当多个线程访问某个类的时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

    要编写线程安全的代码,其核心是在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。
    对象的状态是指存储在状态变量(列如实例或静态域)中的数据。对象的状态可能包括其它依赖对象的域。
    共享:意味可以由多个线程进行访问。
    可变:意味着变量的值在其生命周期里可以发生变化。

    一个对象是否需要线程安全,取决于它是否被多个线程访问。
    无状态对象一定是线安全的。

    当多个线程访问某个状态变量并且只有一个线程执行写入操作的时候,必须采用同步机制来协同这些线程多变量的访问。java主要的同步机制的关键字synchronized,他提供了一种独特的占有的加锁方式。但“同步”这个术语还包括volatile类型的变量,显示锁以及原子变量。

如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出线错误。有三种方式可以修复这个问问题:
1.不在线程之间共享这个状态变量
2.将该状态变量修改为不可变的变量
3.在访问的时候使用同步
复合操作:
    为了确保线程的安全性,“先检查后执行”(列如延迟初始化)和“读取—修改—写入”(列如递增运算)等操作必须是原子行的,我们将这些操作称之为复合操作。
加锁机制
    要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

    java提供了一种内置的锁来支持原子性:同步代码块 (synchronized block)。它包括两个部分:一个作为锁的对象引用,一个作为这个锁保护的代码块。以关键字来修饰的方法就是一种跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法就是一class对象作为锁。
内置锁:
    每个对象都可以作为一种实现同步的锁,这些锁被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

    java的内置锁是一种互斥体(互斥锁),因为,由这个锁保护的同步代码块会以原子方式执行,多个线程在执行该代码块时也不会相互干扰。并发环境中的原子性与事务应用程序中的原子性有相同的的含义。
    任何一个执同步代码块的线程,都不可能看到有其它线程正在执行由同一个锁保护的同步代码块。

重入:
    当某个线程请求一个由其它线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。重入意味着获取锁的操作粒度是“线程”,而不是“调用”。

    重入进一步提升了加锁行为的封装性,简化了面向对象的并发代码的开发。避免了某些情况先死锁的发生。

用锁来保护状态:
    1.对于可能被多个线程同时访问的可变状态变量,在访问它的时候需要持有同一个锁,在这种情况下,我们称状态比变量是由这个锁保护的。
    2.每个共享的和可变的变量都应只由一个锁来保护,从而使维护人员知道是哪一个锁。
    3.对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。


volatile变量:
    是java提供的一种稍弱的同步机制,是一种比sychronized关键字更轻量级的同步机制。同来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其它内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其它处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。

当且仅当满足以下所有条件时候,才能使用volatil变量 :

1.对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。

2.这变量不会与其它状态变量一起纳入不变条件中。

3.在访问变量时不需要加锁

安全的对象构造过程:

不要在构造过程中使用this引用逸出。

在构造过程中使this引用逸出的一个常见错误是,在构造函数中启动一个线程。当对象在其构造函数中创建一个线程的时候,无论是显示创建(通过将他传给构造函数)还是隐式创建(由于thread或runable是该对象的一个内部类),this引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看见它。在构造函数中创建线程并没有错误,但最好不要立刻启动它,而是通过一个start或initialize方法来启动。在构造函数中调用一个可改写的实例方法时(既不是私有方法,也不是终结方法),同样会导致this引用在构造过程中逸出。

体来说,只有当构造函数返回时,this引用才应该从线程中逸出。构造函数可以将this引用保存到本地某个地方,只要其它线程不会在构造函数完成之前使用它。

线程封闭

当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术叫做线程封闭。当某个对象封闭在一个线程中时,这中用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

Ad—hoc线程封闭:指维护线程封闭性的职责完全由程序实现来承担。Ad—hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,例如可见性修饰符或局部变量能将对象封装到目标线程上。事实上,对线程封闭对象的引用通常保存在公有的变量中。

栈封闭:栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。正如封装能使得代码更容易维持不变性条件那样,同步变量也能使对象更容易封闭在线程中。局部变量的固有属性之一就是封闭在执行线程中。他们位于执行线程的栈中,其它线程无法访问这个栈,栈封闭比Ad—hoc线程封闭更容易维护,也更加健壮。

ThreadLocal类:维护线程封闭性的一种更加规范的方法是使用threadLocal,这个类能使线程中的某个值与保存值的对象关联起来。threadLocal提供了get与set等访问接口的方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。ThreadLocaL对象通常用于防止对可变的单实例对象或全局变量进行共享。

不可变的对象一定是线程安全的。

当满足以下条件时,对象是不可变的:

1.对象创建后其状态不能修改 

2.对象的所有域都是final类型 

3.对象是正确创建的(创建期间,没有this引用逸出)


final域

关键字final可以视为C++中const机制的一种受限版本,用于构造不可变对象。final类型的域是不能修改的(但是如果final的域所引用的对象是可变的,那么这些被引用的对象是可以修改的)。然而在,在java内存模型中,final域还有特殊的含义。final域能够确保初始化过程的安全性,从而可以不受限制的访问不可变对象,并在共享这些对象时无须同步。


安全发布的常用模式

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

对象的发布需求取决于它的可变性:

1.不可变对象可以通过任意机制来发布。

2.事实不可变对象必须通过安全方式来发布。

3.可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。

要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

1.在静态初始化函数中初始化一个对象引用。

2.将对象的引用保存volatile类型的域或者AtomicReferance.

3.将对象的引用保存到某个正确构造对象的final类型域中。

4.将对象的引用保存到一个由锁保护的域中

在没有额外的同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。

如果一个状态变量是线程安全的,并且没有任何不可变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。


安全的共享对象

在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:

线程封闭线程封闭的对象只能由一个线程拥有,对象封闭在该线程中,并且只能由这个线程修改。
只读共享在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它,共享的只读对象包括不可变对象和事实不可变对象。
线程安全共享线程安全的对象在其内部实现同步,因此多个线程都可以通过对象的公有接口来进行访问而不需要进一步的同步。
保护对象:被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及发布的并且由某个特定锁保护的对象。











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值