文章目录
- 前言
- 一、什么是线程安全?
- 二、线程安全解决方式
- 1.synchronized块
- 2.synchronized方法
- 3.Lock锁
- 总结
前言
在程序的执行过程中为了保证程序结果的正确性,我们在开发过程中需要注意线程安全问题
一、什么是线程安全?
多个线程访问同一个对象时,如果不考虑线程在运行环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果那么这个对象就是线程安全的。一般线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。
二、线程安全解决方式
1.synchronized块
锁的对象是this对象,多个线程来操作这个synchronized块,只有线程拿到这个锁对象才能执行这个代码块中的代码,否则只能等这个线程执行完并且释放这个锁之后才能拿到锁,而拿到锁之后才能操作这段代代码块 ,保证多线程访问时同时只有一个线程能操作这个代码片段,因此保证了线程的安全性。
synchronized(this) {
for (int j = 0; j < 100; j++) {
i++;
}
}
2.synchronized方法
synchronized方法对其他的synchronized方法和synchronied块是阻塞的,也就是说只要一个线程在执行一个synchronized方法中的代码,是不能同时执行其他的synchronized方法和其他的synchronied块的
public synchronized void increase() {
i++;
}
3.Lock锁
可使用可重入锁来锁住会发生线程安全的代码段,那么什么是可重入锁呢?可重入锁是指一个线程拿到锁对象之后,还是可以再次拿到这把锁的,而且不会出现死锁。又称为“独占锁”。
ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现锁的关键)来实现锁的获取与释放。其可以完全替代 synchronized 关键字。
public class Demo3 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
static class Ticket implements Runnable{
private int count = 10;
Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock();
if(count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"买票成功,余票:"+count);
}else{
break;
}
lock.unlock();
}
}
}
}
总结
可以使用synchronized块,synchronized方法以及可重入锁来实现线程安全问题,其中synchronized块和synchronized方法是隐式锁,Lock是显示锁,保证线程安全可以加锁,但是注意不要一下子锁太多代码段,加锁会影响性能,锁住的代码段越多,性能可能会越差,所以选择锁的时候要选择最关键的代码段上锁。