博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
synchronized 和 volatile
阅读量:4104 次
发布时间:2019-05-25

本文共 9516 字,大约阅读时间需要 31 分钟。

一、synchronized 关键字

1、synchronized 用法

synchronized 关键字可以保证线程的原子性和可见性,它锁的是一个对象,他有如下几种用法

1)对象锁

这里锁的是 object 对象的实例。

public class Demo1 {    private int count = 10;    private Object object = new Object();    public void test(){            synchronized (object) {                while (count>0) {                count--;                System.out.println(Thread.currentThread().getName() + " count = " + count);            }        }    }    public static void main(String[] args){        Demo1 demo1 = new Demo1();        new Thread(()->demo1.test(),"t1").start();        new Thread(()->demo1.test(),"t2").start();    }}
2)this锁

这里使用的是 this 锁,锁的是当前类的实例。

public class Demo2 {    private int count = 10;        public void test(){        synchronized (this) {            while (count > 0) {                count--;                System.out.println(Thread.currentThread().getName() + " count = " + count);            }        }    }    public static void main(String[] args){        Demo2 demo2 = new Demo2();        new Thread(()->demo2.test(),"t1").start();        new Thread(()->demo2.test(),"t2").start();    }}
3)非静态同步函数

直接声明在方法上,相当于相当于是 synchronized(this)

public class Demo3 {    private int count = 10;    public synchronized void test(){        while (count>0) {            count--;            System.out.println(Thread.currentThread().getName() + " count = " + count);        }    }    public static void main(String[] args){        Demo3 demo3 = new Demo3();        new Thread(()->demo3.test(),"t1").start();        new Thread(()->demo3.test(),"t2").start();    }}
4)静态同步函数

1)synchronized 关键字修饰的静态方法锁的是类的.class 文件;

2)静态方法中,锁定的对象只能是类的.class文件,不能是类的实例;

public class Demo4 {    private static int count = 10;    //静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。    public synchronized static void test(){        while (count>0) {        count--;        System.out.println(Thread.currentThread().getName() + " count = " + count);    }}    public static void test2(){        //这里不能替换成this        synchronized (Demo4.class){            while (count>0) {                count--;                System.out.println(Thread.currentThread().getName() + " count = " + count);            }        }    }    public static void main(String[] args){        Demo4 demo4 = new Demo4();        new Thread(()->demo4.test2(),"t1").start();        new Thread(()->demo4.test2(),"t2").start();    }}

2、使用 synchronized 关键字注意事项

1)防止锁对象的引用的改变

当我们定义了一个对象锁,锁住了这个对象 o,如果这个对象的属性发生改变,不会影响锁住的对象,如果这个对象变成了一个新的对象,也就是重新 new 了一次,那锁住的对象就会发生改变,如下例子。

public class Demo1 {    Object o = new Object();    public void test(){        synchronized (o) {            while (true) {                try {                    TimeUnit.SECONDS.sleep(1);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName());            }        }    }    public static void main(String[] args) {        Demo1 demo = new Demo1();        new Thread(demo :: test, "t1").start();        Thread t2 = new Thread(demo :: test, "t2");		//去掉此句 t2 无法执行,否则 t2 能够执行        demo.o = new Object();        t2.start();    }}
2)不要用字符串常量作为锁的对象

如果使用字符串常量作为锁,在字符串相同的情况下,锁住的则是同一个对象,因为他们的引用都指向常量池的同一个地址。

public class Demo2 {    String s1 = "hello";    String s2 = "hello";    public void test1(){        synchronized (s1) {            System.out.println("t1 start...");            try {                TimeUnit.SECONDS.sleep(2);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("t1 end...");        }    }    public void test2(){        synchronized (s2) {            System.out.println("t2 start...");        }    }    public static void main(String[] args) {        Demo2 demo = new Demo2();        new Thread(demo :: test1,"test1").start();        new Thread(demo :: test2,"test2").start();    }}
3)同步代码块中的语句越少越好

在 test1 中,是给整个方法上了锁,但由于我们的业务逻辑只需要锁住的对象是count++,所以在 test2 中我们只给了这个代码上了锁,这样细化了锁的粒度,使代码的效率得以提高。

public class Demo3 {    int count = 0;    public synchronized void test1(){        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }        count ++;        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public void test2(){        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }        synchronized (this) {            count ++;        }        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

3、 synchronized 关键字特点

1)同步方法和非同步方法可以同时调用
public class Demo{    public synchronized void test1(){        System.out.println(Thread.currentThread().getName() + " test1 start...");        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " test1 end...");    }    public void test2(){        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + " test2");    }    public static void main(String[] args) {        Demo demo = new Demo();        new Thread(demo :: test1,"test1").start();        new Thread(demo :: test2,"test2").start();    }}
2)synchronized支持重入

synchronized支持重入,一个同步方法调用另一个同步方法,也可以得到锁。

public class Demo {    synchronized void test1(){        System.out.println("test1 start.........");        try {            TimeUnit.SECONDS.sleep(1);        } catch (InterruptedException e) {            e.printStackTrace();        }        test2();    }    synchronized void test2(){        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("test2 start.......");    }    public static void main(String[] args) {        Demo demo= new Demo();        demo.test1();    }}
3)异常情况会自动释放锁

如果碰到异常情况,当先线程会自动释放锁

public class Demo {    int count = 0;    synchronized void test(){        System.out.println(Thread.currentThread().getName() + " start......");        while (true) {            count ++;            System.out.println(Thread.currentThread().getName() + " count = " + count);            try {                TimeUnit.SECONDS.sleep(1);            } catch (InterruptedException e) {                e.printStackTrace();            }            if (count == 5) {                //碰到异常的情况,如果没有处理,会自动释放锁,所以T2可以执行。                int i = 1/0;            }        }    }    public static void main(String[] args) {        Demo demo11 = new Demo();        Runnable r = () -> demo11.test();        new Thread(r, "t1").start();        try {            TimeUnit.SECONDS.sleep(1);        } catch (InterruptedException e) {            e.printStackTrace();        }        new Thread(r, "t2").start();    }}

二、volatile 关键字

1、volatile 关键字特点

1)保证可见性

什么是可见性,在JMM(Java 内存模型) 中分为主存和本地私有内存,主存中存放的是所有线程的共享数据,本地内存中存放的是某个线程的私有数据,在某一线程启动时,会先去读取主内存的数据到本地私有内存,然后再运行这个副本,但并不会每次都去读取,所以会造成数据不一致问题。

volatile 关键字正是为了解决这个问题,我们通过以下代码举例。

在这里插入图片描述
以下代码,在不添加 volatile 关键字前,虽然 running 改变了,但线程 t1 仍然不会停止,是因为虽然主内存改变了,但本地私有内存并不知道。 当添加了 volatile 关键字后,每一次被 volatile 关键字修饰的变量的值发生改变时,主内存都会通知本地私有内存进行修改,所以线程 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;    }}
2)不保证原子性

如下代码,多个线程对一个共享变量进行累加,比如一个线程 A 加到了100,但是还没有往上添加,另一个线程 B 也来了,它也把 100 拿过来进行累加,他们两都加了 1 ,于是把结果101 存了进去,虽然是加了 2 次,但实际上只加了 1 次,所以 volatile关键字不保证原子性,它不能代替Synchronized。

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

当我们在方法上添加了 synchronized ,发现最终结果为 10000,说明 synchronized 既保证了可见性,又保证了原子性。

public class Demo {    int count = 0;    public synchronized void test(){        for (int i = 0; i < 10000; i++) {            count ++;        }    }    public static void main(String[] args) {        Demo demo = new Demo();        List
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); }}

ABOUT

公众号:【星尘Pro】

github:

推荐阅读

转载地址:http://ldfsi.baihongyu.com/

你可能感兴趣的文章
2016——个人年度总结
查看>>
2017——新的开始,加油!
查看>>
【Python】学习笔记——-6.2、使用第三方模块
查看>>
【Python】学习笔记——-7.0、面向对象编程
查看>>
【Python】学习笔记——-7.1、类和实例
查看>>
【Python】学习笔记——-7.2、访问限制
查看>>
【Python】学习笔记——-7.3、继承和多态
查看>>
【Python】学习笔记——-7.4、获取对象信息
查看>>
【Python】学习笔记——-7.5、实例属性和类属性
查看>>
Linux设备模型(总线、设备、驱动程序和类)之四:class_register
查看>>
git中文安装教程
查看>>
虚拟机 CentOS7/RedHat7/OracleLinux7 配置静态IP地址 Ping 物理机和互联网
查看>>
弱类型、强类型、动态类型、静态类型语言的区别是什么?
查看>>
Struts2技术内幕图书 转载
查看>>
Java异常分类
查看>>
项目中的jackson与json-lib使用比较
查看>>
Jackson Tree Model Example
查看>>
j2ee-验证码
查看>>
日志框架logj的使用
查看>>
js-高德地图规划路线
查看>>