如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全的set 线程安全的list有哪些
线程安全的set 线程安全的list有哪些
或者说:一个类或者程序所提供的接口对于线程来说是原子作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读作,而无写作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写作,一般都需要考虑线程同步,否则就可能影响线程安全。
比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个类型主要有3种:set(集)、list(列表)和map(映射)。元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
编辑本段线程安全性类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。
此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的作是以固定的、全局一致的顺序发生的。
正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、性和持久性)事务时使用的一致性与性之间的关系:从特定线程的角度看,由不同线程所执行的对象作是先后(虽然顺序不定)而不是并行执行的。
线程安全性不是一个非真即的命题。 Vector 的方法都是同步的,并且 Vector 明确地设计为在多线程环境中工作。但是它的线程安全性是有限制的,即在某些方法之间有状态依赖(类似地,如果在迭代过程中 Vector 被其他线程修改,那么由 Vector.iterator() 返回的 iterator会抛出ConcurrentModifiicationException)。
对于 Ja 类中常见的线程安全性级别,没有一种分类系统可被广泛this.id = id;接受,不过重要的是在编写类时尽量记录下它们的线程安全行为。
Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。只要明确地记录下线程安全特性,那么您是否使用这种系统都没关系。这种系统有其局限性 -- 各类之间的界线不是百分之百地明确,而且有些情况它没照顾到 -- 但是这套系统是一个很好的起点。这种分类系统的核心是调用者是否可以或者必须用外部同步包围作(或者一系列作)。下面几节分别描述了线程安全性的这五种类别。不可变不可变的对象一定是线程安全的,并且永远也不需要额外的同步[1]。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Ja 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。线程安全线程安全的对象具有在上面“线程安全”一节中描述的属性 -- 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。有条件的线程安全有条件的线程安全类对于单独的作可以是线程安全的,但是某些作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器 -- 由这些类返回的 fail-fast 迭代器定在迭代器进行遍历的时候底层不会有变化。为了保证其他线程不会在遍历的时候改变,进行迭代的线程应该确保它是独占性地访问以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的 -- 并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。
如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的,而且还要记录必须防止哪些作序列的并发访问。用户可以合理地设其他作序列不需要任何额外的同步。线程兼容线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用 synchronized 块包围某些作序列。为了程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。
许多常见的类是线程兼容的,如类 ArrayList 和 HashMap 、 ja.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。线程对立线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。
Ja的类都位于ja.util包中,Ja中存放的是对象的引用,而非对象本身。
如果大家经常看MSDN或者VS帮助中的 NET类库参考的话 就不难发现几乎所有的类型都有这么一句话的描述 此类型的任何公共 static(在 Visual Basic中为 Shared) 成员都是线程安全的 但不保证所有实例成员都是线程安全的 那么线程安全到底是什么意思?Ja主要分为三种类型:
而不是直接lock( Container )a.Set(集):中的对象不按特定方式排序,并且没有重复对象。它的有些实现类能对中的对象按特定方式排序。
b.List(列表):中的对象按索引位置排序,可以有重复对象,允许按照对象在中的索引位置检索对象。
c.Map(映射):中的每一个元素包含一对键对象和值对象,中没有重复的键对象,值对象可以重复。它的有些实现类能对中的键对象进行排序。
Set、List和Map统称为Ja。
1.Set(集)
Set中的对象不按特定方式排序,并且没有重复对象。Set接口主要有两个实现类HashSet和TreeSet。HashSet类按照哈希算法来存取中的对象,存取速度比较快。HashSet类还有一个子类LinkedHashSet类,它不仅实现了哈希算法,而且实现了链表数据结构。TreeSet类实现了SortedSet接口,具有排序功能。
Set的add()方法判断对象是否已经存在于中的判断流程:
boolean isExists = false;
Iterator it = set.iterator();
while(it.hasNext()){
Object object = it.next();
if(newObject.equals(oldObject)){
isExists = true;
break;
}}
2.HashSet类
当HashSet向中加入一个对象时,会调用对象的hashCode()方法获得哈希码,然后根据这个哈希码进一步计算出对象在中的存放位置。
当Object1变量和object2变量实际上引用了同一个对象,那么object1和object2的哈希码肯定相同。
为了保证HashSet能正常工作,要求当两个对象用equals()方法比较的结果为相等时,它们的哈希码也相等。即:
customer1.hashCode() == customer2.hashCode();
如:对应于Customer类的以下重写后的equals()方法:
public boolean equals(Object o){
if(!o instanceof Customer) return false;
final Customer other = (Customer)o;
if(this.name.equals(other.getName())&&this.age==other.getAge())
return true;
else
}为了保证HashSet正常工作,如果Customer类覆盖了equals()方法,也应该覆盖hashCode()方法,并且保证两个相等的Customer对象的哈希码也一样。
public int hashCode(){
int result;
result = (name==null?0:name.hashCode());
result = 29result+(age==null?0:age.hashCode());
return result;
}3.TreeSet类
TreeSet类实现了SortedSet接口,能够对中的对象进行排序。TreeSet支持两种排序方式:自然排序和客户化排序,在默认情况下TreeSet采用自然排序方式。
在JDK中,有一部分类实现了Comparable接口,如Integer、Double和String等。Comparable接口有一个compareTo(Object o)方法,它返回整数类型。对于表达式xpareTo(y),如果返回值为0,表示x和y相等,如果返回值大于0,表示x大于y,如果返回值小于0,表示x小于y。
以下列出了JDK中实现了Comparable接口的一些类的排序方式
类 排序
BigDecimalBigIntegerByteDoubleFloatIntegerLongShort 按数字大小排序
Character 按字符的Unicode值的数字大小排序
String 按字符串中字符的Unicode值排序
例如:
以下是Customer类的compareTo()方法的一种实现方式:
public int compareTo(Object o){
//先按照name属性排序
if(this.namepareTo(other.getName())>0) return 1;
if(this.namepareTo(other.getName())<0) return -1;
//再按照age属性排序
if(this.age>other.getAge()) return 1;
return 0;
}为了保证TreeSet能正确地排序,要求Customer类的compareTo()方法与equals()方法按相同的规则比较两个Customer对象是否相等。
public boolean equals(Object o){
final Customer other = (Customer)o;
if(this.name.equals(other.getName())&&this.age==other.getAge()){
return true;
}else{
}}
值得注意的是,对于TreeSet中已经存在的Customer对象,如果修改了它们的属性,TreeSet不会对进行重新排序。在实际域模型中,实体类的属性可以被更新,因此不适合通过TreeSet来排序。最适合于排序的是不可变类。
b.客户化排序
除了自然排序,TreeSet还支持客户化排序。ja.util.Comparator接口用于指定具体的排序方式,它有个compare(Object object1,Object object2)方法,用于比较两个对象的大小。当表达式compare(x,y)的值大于0,表示x大于y;当compare(x,y)的值小于0,表示x小于y;当compare(x,y)的值等于0,表示x等于y。
例如:如果希望TreeSet仅按照Customer对象的name属性进行降序排列,可以创建一个实现Comparator接口的类CustomerComparator:
public class CustomerComparator implements Comparator{
public int compare(Object o1,Object o2){
Customer c1= (Customer)o1;
Customer c2 = (Customer)o2;
if(c1.getName()pareTo(c2.getName())>0) return -1;
if(c2.getName()pareTo(c2.getName())<0) return 1;
return 0;
}}
接下来在构造TreeSet的实例时,调用它的TreeSet(Comparator comparator)构造方法:
Set set = new TreeSet(new CustomerComparator());
4.向Set中加入持久化类的对象
例如两个Session实例从数据库加载相同的Order对象,然后往HashSet里存放,在默认情况下,Order类的equals()方法比较两个Orer对象的内存地址是否相同,因此order1.equals(order2)==false,所以order1和order2游离对象都加入到HashSet中,但实际上order1和order2对应的是ORDERS表中的同一条记录。对于这一问题,有两种解决方案:
(1)在应用程序中,谨慎地把来自于不同Session缓存的游离对象加入到Set中,如:
Set orders = new HashSet();
orders.add(order1);
order.add(order2);
(2)在Order类中重新实现equals()和hashCode()方法,按照业务主键比较两个Order对象是否相等。
提示:为了保证HashSet正常工作,要求当一个对象加入到HashSet中后,它的哈希码不会发生变化。
5.List(列表)
List的主要特征是其对象以线性方式存储,中允许存放重复对象。List接口主要的实现类有LinkedList和ArrayList。LinkedList采用链表数据结构,而ArrayList代表大小可变的数组。List接口还有一个实现类Vector,它的功能和ArrayList比较相似,两者的区别在于Vector类的实现采用了同步机制,而ArrayList没有使用同步机制。
List只能对中的对象按索引位置排序,如果希望对List中的对象按其他特定方式排序,可以借助Comparator和Collections类。Collections类是API中的辅助类,它提供了纵的各种静态方法,其中sort()方法用于对List中的对象进行排序:
a.sort(List list):对List中的对象进行自然排序。
b.sort(List list,Comparator comparator):对List中的对象进行客户化排序,comparator参数指定排序方式。
如Collections.sort(list);
6.Map(映射)
Map(映射)是一种把键对象和值对象进行映射的,它的每一个元素都包含一对键对象和值对象,而值对象仍可以是Map类型,依次类推,这样就形成了多级映射。
Map有两种比较常用的实现:HashMap和TreeMap。HashMap按照哈希算法来存取键对象,有很好的存取性能,为了保证HashMap能正常工作,和HashSet一样,要求当两个键对象通过equals()方法比较为true时,这两个对象的hashCode()方法返回的哈希码也一样。
TreeMap实现了SortedMap接口,能对键对象进行排序。和TreeSet一样,TreeMap也支持自然排序和客户化排序两种方式。
例:创建一个缓存类EntityCache,它能粗略地模仿Session的缓存功能,保证缓存中不会出现两个OID相同的Customer对象或两个OID相同的Order对象,这种惟一性是由键对象的惟一性来保证的。
Key.ja:
package mypack;
public class Key{
private Class classType;
private Long id;
public Key(Class classType,Long id){
this.classType = classType;
}public Class getClassType(){
return this.classType;
return this.id;
}public boolean equals(Object o){
if(!(o instanceof Key)) return false;
final Key other = (Key)o;
if(classType.equals(other.getClassType())&&id.equals(other.getId()))
return true;
}public int hashCode(){
int result;
result = classType.hashCode();
result = 29 result + id.hashCode();
return result;
}}
EntityCache.ja:
package mypack;
import ja.util.;
public class EntityCache {
private Map entitiesByKey;
public EntityCache() {
entitiesByKey=new HashMap();
}public void put(BusinessObject entity){
entitiesByKey.put(key,entity);
}public Object get(Class classType,Long id){
Key key=new Key(classType,id);
return entitiesByKey.get(key);
}public Collection getAllEntities(){
return entitiesByKey.values();
}public boolean contains(Class classType,Long id){
Key key=new Key(classType,id);
return entitiesByKey.containsKey(key);
}}
2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
3、List接口有三个实现类:LinkedList,ArrayList,Vector ,Set接口有两个实现类:HashSet(底层由HashMap实现),LinkedHashSet
List和Set都是接口。他们各自有自己的实现类,有无顺序的实现类,也有有顺序的实现类。
的不同就是List是可以重复的。而Set是不能重复的。
List适合经常追加数据,插入,删除数据。但随即取数效率比较低。
Set适合经常地随即储存,插入,删除。但是在遍历时效率比较低。
List有序而且可以重复,Set是没有顺序但不可以重复。如何使用那得看实际应用。比如你在设计实体时,这个实体包含了另一个实体的属性,而且是一对多的关系,那么你在一的这方是如果设计多的那方的属性呢?你觉得用list还是用set合适呢?其实用哪个都可以,但实体都是需要持久化到数据库的,要知道数据库一般是不允许重复,所以设计时一般使用set。另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。
有序可重复,使用list;无序不可重复使用set。一般开发中使用list
比如:
list.add('1');
list.add('1');
set.add('1');
list的长度为2,而set的长度为1
List就是可以重复的。而Set是不可以重复的。
如你输入1,2,2,4
那么在List中就有4个元素:1,2,2,4
但如果在Set中就只有3个元素了:1,2,4
类型主要有3种:set(集)、bailist(列表)和map(映射)。
1、List(有序、可重复)
List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。
2、Set(无序、不能重复)
Set里存放的对象是无序,不能重复的,中的对象不按特定的方式排序,只是简单地把对象加入中。
3、Map(键值对、键、值不)
Map中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map遍历时先得到键的set,对set进行遍历,得到相应的值。
List
控制的是一个数组,那么可以有重复的数据。
在integer的list中,添加4个3的话,会有4个元素在list中。
而set是不允许有重复的数据,所以如果set中添加4个3,只会有1个3.
set的一个用处是,设你要在一个名单里面查找又没有名字相同的,就可以用set,
如果set中没有该名字,就存进set。如果有的话,说明找到了重复的。
set一般常用的是hashset,查询和插入效率为o(1)。
list存有序元素,可以重复,set存无序元素,不可重复
一 什么是线程冲突
线程冲突其实就是指 两个或以上的线程同时对同一个共享资源进行作而造成的问题
一个比较经典的例子是 用一个全局变量做计数器 然后开N个线程去完成某个任务 每个线程完成一次任务就将计数器加一 直到完成 次任务 如果不考虑线程冲突问题 用类似下面的代码去做 则很可能会超额完成任务 线程越多 完成任务次数超出 次的可能性就越大
伪代码如下
int count = ;//全局计数器
void ThreadMod()//运行在每个线程的方法
{while( true )
break;//中断线程执行
DoSoming();//完成某个任务
count++;
}}
//省略线程的创建等代码
具体的 为什么会超额完成任务的原因在这里我就不赘述了 这个例子在单线程环境中是不会超额完成任务的
当然 在这个例子中 将count++放到if语句中 也许能降低一些发生的概率 但那不是的 换言之这样的程序不能杜绝超额完成任务的可能
其实从线程冲突的定义中我们不难发现 要造成线程冲突有两个必要条件 多线程和共享资源 这两个条件中有一个不成立 就不可能发生线程冲突问题
所以 在单线程环境中 是不存在线程冲突的问题的 不过很可惜的是 我们的软件早已进化到了多进程多线程的时代 单线程的程序几乎是不存在的 无论是WinForm还是WebForm 程序运行的环境都是多线程的 而不论你自己是不是明确的开启了一个线程
既然多线程是不可避免的 那么要避免线程冲突就只能从共享资源来开刀了
二 线程安全的资源
其实线程安全很简单 就是指一个函数(方法 属性 字段或者别的)在同一时间被不同线程使用 不会造成任何线程冲突的问题 就说这个东西是线程安全的
接下来来谈谈什么样的资源是线程安全的
之所以使用资源这个词 是因为线程冲突不仅仅会发生在共享的变量set.add('1').上 两个线程同时对同一个文件进行读写 两个程序同时用同一个端口与同一个地址进行通信 都会造成线程冲突 只不过是作系统和帮我们协调了这些冲突而已
一个线程安全的资源即是指 在不同线程中使用不会导致线程冲突问题的资源
一个不能被改变的资源是线程安全的 比如说一个常量
const decimal pai = ;
//C++: const double pai = ;
因为pai的值不可能被改变 所以在不同的线程中使用也不会造成冲突 换言之它在不同的线程中同时被使用和在一个线程中被使用是没有区别的 所以这个东西 是 线程安全的
同样的 在 NET中 一个字符串的 实例 也是线程安全的 因为字符串的实例在 NET中也是不可以被改变的 一个字符串的实例一旦被创建 对其所有的属性 方法调用的结果都是确定的 永远不会改变的 所以 NET类库参考中String类型才有 此类型是线程安全的 与之类似的Type类型 Assembly类型 都是线程安全的
但string的 实例 是线程安全的 却不代表string的 变量 是线程安全的 换言之 设有一个静态变量
public static string str = ;
str不是线程安全的 因为str这个变量的字符串实例可以被任何线程修改
再考虑这样的例子
public static readonly SqlConnection connection = new SqlConnection( connectionString );
虽然connection本身虽然是线程安全的 但connection的任何成员都不是线程安全的
比如说 我在一个线程中对这个connection调用了Open方法 然后进行查询作 但在同一时刻 另一个线程调用了Close方法 这时候 就出现错误了
线程安全的资源其实还有很多 在此不一一赘述
那么对于属性和方法来说 怎么知道是不是线程安全的?
三 线程安全的函数
因为属性和方法都是函数组成的 所以我们探讨一下什么是线程安全的函数
上面我们说到 线程冲突的必要条件是多线程和共享资源 那么如果一个函数里面没有使用任何可能共享的资源 那么就不可能出现线程冲突 也就是线程安全的 比如说这样的函数
public static int Add( int a int b )
{return a + b;
}这个函数中所使用的所有的资源都是自己的局部变量 而函数的局部变量是储存在堆栈上的 每个线程都有自己的堆栈 所以局部变量不可能跨线程共享 所以这样的函数显然是线程安全的
public static void Swap( ref int a ref int b )
//C++: void Swap( in& a int& b )
{int c = a;
a = b;
b = c;
}因为ref的存在 使得函数的参数是按引用传递进来的 换言之a和b看起来是函数的局部变量 但实际上却是函数外面的东西 如果这两个东西是另一个函数的局部变量 倒也没有问题 如果这两个东西是全局变量(静态成员) 就不能确保没有线程冲突了 而在上个例子中 a和b在传入函数之时 就做了一个拷贝的动作 所以传进来的a b到底是全局变量还是静态成员都没有关系了
同样 这样的函数也 不是 线程安全的
public static int Add( INumber a INumber b )
//1.ArrayList: 元素单个,效率高,多用于查询C++: int Add( INumber a INumber b );
{return a Number + b Number;
//C++: return a >Number + b >Number;
}原因在于a和b虽然是函数的内部变量没错 但a Number和b Number却不是 它们不存在于堆栈上 而是在托管堆上 可能被其他线程更改
但只使用局部变量的函数在 NET类库中是很少的 但 NET类库中还是有那么多线程安全的函数 是为什么呢?
因为 即使一个函数使用了共享资源 如果其所使用的共享资源都是线程安全的 则这个函数也是线程安全的
比如说这样的函数
private const string connectionString = … ;
public string GetConnectionString()
{return connectionString;
同样的 我们可以得出 如果一个函数只调用线程安全的函数 只使用线程安全的共享资源
那么这个函数也是线程安全的
这里有一个容易被忽略的问题 运算符 并不是所有的运算符(尤其是重载后的运算符)都是线程安全的
四 互斥锁
有时候我们不得不面对线程不安全的问题 比如说在一开始提出来的那个例子 多线程完成 次任务 我们怎样才能解决这个问题 一个简单的办法就是给共享资源加上互斥锁 在C#中这很简单 比如一开始的那个例子
public static class Environment
}//…
void ThreadMod()//运行在每个线程的方法
{while( true )
{lock ( typeof( Environment ) )
break;//中断线程执行
DoSoming();//完成某个任务
count++;}}}
通过互斥锁 使得一个线程在使用count字段的时候 其他所有的线程都无法使用 而被阻塞等待 达到了避免线程冲突的效果
当然 这样的锁会使得这个多线程程序退化成同时只有一个线程在跑 所以我们可以把count++提前 使得lock的范围缩小 如这样
void ThreadMod()//运行在每个线程的方法
{while( true )
{lock ( typeof( Environment ) )
{if ( count++ >= )//如果达到任务指标
break;//中断线程执行
}DoSoming();//完成某个任务
}}
来聊聊SyncRoot的问题
用 NET的一定会有很多朋友困惑 为什么对一个容器加锁 需要这样写
lock( Container SyncRoot )
因为锁定一个容器并不能保证不会对这个容器进行修改 考虑这样一个容器
public class Collection
{private ArrayList _list;
public Add( object )
{_list Add( );
{get { return _list[index]; }
set { _list[index] = value;}
}}
lishixinzhi/Article/program/net/201311/13071
1、Bean的
if(!(o instanceof Customer)) return false;在Spring中,那些组成应用程序的主体(backbone)及由Spring IoC容器所管理的对象,被称之为bean。 简单地讲,bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。 而bean定义以及bean相互间的依赖关系将通过配置元数据来描述。
System.out.println(value);2、Bean的作用域
创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方(recipe)”。把bean定义看成一个配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。
不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在Ja Class级定义作用域。Spring Framework支持五种作用域(其中有三种只能用在基于web的Spring ApplicationContext)。
1)singleton
当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
注意:Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
3)request
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
考虑下面bean定义:
4)session
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
考虑下面bean定义:
5)global session
在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。
考虑下面bean定义:
单例的线程肯定不安全,所以action要配制成原型模式
是。根据查询CSDN博客网显示。
{if ( count >= )//如果达到任务指标1、简单的进行put或者get等之类的作是线程安全的,具体实现和concurrenthashmap的版本有关,1.7使用的分段锁机制去实现的,1.8之后采用的是cas加synchronized加红黑树实现,1.8之后并行度不会局限于segment的大小。虽然本身是线程安全的,但是组合起来还是有问题,这个问题也就是竞态条件,竞态条件指的是程序的执行结果依赖于线程的执行顺序。
2、concurrenthashmap的get和set作是线程安全的,但是我设获取到中的值再进行加1作,mapsetgetkey加1:这个作就是线程不安全的,设此时两个线程去get这个key,get出来的值是相同的,然后进行加1,再set到中,}public Long getId(){得到的结果肯定是对原来的值只增加了1,而不是我们认为的2,但是如果一个线程执行完mapsetgetkey加1:另一个线程再去执行这个作,那么得到的结果便是我们想要的结果。也就是说这个结果依赖于线程的执行顺序。因此在线程安全的方法中进行组合作要考虑竞态问题。为了能够保证线程安全,也需要进行加锁作。
Spring框架里的bean,或者说组件,获取实例for(Iterator
单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法)。 public class PersonController{
private PersonServ personServ;
public void setFirstName(HttpServletRequest request){
}public String getFirstName(){
return personServ.getFirstName();
}}当有两个线程访问PersonController时,一个先调用setFristName来设置firstname, 而另一个线程调用getFirstName就会得到个线程所设的值。这是因为,PersonController是缺省情况下为单一实例(Singleton),而personServ也是一个单独的实例被注入到personController里。这种情况下,多线程是不安全的,除非能够保证每个方法(mod)都不会改变所共享的对象的属性或状态。
线程安全的方法
public class PersonController {
private PersonServ personServ
public void setFirstName(HttpServletRequest request){
Person person = new Person();
person.setFirstName();
}}上面的例子是一种线程安全的写法,例如,线程A访问setFirstName方法,当它执行到person.setFirstName()后,系统决定挂起这个线程,转向执行B,B做了同样的事情并保存。然后,系统再执行被挂起的A线程,由于A线程有自己的堆栈状态,而且它本身没有与其他线程共享的对象实例或状态。需要补充说一下,这里定personServ.sePerson()方法是线程安全的,否则,还需要调查personServ.sePerson()是否线程安全。
因此,在大多数情况下,spring bean是非线程安全的,或者说,如果你不告诉它如何管理对象或方法的线程安全,那么就会潜在线程安全问题。
spring bean因此给出了以下的线程安全的可用声明(annotation),你可以根据实际情况,分别定义你的类或方法。
1.将并行作转化成串行作,常用的实现方式:
a.加锁,使临界区资源,只能有一个线程/进程可以访问。
b.执行业务逻辑的工作线程只分配一个list 和set 有共同的父类 它们的用法也是一样的 的不太就是set中不能有相同的元素 list中可以,这也可以从根本上防止并发问题的产生。
2.基于作系统提供给上层应用的原子作能力,实现"CAS"的原子作。
以上方案各有优劣,都有各自的使用场景,这里我们不做过多比较。其实工作遇到的问题,在一些开源软件中也会遇到,在一些比return false;较开源软件中又是如何解决线程安全问题呢?或者能从中学到一些知识
ja中 i++ 或者++i这些都不是线程安全的
在ja中解决这个问题可以使用synchronized或者AtomicInteger包装类,当然会增加开销.
JDK API 1.6文档中该类的描述和方法如下:
AtomicInteger 可用在应用程序中(如以原子方式增加的计数器),并且不能用于替换 Integer。但是,此类确实扩展了
Number,允许那些处理基于数字类的工具和实用工具进行统一访问。
int addAndGet(int delta)
以原子方式将给定值与}虽然这个函数使用了一个共享资源connectionString 但因为这个资源是线程安全的 所以这个函数还是线程安全的当前值相加。
boolean compareAndSet(int expect,
int update)
如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
int decrementAndGet()
以原子方式将当前值减 3 Set接口有两个实现类:1。
以 double 形式返回指定的数值。
float floatValue()
以 float 形式返回指定的数值。
int get()
获取当前值。
int getAndAdd(int delta)
以原子方式将给定值与当前值相加。
以原子方式将当前值减 1。
int getAndIncrement()
以原子方式将当前值加 1。
int getAndSet(int newValue)
以原子方式设置为给定值,并返回旧值。
int incrementAndGet()
以原子方式将当前值加 1。
int intValue()
以 int 形式返回指定的数值。
void lazySet(int newValue)
设置为给定值。
long longValue()
以 long 形式返回指定的数值。
void set(int newValue)
设置为给定值。
String toString()
返回当前值的字符串表示形式。
boolean weakCompareAndSet(int expect,
int update)
如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。
++本身不是线程安全的,需要在同步块或同步方法中。
常用的类有一下几种:
List结构的类:ArrayList类,LinkedList类,Vector类,Stack类
Map结构的类:HashMap类,Hashtable类
Set结构的类:HashSet类,TreeSet类
Queue结构的:Queue接口
HashMap和Hashtable的区别:
HashMap和Hashtable都是ja的类,都可以用来存放ja对象,这是他们的相同点
以下是他们的区别:
1.历史原因:
Hashtable是基于陈旧的Dictionary类的,HashMap是ja 1.2引进的Map接口的一个现实。
2.同步性:
Hashtable是同步的,这个类中的一些方法保证了Hashtable中的对象是线程安全的,而HashMap则是异步的,因此HashMap中的对象并不是线程安全的,因为同步的要求会影响执行的效率,所以如果你不需要线程安全的结合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率,我们一般所编写的程序都是异步的,但如果是端的代码除外。
3.值:
HashMap可以让你将空值作为一个表的条目的key或value
ArrayList和Vector的区别:
ArrayList与Vector都是ja的类,都是用来存放ja对象,这是他们的相同点,
区别:
1.同步性:
Vector是同步的,这个类的一些方法保证了Vector中的对象的线程安全的,而ArrayList则是异步的,因此ArrayList中的对象并不 是线程安全的,因为同步要求会影响执行的效率,所以你不需要线程安全的那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必 要的性能开销。
2.数据增长:
从内部实现的机制来讲,ArrayList和Vector都是使用数组(Array)来控制中的对象,当你向两种类型中增加元素的时候,如果元素的数目超过了内部数组目前的长度他们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以你获得的这个所占的空间总是比你实际需要的要大,所以如果你要在中保存大量的数据,那么使用Vector有一些优势,因为你可以通过设置的初始大小来避免不必要的资源开销。
1)如果要求线程安全,使用Vector,Hashtable
2)如果不要求线程安全,使用ArrayList,LinkedList,HashMap
4)如果数据量很大,又要求线程安全考虑Vector
2.Vector: 元素单个,线程安全,多用于查询
3.LinkedList:元素单个,多用于插入和删除
4.HashMap: 元素成对,元素可为空
5}public object this[ int index ].HashTable: 元素成对,线程安全,元素不可为空
ArrayList
底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。
而在LinkedList的底层是一种双向循环链表。在此链表上每一个数据都由三部分组成:前指针(指向前面的的位置),数据,后指针(指向后面的的位置)。一个的后指针指向个的前指针,形成一个循环。
双向循环链表的查询效率低但是增删效率高。
ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。
LinkedList
经常用在增删作较多而查询作很少的情况下:队列和堆栈。
队列:先进先出的数据结构。
栈:后进先出的数据结构。
注意:使用栈的时候一定不能提供方法让不是一个元素的元素获得出栈的机会。
Vector
(与ArrayList相似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。
面试经验(知识点):
ja.util.stack(st{public static int count = ;//全局计数器ack即为堆栈)的父类为Vector。可是stack的父类是最不应该为Vector的。因为Vector的底层是数组,且Vector有get方法(意味着它可能访问到并不属于一个位置元素的其他元素,很不安全)。
对于堆栈和队列只能用push类和get类。
Stack类以后不要轻易使用。
实现栈一定要用LinkedList。
(在JAVA1.5中,collection有queue来实现队列。)
Set-HashSet实现类:
遍历一个Set的方法只有一个:迭代器(interator)。
HashSet中元素是无序的(这个无序指的是数据的添加顺序和后来的排列顺序不同),而且元素不可重复。
在Object中除了有finalize(),toString(),equals(),还有hashCode()。
HashSet底层用的也是数组。
当向数组中利用add(Object o)添加对象的时候,系统先找对象的hashCode:
int hc=o.hashCode(); 返回的hashCode为整数值。
Int I=hc%n;(n为数组的长度),取得余数后,利用余数向数组中相应的位置添加数据,以n为6为例,如果I=0则放在数组a[0]位置,如果I=1,则 放在数组a[1]位置。如果equals()返回的值为true,则说明数据重复。如果equals()返回的值为false,则再找其他的位置进行比 较。这样的机制就导致两个相同的对象有可能重复地添加到数组中,因为他们的hashCode不同。
如果我们能够使两个相同的对象具有相同hashcode,才能在equals()返回为真。
在实例中,定义student对象时覆盖它的hashcode。
因为String类是自动覆盖的,所以当比较String类的对象的时候,就不会出现有两个相同的string对象的情况。
现在,在大部分的JDK中,都已经要求覆盖了hashCode。
结论:如将自定义类用hashSet来添加对象,一定要覆盖hashcode()和equals(),覆盖的原则是保证当两个对象hashcode返回相同的整数,而且equals()返回值为True。
如果偷懒,没有设定equals(),就会造成返回hashCode虽然结果相同,但在程序执行的过程中会多次地调用equals(),从而影响程序执行的效率。
DLL有个共同的特点就是都有一个初始化函数,一个资源释放函数,其他几个函数都是核心功能函数。而且这些DLL有时会被多个进程同时调用,这就牵扯到多进程的多线程调用DLL的问题。有点绕口,以下我根据我实践中遇到的问题,分四种情况分享一下我解决此类问题的经验:
结论:在考虑并发的情况下用Vector(保证线程的安全)。1、动态库只有一个导出函数。
这种情况非常少,也是最容易处理的情况。这种情况下编写函数时,只需要考虑不要有冲突的全局数据就可以了。这里的全局数据包括了在堆中分配的数据块和静态全局变量等。如果存在这样的全局数据,那么进程中的不同线程访问这个函数就会造成冲突。
解决办法也很简单,就是尽量用堆栈(stack)来解决问题。由于堆栈的所有人是线程,所以它必然是线程安全的。当然也要注意避免堆栈溢出。
我们都知道,如果要在函数再次调用时保留前一次调用的状态,可以使用静态变量。但如果你要保持函数的线程安全,那么静态变量是不能用的,因为静态变量是全局的,是属于进程的,也就是属于进程内线程共享的。所以如果确实需要在同一线程中保持函数的状态,相当于在不同次调用间传递参数,可以考虑使用静态全局线程局部变量,即:
该变量定义就使编译器保证了tls_i是对应于每个线程的,即每个线程都一个tls_i的副本(copy),这样必然就是线程安全的。
2、动态库导出了多个函数,而且多List list = new ArrayList();个函数间存在数据传递。
就像前面说的,一般DLL都导出多个函数,一个初始化,一个资源释放,其他为核心功能函数。这些函数间极有可能发生数据传递。如果一个初始化函数是在线程A中调用的,而核心功能函数是在线程B中调用的,那么线程A初始化函数的资源就无法对应线程B中的核心功能,此外还有核心功能函数间的数据传递,这样的DLL就不是线程安全的,必然导致错误。
解决办法是由用户(即使用DLL的人)保证这些导出函数是在一个线程中调用。但这样会很大程度上限制接口的设计和用户的使用自由度。所以的方法是函数只管自己的线程安全,不同函数传递数据用动态TLS,线程局部存储。
比如:我在全局定义了一个变量,用于存储当前线程局部存储的index ID。
当调用分配资源的函数时,调用动态TLS函数TlsAlloc,分配一个ID,将其记录在全局的线程安全的tls_i变量,并通过TlsSetValue函数将数据保存在线程安全的区域;当调用获取资源的函数时,通过TlsGetValue获取资源,处理完成后,调用Tlsfree对TLS index释放,以便新线程占有。
这样,只要DLL中每个函数保证其局部是线程安全的,函数间传递数据通过TLS(静态和动态),就可以实现整个DLL的线程安全。
3、限制访问DLL中某一函数的线程数目。
有时候,对于DLL中的某一个函数的访问线程数目是有限制的,超过了限制其他线程就得等一定的时间,一定的时间过后如果还不能得到执行机会,那就返回超时。这样的设计对用户来说是友好的,而且很实用,有的商业程序确实是按照允许用户访问的通道数目来计价的。
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
// pointer to security attributes
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName // pointer to semaphore-object name);对于信号量,它每WaitForSingleObject一次(当然是要进入),其状态值(一个整数)就减1,使用完ReleaseSemaphore其状态值就加1,当其状态值为0时信号量就由有信号变为无信号。利用信号量的这一特性,我们在初始化时将信号量的初始值(第2个参数)设置为限制的线程访问数目。在要限制访问线程数目的函数内部,通过调用WaitForSingleOject获取控制权,并指定一个等待时间(这个由配置文件指定),根据情况超时返回,使用完ReleaseSemaphore释放对占用,让其他线程可以调用这个函数。
4、多进程情况下的多线程安全DLL。
现实情况是,多进程情况下,一般不是简单的多进程共享一个Semaphore就可以了。多进程间需要互通很多信息。一般的解决办法是,采用共享数据段。
#pragma data_seg("share")
int share_data;
#pragma data_seg()
通过pragam编译器指令生成了一个名叫share的共享数据段,这样对于变量share_data就可以多进程共享的了。
版权声明:本文内容由互联。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发 836084111@qq.com 邮箱删除。