synchronized和volatile关键字的使用总结
前言
本文着重介绍synchronized和volatile关键字的各种使用,并且尽可能多的将使用中可能碰到的情况,通过代码demo的形式展现出来。
1.synzhronized 关键字
synchronized 关键字,给一个对象上锁,任何需要访问该对象的线程,都必须先获得该锁。
synchronize关键字锁定的是对象不是代码块
Demo:synchronized 关键字锁的对象 instance object v.s. class object
synchronized 关键字锁定的对象有2中:1是类的实例(实例对象锁),2是类对象(类对象锁)
synchronized 关键字用得不恰当时,并不一定能保证实现线程安全,具体还要看锁定的对象是否唯一
/**
* 这里syncrhonized 关键字锁定的是 object对象
**/
public class Demo1 {
private int count = 10;
private Object object = new Object();
public void test(){
synchronized (object){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
/**
* 这里syncrhonized 关键字锁定的是当前类的实例对象,即Demo2的Instance
**/
public class Demo2 {
private int count = 10;
public void test(){
//synchronized(this)锁定的是当前类的实例,这里锁定的是Demo2类的实例
synchronized (this){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
/**
* 这里syncrhonized关键字直接加在方法声明上,相当于是syncrhonized(this)
**/
public class Demo3 {
private int count = 10;
//直接加在方法声明上,相当于是synchronized(this)
public synchronized void test(){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
/**
* 这里syncrhonized关键字修饰静态方法,锁定的是类对象
**/
public class Demo4 {
private static int count = 10;
//synchronize关键字修饰静态方法锁定的是类对象
public synchronized static void test(){
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void test2(){
synchronized (Demo4.class){//这里不能替换成this
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
2.volatile 关键字
volatile 关键字,使一个变量在多个线程间可见
Demo:volatile的使用及局限性
使用:
volatile关键字可以用来保证变量在线程之间的可见性
/**
* main线程和t1线程都共用到一个变量running,java默认保留的是t1线程中的一份副本,这样如果main线程修改了该变量,t1线程未必知道
* 使用volatile关键字,会让所有线程都会读到变量的修改值
* 在下面的代码中,running是存在与Java堆内存的t对象中,
* 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中,直接使用这个副本,
* 并不会每次都去读取对内存,这样,当主线程修改running的值之后,t1线程无法知道,因此也不会停止运行。
* 而当我们在running值加上volatile关键字之后,当running值发生变化时,会第一时间通知到t1线程,t1线程也会更新running值,
* 此时t1也将停止运行。
**/
public class Demo {
/*volatile*/ boolean running = true;
public void test(){
System.out.println("test start...");
while (running){
}
System.out.println("test end...");
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(() -> demo.test(),"t1").start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.running = false;
}
}
局限性:
volatile并不能保证多个线程共同修改同一个变量时所带来的不一致问题,
也就是说volatile并不能替代synchronized或者说volatile保证不了原子性。
/**
* 理想情况,当10个线程每个线程对count进行1万次添加操作后,我们得到的结果应该是10w,然而运行以下代码,
* 我们得到的count值要远远小于10w。Why?
* 比如,当第一个线程加到100时,还没往上加,这个时候,第二个线程也来了,把100拿过来执行++操作。
* 然后第一个线程继续加到101,第二个线程也加到101,他们往回写的值都是101。所以虽然说他们都进行了++操作,实际上只加了1。
**/
public class Demo {
volatile int count = 0;
public void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
Demo demo = new Demo();
List<Thread> threads = new ArrayList();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo::test, "thread-" + i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo.count);
}
}
3. 关于synchronized关键字
在jdk1.5及之前的版本,synchronized关键字只存在一种锁:重量级锁。这种锁虽然可以保证被锁定对象的线程安全,但是性能过于低下。于是Sun公司在jdk1.6之后对synchronized 关键字进行了大幅度的优化,引入了偏向锁和轻量级锁。当不存在线程竞争时(即只有1个线程获取被syncrhonized关键字所修饰的对象时),synchronized关键字会使用偏向锁;而当有2个或以上线程获取该对象且未发生竞争时(即2个或以上线程交替获取该对象),锁会膨胀为轻量级锁;只有当2个或以上线程获取该对象且发生竞争时,锁会最终膨胀为重量级锁。这三种锁的性能相差极大,具体细节我将在以后的Blog中证明与阐述。