线程安全与锁优化
面向过程是将数据和过程当作两个独立部分来考虑,是从计算机角度考虑问题
面向对象是将数据和行为看作对象一部分
线程安全
共分为5类:
1.不可变
通过final来修饰的基本类型的属性具有不可变性,所以是线程安全的。
2.绝对线程安全
绝对线程安全是不管运行在什么环境下,调用者都不需要任何额外的同步措施,绝大部分说线程安全的其实都不是绝对线程安全 ,如Vector,仍需要同步代码块去修饰,才能保证绝对线程安全
此程序会产生异常,说明Vector不是线程安全的
private static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args){
while(true){
for(int i = 0;i < 10; i++){
vector.add(i);
}
Thread removeThread = new Thread(new Runnable(){
@Override
public void run(){
for(int i = 0;i < vector.size();i++){
vector.remove(i);
}
}
});
Thread printThread = new Thread(new Runnable(){
@Override
public void run(){
for(int i = 0;i < vector.size();i++){
System.out.println(vector.get(i));
}
}
});
removeThread.start();
printThread.start();
while(Thread.activeCount() > 20);
}
}
Thread removeThread = new Thread(new Runnable(){
@Override
public void run(){
for(int i = 0;i < vector.size();i++){
vector.remove(i);
}
}
});
Thread printThread = new Thread(new Runnable(){
@Override
public void run(){
synchronized(vector){
for(int i = 0;i < vector.size();i++){
System.out.println(vector.get(i));
}
}
}
});
加入同步锁机制,确保Vector的线程绝对安全
3.相对线程安全
相对线程安全就是我们所见的线程安全,他需要保证对这个对象单词的操作是线程安全的,如Vector,HashTable,Collections的synchronizedCollection()方法包装的集合。
4.线程兼容
线程兼容是指对象本身并不是线程安全的,但可以通过使用同步手段来达到线程安全,如ArraytList和HashMap等
5.线程对立
就是不管采用什么同步措施,都无法实现在多并发线程下使用代码
线程安全的实现方法
1.互斥同步
互斥同步是一种最常见也最主要的手段,即通过临界区,互斥量,信号量来实现多线程并发的互斥同步。
典型的例子就是synchronized关键字,它是在同步代码块前后形成monitorenter和monitor exit这两个字节码命令来实现对象的上锁和解锁,这里有一个锁计数器的概念,即:当通过循环等同一线程可以反复进入同步代码块,这样锁计数器会一直进行加一和减一操作,直到所有循环全部走完,锁计数器清空为零才会真正释放锁。
synchronized需要不断地切换用户态和内核态,所以是一个重量级地所应用,如果仅仅为了对get(),set()方法加锁,那么就内核切换时间都要大于程序执行时间了,明显不合算。所以又引入了ReentrantLock等其他锁。
2.非阻塞同步
互斥同步是一种悲观同步,每次都需要进行加锁去锁,而非阻塞同步则是乐观同步,即当冲突未发生时,就不对程序进行加锁,让程序直接操作,只有在程序发生冲突时,才会对冲突部分进行补偿。
此时,大大提高了程序地运行效率,程序运行起来更加happy了。
锁消除
即对不可能存在共享数据竞争地锁进行消除,其实Java代码中地同步措施远超想象,就全靠JVM对锁进行消除
public String concatString(String s1, String s2,String s3){
return s1 + s2 + s3;
}
最典型的字符串相加
内部完成其实是这样的:
public String concatString(String s1,String s2,String s3){
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString();
sb其实就是锁
}
锁粗化
当又连续操作对同一对象反复加锁和解锁,那么JVM就自动将其合并只进行一次加解锁操作。
轻量级锁和偏向锁
轻量级锁用于程序在未发生冲突时避免使用互斥量,这样可以大大减少开销
偏向锁则用于在程序未发生冲突时连整个同步状态都取消掉。