1线程安全的定义
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
2.两种状态
(1)状态
对象的状态是指存储在状态变量(例如实例和静态域)中的数据。
【举例】
Person p=new Person(Li);
p.age=12;
对象Li今年12岁,这是对象Li存于的一个状态
(2)共享状态
“共享”意味着变量可以由多个线程同时访问
(3)可变状态
“可变”则意味变量的值在其生命周期内方式变化
【举例】
两个线程进行计数
T1()
{count++;
}
T2()
{count++;
}
两个线程都访问count,所以count是共享变量,都执行count++操作,所以count是可变变量。
3.竞态条件
在并发编程中,由于恰当的执行时序而出现不正确结果的情况,叫做竞态条件。
【举例】
例1“读取—修改—写入”
Static int count;
GetCount(int count)
{return count;
}
T1()
{count=GetCount();
count++;
}
T2()
{count=GetCount();
count++;
}
这样会出现竞态条件。
初始count=0;
(1) T1获取CPU,执行GetCount(),count=0;
(2) T2获取CPU,执行GetCount(),count=0;
(3) T1获取CPU,这行count++,返回,count=1;
(4) T2 获取CPU,执行count++,返回,count=1;
进行了两次计数,但是计数的值为1.
例2“延迟初始化”
T1()
{if(instance==null)
Instance=new Object();
Return instance;
}
T2()
{if(instance==null)
Instance=new Object();
Return instance;
}
这样也会出现竞态条件
初始instance=null;
(1) T1获取CPU,执行if(instance==null)
(2) T2获取CPU,执行if(instance==null)
(3) T1获取CPU,执行Instance=new Object();instance被实例化了
(4) T2获取CPU,执行Instance=new Object();instance又被实例化了
(5) T1获取CPU,返回intance,但是返回的是T2实例化的对象
这两个例子是比较典型的静态条件的例子。
4.如何保证线程的安全
4.1原子性
一组操作要么全都执行,要么全都不执行
4.2加锁操作
Lock{
保护的代码块。
}
保证了原子性,但是,性能比较低
4.3重入
当某个线程请求由其他线程所持有的锁的时候,发出请求的线程就会阻塞。但是,如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求将会成功,这就是“重入”。
【举例】
Public class Widget{ Public lock void doSomething() { } } Public class LoggingWidget extends Widget(){ Public lock void doSometing(){ Super.doSomething(); } } 如果不能重入的话, LoggingWidget Lw=new LoggingWidget(); Lw.doSometing();//执行这条执行的时候,父类和子类中的doSometing都上锁了。当使用Super.doSomething()的时候将无法执行。
如果可以重入的话,这个doSometing函数的锁已经被线程所持有,所以它可以获得所有doSomething的锁,就不会陷入“死锁”。