【并发编程】从一个程序入门synchronized

看这么一段代码,思考为什么结果不是0?

public class SyncDemo {

	private static int count = 0;

	public static void increment() {
		count++;
	}

	public static void decrement() {
		count--;
	}

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 10000; i++) {
				increment();
			}
		}, "t1");
		Thread t2 = new Thread(() -> {
			for (int i = 0; i < 10000; i++) {
				decrement();
			}
		}, "t2");
		t1.start();
		t2.start();
		t1.join();
		t2.join();

		System.out.println(count);
	}
}

问题分析

  • Java 中对静态变量的自增,自减并不是原子操作。

i++的JVM 字节码指令

  • getstatic i // 获取静态变量i的值
  • iconst_1 // 将int常量1压入操作数栈
  • iadd // 自增

i--的JVM 字节码指令

  • getstatic i // 获取静态变量i的值
  • iconst_1 // 将int常量1压入操作数栈
  • isub // 自减

临界区( Critical Section)

  • 我们知道一个程序可以运行多个线程是没有任何问题的。
  • 但是多个线程去读共享资源,也是没有问题的!
  • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题!
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临界资源。往简单说,上面的i-- 与 i++ 的代码块就是临界区。

竞态条件( Race Condition )

  • 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

synchronized解决结果不为0的问题

  • 第一种方式
	public synchronized static void increment() {
		count++;
	}

	public static synchronized void decrement() {
		count--;
	}

  • 第二种方式
	private static String lock = "";

	public static void increment() {
		synchronized (lock) {
			count++;
		}
	}

	public static void decrement() {
		synchronized (lock) {
			count--;
		}
	}

不同位置的synchronized有什么区别呢?

【并发编程】从一个程序入门synchronized

结束语

  • 本文只说synchronized的基础使用。深入研究请关注后面的文章!
  • synchronized底层原理:Monitor(管程/监视器)
  • 从对象在内存布局的布局上深入了解synchronized的各种锁(锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、自适应自旋(Adaptive Spinning))优化原理
  • 为什么synchronized的性能能与Lock持平?锁的优化!

结束语

  • 获取更多有价值的文章,让我们一起成为架构师!
  • 关注公众号,可以让你对MySQL有非常深入的了解
  • 关注公众号,每天持续高效的了解并发编程!
  • 这个公众号,无广告!!!每日更新!!!
    【并发编程】从一个程序入门synchronized
上一篇:代码优化:使用同步代码块替代同步方法


下一篇:09.13日记(2014年9月13日00:18:26)英语,bootstrap,阮一峰,