线程安全问题
什么是线程安全?
多个线程同时访问了相同的资源,并对该资源进行写的操作,使得资源发生改变时就会产生线程安全问题。只有资源没有发生变化,多个资源同时进行读取操作的时候线程才是安全的。
例:
class SaleTicket implements Runnable(
// 初始化当前剩余票数
private Integer count = 100;
@Override
public void run() {
while (ticketCount>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.sale();
}
}
public void sale(){
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
ticketCount--;
}
}
)
在测试方法中启动多个线程同时进行买票操作:
class TestSaleTicket{
public static void main(String[] args){
SaleTicket sale = new SaleTicket();
Thread t1 = new Thread(sale,"售票窗口1");
Thread t2 = new Thread(sale,"售票窗口2");
t1.start();
t2.start();
}
}
该方法执行之后可以看到两个线程会同时卖一张票,此时这段代码就出现了线程安全问题。
解决线程安全问题的方法
- 同步代码块:多个线程抢同一个锁,没有抢到锁的线程等待抢到锁的线程执行完毕之后释放锁。
synchronized(锁对象){
/* 操作共享数据的代码
* ......
*/
}
- 代码块中的锁对象可以是任意对象。
- 所有线程使用的锁对象时同一个对象。
- 锁对象锁住同步代码块,只能让一个线程在代码块中执行。
如上代码中的sale()方法可以写成:
public void sale(){
synchronized(this){
if(count > 0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
ticketCount--;
}
}
}
- 使用同步方法:将需要访问资源的代码封装成一个方法,并且加上
synchronized
修饰符。 例:
public synchronized void sale(){
if(count > 0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
ticketCount--;
}
}
- 静态同步方法(静态方法+同步代码块):静态方法的锁对象是本类的class属性。 例:
class SaleTicket implements Runnable{
// 初始化定义车票的数量
private static Integer count = 100;
@Override
public void run() {
while (ticketCount>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sale();
}
}
public static void sale(){
synchronized(SaleTicket.class){
if(count > 0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
ticketCount--;
}
}
}
}
- Lock锁:java.util.concurrent.locks包中的Lock接口
两个方法:
void lock() 获取锁
void unlock() 释放锁
Lock接口实现类java.util.concurrent.locks.ReentrantLock
使用步骤:
在成员位置创建一个ReentrantLock对象 在可能出现安全问题的代码前调用lock方法获取锁 在可能出翔安全问题的代码后调用unlock方法释放锁
private Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
if(ticketCount > 0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(100-ticketCount+1)+"张票");
ticketCount--;
}
lock.unlock();
}
非静态同步方法使用的是this锁。
使用synchroized时最好使代码块包裹可能会产生线程安全的代码,不要包裹无关线程安全问题的代码,否则会影响程序的效率。