线程安全问题
关注数据在多线程并发环境下是否安全。
1、什么时候数据会在多线程并发的环境下会存在安全问题?
三个条件:
条件1:多线程并发
条件2:有共享数据
条件3:共享数据有修改行为
满足以上三个条件后,就会存在线程安全问题。
2、怎么样解决线程安全问题?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就会存在安全问题。
如何解决?
线程排队执行。(不能并发) 用排队解决线程安全问题。
这种机制被称为: 线程同步机制。
专业术语叫: 线程同步,实际上就是线程不能并发了,必须排队执行
解决线程安全问题: 使用线程同步机制。
线程同步就是线程排队了,线程排队就会牺牲一部分效率。但是,没办法,数据安全才是第一位,只有数据安全了,我们才能谈效率,数据不安全,就没有效率那回事了。
3、说到线程同步,就涉及到:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2也不管t1,谁也不需要等谁,这种编程模型就叫作:异步编程模型。
其实就是:多线程并发(效率较高)
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待线程t2执行结束,或者说线程t2执行时,必须等待线程t1执行结束,俩个线程之间发生了等待关系,这就是同步编程模型。
线程同步:
同步就是排队。由于同一进程的多个线程共享同一块存储空间,再带来方便的同时,也带来了访问冲突问题,为保证数据在方法中被访问时的正确性,于是在访问时加入了 锁机制 synchronized,当一个对象获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。但是存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程的竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级到置,引起性能问题;
4、Java中有三大变量
**实例变量:**在堆中。
**静态变量:**在方法去中。
**局部变量:**在栈中 。
以上三大变量中:
局部变量永远都不会存在线程安全问题,因为局部变量不共享。(一个线程一个栈)
实例变量在堆中,堆只有一个。
静态变量在方法区中,方法区只有一个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题 。
如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题,选择StringBuilder。
StringBuffer效率较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap , HashSet是非线程安全的。
Hashtable是线程安全的。
5、synchronized有三种写法:
第一种:同步代码块
灵活
synchronized (线程共享对象 Obj)
同步代码块
}
**Obj **称之为 同步监视器 。 Obj 可以是任何对象,但是推荐使用共享对象作为同步监视器
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体
第三种:在静态方法上使用synchronized
表示找类锁
类锁永远只有一把
就算创建了10000个对象,类锁也只有一把
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this ,就是这个对象本身,
或者class。
- 对象锁:一个对象一把锁,100个对象100把锁。
- 类锁:100个对象也只有一把类锁。
6、Lock(锁)
扩展
- JDK 5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对象的独占访问,每一次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
- ReentrantLock 类实现了 Lock ,它拥有与synchronized 相同的并发性和内存语义,在实现线程安全控制中,常用 ReentrantLock ,可显示加锁,释放锁。
//定义lock 锁
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); //加锁
try{
.....
}
finally{
lock.unlock(); //解锁
}
}
Lock 与 synchronized 的对比
- Lock是显式锁(手动开启和关闭锁,不能忘记关锁)synchronized是隐式锁,出了作用域自动释放
- Lock锁只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且有更好的扩展性(提高更多的子类)
7、以后在开发中如何解决线程安全问题?
一上来就使用线程同步?synchronized
不是,synchronized会让程序的执行效率变低,用户体验不佳。系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替实例变量和静态变量。
第二种方案:如果必须是实例变量,那么就可以考虑创建多个对象,这样实例变量的内存就不共享了,(一个线程对应一个对象,100线程对应100对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,也不能创建多个对象,这个时候就选择synchronized
线程同步机制。