第66条:同步访问共享的可变数据《高效java》

第66条:同步访问共享的可变数据《高效java》_第1张图片
同步.jpg

关键字:synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块。

同步

概念:

单线程

理解:

1、当一个对象被一个线程修改的时候,可以组织另一个线程观察到对象内部不一致的状态。
2、如果没有同步,一个线程的变化就不能被其他线程看到。同步不仅可以阻止一个线程看到对象处于不一致的状态,还可以保证进入同步代码块的线程看到同一个锁之前所有的修改效果。

原子性

概念

原子是
读或写一个非long或double的基本类型变量是原子的

理解

如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性

应用场景:一个线程干扰另一个线程任务

如果需要停止一个线程,可以使用Thread.stop方法,但是这个方法很久以前就不提倡使用了,因为不安全——使用它会使数据被破坏。
因此,建议的做法是,让一个线程轮询一个boolean域,另一个线程设置这个boolean域即可

错误示例

public class Temp {  
  
    private static boolean stopRequested;  
  
    public static void main(String[] args) throws InterruptedException {  
        Thread backgroundThread = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                int i = 0;  
                while (!stopRequested) {  
                    i++;  
                }  
            }  
        });  
        backgroundThread.start();  
        TimeUnit.SECONDS.sleep(1);  
        stopRequested = true;  
    }  
  
}  

实际上以上这段程序会永远的运行下去,因为没有使用同步,无法保证后台进程可以看到stopRequested值的改变。虚拟机将代码:

while (!stopRequested) {  
    i++;  
}  

编译成:

if(!stopRequested)  
    while(true)  
        {  
            i++;  
        }  

这样一来,永远都不会看到stopRequested的改变。

修正示例-使用synchronized

private static boolean stopRequested;  
  
private static synchronized void requestStop() {  
    stopRequested = true;  
}  
  
private static synchronized boolean stopRequested() {  
    return stopRequested;  
}  
  
public static void main(String[] args) throws InterruptedException {  
    Thread backgroundThread = new Thread(new Runnable() {  
        @Override  
        public void run() {  
            int i = 0;  
            while (!stopRequested()) {  
                i++;  
            }  
        }  
    });  
    backgroundThread.start();  
    TimeUnit.SECONDS.sleep(1);  
    requestStop();  
}  

修正示例-使用volatile

private static volatile boolean stopRequested;  
  
public static void main(String[] args) throws InterruptedException {  
    Thread backgroundThread = new Thread(new Runnable() {  
        @Override  
        public void run() {  
            int i = 0;  
            while (!stopRequested) {  
                i++;  
            }  
        }  
    });  
    backgroundThread.start();  
    TimeUnit.SECONDS.sleep(1);  
    stopRequested = true;  
}  

==注意==:使用volatile容易出现错误

private static volatile int nextSerialNumber = 0;  
  
public static int generateSerialNumber() {  
    return nextSerialNumber++;  
}

尽管使用了volatile,但是由于++运算符不是原子的,因此在多线程的时候会出错。++运算符执行两项操作:1、读取值;2、写回新值(相当于原值+1)。如果第二个线程在第一个线程读取旧值和写会新值的时候读取了这个域,就会产生错误,他们会得到相同的SerialNumber。
解决方法可以是,加入synchronized并去掉volatile。进一步的,可以用Long来代替int,或者在快要溢出的时候,抛出异常。更好的是使用AtomicLong类。

private static final AtomicLong nextSerialNumber = new AtomicLong(0);  
  
public static Long generateSerialNumber() {  
    return nextSerialNumber.incrementAndGet();  
}

你可能感兴趣的:(第66条:同步访问共享的可变数据《高效java》)