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;
}
}
}
}