多行代码原子性问题描述
- AtomicInteger类只能保证"变量"的原子性操作,而对多行代码进行"原子性"操作,使用AtomicInteger类就不能达到效果了。
先看现象,代码演示
public class TicketsSaleRunnable implements Runnable {
int tickets = 100;
@Override
public void run() {
while (true) {
// 如果票卖完了,就退出循环
if (tickets < 1) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("tickets:"+ tickets--);
}
}
public static void main(String[] args) {
// 三个窗口
TicketsSaleRunnable r = new TicketsSaleRunnable();
Thread tA = new Thread(r);
Thread tB = new Thread(r);
Thread tC = new Thread(r);
tA.start();
tB.start();
tC.start();
//...
//tickets:96
//tickets:96
//...
//tickets:4
//tickets:3
//tickets:2
//tickets:1
//tickets:0
//tickets:-1
}
}
我们可以看到两个明显的问题:
- 相同的票卖了两次或多次
- 剩余的票居然会有负值
产生线程安全问题的原因:
线程安全问题都是由全局变量及静态变量引起的。而每个线程操作这个变量都需要很多步骤:获取变量的值、打印变量的值、更改变量的值,而一个线程在执行某一步骤时都可能被暂停,而另一个线程会执行,这同样会导致多个线程访问同一个变量,最终导致这个变量的值不准确。
解决方法
同步块
在Java中提供了同步机制,可以有效地防止资源冲突。同步机制使用synchronized关键字。
其语法如下:
synchronized(Object){
}
代码演示
public class TicketsSaleRunnable implements Runnable {
int tickets = 100;
@Override
public void run() {
while (true) {
synchronized ("") {
// 如果票卖完了,就退出循环
if (tickets < 1) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"tickets:"+ tickets--);
}
}
}
public static void main(String[] args) {
TicketsSaleRunnable r = new TicketsSaleRunnable();
Thread tA = new Thread(r);
Thread tB = new Thread(r);
Thread tC = new Thread(r);
tA.start();
tB.start();
tC.start();
}
}
通常将共享资源的操作放置到 synchronized 定义的区域内,这样其他线程也获取到这个锁时, 必须等待锁被释放时才能进入该区域. Object为任意一个对象, 每一个对象都存在一个标志位, 并具有两个值, 0 或 1. 一个线程运行到同步块时首先检查该对象的标志位,如果为 0 状态,表明此同步块中存在其他线程运行. 这时该线程处于就绪状态, 直到处于同步块中的线程执行完同步块中的代码为止. 这时该对象的标识位被设置为 1, 该线程才能执行同步块中的代码, 并将Object对象的标识位设置为0,防止其他线程执行同步块中的代码.
同步方法
同步方法就是在方法前面修饰 synchronized 关键字的方法, 其语法如下:
synchronized void f(){}
代码演示
public class TicketsSaleRunnable implements Runnable {
int tickets = 100;
public synchronized void saleTickets(){
while (true) {
// 如果票卖完了,就退出循环
if (tickets < 1) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("tickets:"+ tickets--);
}
}
@Override
public void run() {
saleTickets();
}
}
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
代码演示
public class TicketsSaleRunnable implements Runnable {
int tickets = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 上锁
lock.lock();
// 如果票卖完了,就退出循环
if (tickets < 1) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("tickets:" + tickets--);
// 解锁
lock.unlock();
}
}
}
以上就是三种解决线程安全问题的方法.