package util.bloomfilter; import lombok.var; /** *@Topic:同步线程 * * * * * 11111 * 它表示用Counter.lock实例作为锁,两个线程在执行各自的synchronized(Counter.lock) { ... }代码块时,必须先获得锁,才能进入代码块进行。执行结束后,在synchronized语句块结束会自动释放锁。这样一来,对Counter.count变量进行读写就不可能同时进行。上述代码无论运行多少次,最终结果都是0。 * * 使用synchronized解决了多线程同步访问共享变量的正确性问题。但是,它的缺点是带来了性能下降。因为synchronized代码块无法并发执行。此外,加锁和解锁需要消耗一定的时间,所以,synchronized会降低程序的执行效率。 * * 我们来概括一下如何使用synchronized: * * 找出修改共享变量的线程代码块; * 选择一个共享实例作为锁; * 使用synchronized(lockObject) { ... }。 * * 在使用synchronized的时候,不必担心抛出异常。因为无论是否有异常,都会在synchronized结束处正确释放锁: * * *22222 * 因为JVM只保证同一个锁在任意时刻只能被一个线程获取,但两个不同的锁在同一时刻可以被两个线程分别获取。 * *33333 * synchronized代码块无法并发执行 * * * * * *原子操作要保证结果正确,这里必须加锁 * * * * * * * * * * 不需要synchronized的操作 * * JVM规范定义了几种原子操作: * * 基本类型(long和double除外)赋值,例如:int n = m; * 引用类型赋值,例如:List<String> list = anotherList。 * * long和double是64位数据,JVM没有明确规定64位赋值操作是不是一个原子操作,不过在x64平台的JVM是把long和double的赋值作为原子操作实现的。 */
public class Thread1 { // 单条原子操作的语句不需要同 public void set0(int m) { synchronized(lock) { this.value = m; } } //对引用也是类似 public void set1(String s) { this.value = s; } //如果是多行赋值语句,就必须保证是同步操作 int first; int last; public void set2(int first, int last) { synchronized(this) { this.first = first; this.last = last; } } // 通过一些巧妙的转换,可以把非原子操作变为原子操作 int[] pair; public void set3(int first, int last) { int[] ps = new int[] { first, last }; this.pair = ps; } public static void main(String[] args) throws InterruptedException { var add = new AddThread(); var dec = new DecThread(); add.start(); dec.start(); add.join(); dec.join(); System.out.println(Counter.count); } } class Counter { //对象锁 public static final Object lock = new Object(); public static int count = 0; } class AddThread extends Thread { public void run() { for (int i=0; i<10000; i++) { //对象锁保证同一时刻只有一个线程进行操作 synchronized(Counter.lock){ Counter.count += 1; } } } } class DecThread extends Thread { public void run() { for (int i=0; i<10000; i++) { //对象锁保证同一时刻只有一个线程进行操作 synchronized(Counter.lock){ Counter.count -= 1; } } } }