Java共享模型带来的线程安全问题
两个线程对一个初始值为0的静态变量一个线程自增5000次,一个线程自减5000次,所带来的线程安全问题。
/**
* 多线程对共享变量操作线程安全问题案例
*
* @Author warrior
**/
public class SyncDemo {
//共享变量
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
/**
* 创建按一个线程,对共享变量count自增操作5000次
*/
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
increment();
}
}
});
/**
* 创建按一个线程,对共享变量count自减操作5000次
*/
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
decrement();
}
}
});
//启动线程
thread1.start();
thread2.start();
//等待线程执行完成
thread1.join();
thread2.join();
System.out.println("count:" + count);
}
/**
* 对共享变量count自增操作
*/
public static void increment() {
count++;
}
/**
* 对共享变量count自减操作
*/
public static void decrement() {
count--;
}
}
问题分析
在IDEA 安装jclasslib插件,查看相关类方法字节码。
count++ JVM字节码指令
getstatic //获取静态变量值
iconst_1 //将int常量1压入操作数栈
iadd //自增
putstatic //修改后的值存入静态变量
return
count-- JVM字节码指令
getstatic //获取静态变量值
iconst_1 //将int常量1压入操作数栈
isub //自减
putstatic //修改后的值存入静态变量
return
如果单线程执行上面JVM操作指令是顺序执行的,不会出现交错执行,不会存在线程安全问题。在多线程下执行上面JVM指令,可能交错运行,就会存在线程安全问题。
临界区
一段代码块内如果存在对共享资源的多线程读写操作,这段代码块称为临界区,其共享资源为临界资源。一个程序开启多个线程运行本身是没有问题的,但是多个线程读取共享资源也是没有问题,在多个线程对共享资源进行读写操作时发生指令的交错执行此时就会出现线程安全问题。
竞态条件
多个线程在临界区内执行,由于代码执行序列不同而导致结果无法预测,称之为发生了竞态条件。为了避免临界区的竞态条件发生,有多种方式可以保证:
- 阻塞式的解决方案:synchronized、Lock
- 非阻塞式的解决方案:原子变量
synchorized
在Java中互斥和同步都可以使用synchronized关键字来实现,但它们还是有区别的,互斥是保证临界区的竞态条件的发生,同一时刻只能有一个线程执行临界区的代码。同步是由于线程的执行先后、顺序不同,需要一个线程等待其他线程执行到某个点。synchronized同步代码块是Java提供的一种原子性内置锁,Java中的每一个对象都可以把它当作一个同步锁来使用,这些Java使用者看不到的锁被称为内置锁或监视锁。
加锁方式
解决共享问题
方式一:
/**
* 对共享变量count自增操作
*/
public synchronized static void increment() {
count++;
}
/**
* 对共享变量count自减操作
*/
public synchronized static void decrement() {
count--;
}
方式二:
private static Object lock = new Object();
/**
* 对共享变量count自增操作
*/
public static void increment() {
synchronized (lock) {
count++;
}
}
/**
* 对共享变量count自减操作
*/
public static void decrement() {
synchronized (lock) {
count--;
}
}
synchronized实际上是使用对象锁保证了临界区内代码的原子性。