线程安全问题的解决方法
1. 问题描述
- 当某个线程的操作还未结束的时候,其他线程就参与了操作,导致线程出现问题。
2. 解决方法:同步代码块
-
方法格式:
synchronized (同步监视器(锁)) { // 需要被同步的代码 // 等于操作共享数据的代码 }-
说明:需要同步的代码块 == 操作共享数据的代码。
-
说明:同步监视器 == 锁。任何一个类的对象都可以充当锁。多线程共用同一把锁。
如果是实现 Runnable 接口的方法来创造线程一般使用当前对象 this 作为同步监视器。
如果是继承 Thread 类的方法创建线程一般使用当前类本身 类名.class 来作为同步监视器。
-
3. 解决方法:同步方法
-
方法格式:
-
Runnable 接口实现创造线程的同步方法
private synchronized 方法类型 方法名() { // 需要被同步的代码 // 等于操作共享数据的代码 } -
Thread 类被继承创造线程的同步方法
private static synchronized 方法类型 方法名() { // 需要被同步的代码 // 等于操作共享数据的代码 }
-
-
说明:
- 通过将需要操作共享数据的代码方法另外一个方法中,并且用 synchronized 关键字修饰。
- 如果是 Thread 类的同步方法,需要用 static 关键字修饰
- 同步方法会自动使用同步监视器。Runnable 用的是当前对象 this; Thread 用的是当前类本身 类名.class
4. 解决方法:ReentrantLock 方法
-
方法格式:
-
Runnable 接口实现创造线程的lock() 方法
class lockTest implements Runnable { // 1. 实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { // 2. 调用上锁方法 lock() lock.lock(); // 需要被同步的代码 // 操作共享数据的代码 } finally { // 3. 调用解锁方法 unlock() lock.unlock(); } } } } -
Thread 类被继承创造线程的lock() 方法
class lockTest implements Runnable { // 1. 实例化ReentrantLock private static ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { // 2. 调用上锁方法 lock() lock.lock(); // 需要被同步的代码 // 操作共享数据的代码 } finally { // 3. 调用解锁方法 unlock() lock.unlock(); } } } }
-
-
说明:
-
使用过后必须释放锁,需要手动调用解锁方法。
-
ReentrantLock 方法不会自动解锁。
-
new ReentrantLock(true) :按照访问顺序执行线程。先进先出。不会出现刚释放CPU控制权就又获取CPU控制权的情况。
new ReentrantLock(false) :随机获取。可能会出现同一线程不断获取CPU控制权
-
使用 try —— finally 结构调用解锁方法是为了防止忘记手动解锁造成死锁。
-
4. 相同与不同
- 相同
- 都可以解决线程安全问题
- 不同
- Lock 需要手动实现上锁和解锁;synchronized 可以自动实现上锁和解锁功能。
- Lock 只有代码块锁;synchronized 又代码块锁和方法锁。
- 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且又更好的扩展性。
5.个人重点
- 同步代码不可多,同步代码不可少。多了会变慢,使资源不能更好利用。少了会出错,出现线程安全问题。
- 宁愿多也不要少。可以慢,不能错。
- 尽量使用锁可以是任何类的特点。
- 建议优先使用 lock。