原子性----锁(synchronized)
jdk提供锁主要分两种:
1、synchionized:依赖JVM,java关键字,作用对象的作用范围内
修饰代码块:大括号括起来的代码,作用于调用的对象,当被修饰的代码和修饰的方法一样时,那么修饰代码块和修饰方法是等同的
public void test1(){
synchronized (this){
for (int i = 0; i < 10; i++) {
System.out.println("test1:"+i);
}
}
}
修饰方法:整个方法,作用于调用的对象,同步方法
public synchronized void test2(){
for (int i = 0; i < 10; i++) {
System.out.println("test2:"+i);
}
}
注:如果当前类是父类,子类继承了父类之后,要调用test2的时候,那么它是带不上synchronized关键字的,因为synchronized不是方法的一部分,如果子类也想使用synchronized的话,那么需要显示声明synchronized
修饰静态方法:整个静态方法,作用于所有对象
public static synchronized void test2(){
for (int i = 0; i < 10; i++) {
System.out.println("test2:"+i);
}
}
修饰类:括号括起来的部分,作用于所有对象
public static void test1(){
synchronized (SynchionizedExample2.class){
for (int i = 0; i < 10; i++) {
System.out.println("test1:"+i);
}
}
}
注:在一个方法里面如果一个synchronized是被一个类修饰的话,那么它和一个被synchronized修饰的一个static方法的表象是一致的
计数问题,添加synchronized,使之成为线程安全
代码实例:
public class CountExample4 {
//请求总数
private static int clientTotal=5000;
//同时并发请求的线程数
private static int threadTotal=200;
private static int count=0;
public static LongAdder longAdder=new LongAdder();
// public static int count=0;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore=new Semaphore(threadTotal);
final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
semaphore.release();
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
// System.out.println("count:"+count);
System.out.println("count:"+count);
}
private static synchronized void add() {
count++;
}
//private static void add() {
// count++;
//}
}
2、lock:jdk提供的代码层面的锁,依赖特殊的CPU指令,代码实现,如:ReentrantLock
synchronized和lock和Atomic之间原子性比较
synchronized:不可中断锁,适合竞争不激烈,可读性好,竞争激烈时性能下降特别快
lock:可中断锁,只要调用unlock即可,多样同步化,竞争激烈时能维持常态
Atomic:竞争激烈能维持常态,比Lock性能好,缺点:只能同步一个值
线程安全性—可见性
定义:一个线程对主内存的修改,可以及时的被其它线程观察到
导致共享变量在线程间不可见的原因
线程交叉执行
重排序结合线程交叉执行
共享变量更新后的值没有在工作内存和主内存之间及时更新
jvm提供synchronized和volatile两种可见性
JMM关于synchronized的两条规定
线程解锁前,必须把共享变量的最新值刷到主内存
线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁是同一把锁)
volatile是通过内存屏障和禁止重排序优化来实现可见性
对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量的值刷新到主内存,
对volatile变量读操作时,会在读操作前加入一条loat屏障指令,从主内存中读取共享变量
volatile在读取变量时,会强迫从主内存中读取数据,当该变量发生变化时,又会强迫最新变量刷新到注内存
演示实例
public class CountExample4 {
//请求总数
private static int clientTotal=5000;
//同时并发请求的线程数
private static int threadTotal=200;
private static volatile int count=0;
public static LongAdder longAdder=new LongAdder();
// public static int count=0;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore=new Semaphore(threadTotal);
final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(new Runnable() {
public void run() {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
add();
semaphore.release();
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
// System.out.println("count:"+count);
System.out.println("count:"+count);
}
private static void add() {
count++;
}
}
注:从结果可以看出无法保证线程安全,原因是因为1、从内存中取出count值这时候的count值时最新的,2、执行加1操作和重新写回主内存,问题是如果有两个进程同时运行++操作,两个进程都读取了第一步,同时进行了+1操作并写回主存,尽管在第一步同时拿到了最新值,但是它们同时把+1后的值写回到注内存,这样就丢掉了一次+1操作,这样就会发现执行后的代码比5000小,有时候小很多,因此确定使用volatile不是线程安全的,同时也说明了volatile不具有原子性
volatile使用场景
具备两个条件
对变量的写操作不依赖于当前值
该变量不包含在具有其它变量的不变的式子中,volatile特别适合状态标记量
例子:
volatile boolean inited=false;
//线程1
context=loadContext();
inited=true;
//线程2
while(!inited){
sleep();
}
doSomeStringWithConfig(context);
线程安全性—有序性
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性
volatile、synchronized、lock
synchronized、lock顺序执行保证有序性
有序性—happens-before原则
程序次序规则:一个线程内,按照代码顺序,书写在前面操作先行发生于书写在后面的操作
锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
volatile变量操作:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start操作先行发生于此线程的每一个动作
线程终端规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行于线程的终止检测我们可以通过Thread.join()方法结束、Thread.isAlive的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于它的finalize方法的开始
原子性:Atomic包、CAS算法、synchronized、lock
可见性:volatile、synchronized
有序性:happends-before原则