美文网首页
Synchronized关键字的使用

Synchronized关键字的使用

作者: 少博先生 | 来源:发表于2017-09-17 01:17 被阅读0次

    前面介绍了多线程的诸多优点,好用归好用,同时也出现了一个问题就是线程的同步。当不做线程的同步控制,多个线程跑起来并对一个共享变量做改动,执行完毕后,你会发现共享变量的值不如你愿。因为这些线程没能实现线程安全。关于线程安全和线程非安全,看下百度的解释:

    线程安全&线程非安全

    线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

    保证线程安全,java提供了以下几种同步策略:

    1. synchronized关键字。
    2. volatile关键字。
    3. JUC包中的Lock接口。
      4.ThreadLocal管理变量。
      本篇主要介绍一下synchronized关键字的使用。
      通俗点描述synchronized,它就像是挂在一个衣柜上的锁。记得大学和兄弟们打完球,一起去澡堂冲凉,走进更衣室,会看见有很多衣柜,一个个衣柜就如同一个个共享变量,如果一个衣柜上有锁(synchronized),说明这个柜(共享变量)正在被人(线程)使用,你如果非要用这个柜子,就得等正在使用这个柜子的人用完柜子,释放了锁,你再使用。
      先示范一把线程非安全的例子:
    public class Test5 implements Runnable {
        private  int count = 0;
        @Override
        public void run() {
            for(int i = 0; i < 8; i++){
                System.out.println(Thread.currentThread().getName()+ ":" + count++);
            }
        }
        public static void main(String[] args) {
            Test5 test5 = new Test5();
            Thread thread1 = new Thread(test5,"thread1");
            Thread thread2 = new Thread(test5,"thread2");
            thread1.start();
            thread2.start();
        }
    }
    

    运行结果:(本想着count值为15,执行结果却是14,且thread1和thread2交叉着修改着count)


    synchronized修饰一个方法

    如果想让上面例子线程安全,且最后的count为15,使用synchronized修饰方法。修改上述代码的run()方法,两种写法:
    ①直接在方法访问修饰符后面加synchronized

    @Override
    public synchronized void run() {
        for(int i = 0; i < 8; i++){
            System.out.println(Thread.currentThread().getName()+ ":" + count++);
        }
     }
    

    ②在方法内部使用synchronized修饰方法代码块

    @Override
    public void run() {
         synchronized (this){
             for(int i = 0; i < 8; i++){
                 System.out.println(Thread.currentThread().getName()+ ":" + count++);
             }
         }
    }
    

    两种结果的执行结果是一致的,可以看出:当两个线程访问同一个对象(test5)的synchronized方法时,同一时刻只能有一个线程执行,另一个阻塞,两个线程是互斥的,只有当第一个执行完毕释放了锁,第二个线程才能获取锁重新锁定对象。


    继续玩Test5,将其main方法内部做下修改,修改如下:

    public static void main(String[] args) {
         Thread thread1 = new Thread(new Test5(),"thread1");
         Thread thread2 = new Thread(new Test5(),"thread2");
         thread1.start();
         thread2.start();
    }
    

    执行结果如下:


    这是怎么回事,不是已经用synchronized修饰了run(),锁定对象了吗,怎么两个线程交叉执行,且count都是7,难道使用的姿势不对???



    原因:synchronized锁定的是对象,new Test5()两次会创建两个独立的对象,所以会有两把锁锁定两个对象,thread1执行着对象1的run(),thread2执行着对象2的run(),所以两个线程互不影响,各执行各的。

    synchronized(this),this为何物?
    public class Test5 implements Runnable {
        private static int count = 0;
        @Override
        public void run() {
            synchronized (this){
                for(int i = 0; i < 8; i++){
                    System.out.println(Thread.currentThread().getName()+ ":" + count++);
                }
            }
        }
        public static void main(String[] args) {
            Test5 test5 = new Test5();
            Thread thread1 = new Thread(test5,"thread1");
            Thread thread2 = new Thread(test5,"thread2");
            thread1.start();
            thread2.start();
        }
    }
    

    执行结果(线程安全):


    修改main方法如下:

    public static void main(String[] args) {
            Thread thread1 = new Thread(new Test5(),"thread1");
            Thread thread2 = new Thread(new Test5(),"thread2");
            thread1.start();
            thread2.start();
    }
    

    从这个例子就可以看出:这里的this是指当前对象,不管是对象A还是对象B,刚开始运行这段代码的时候会给这段代码加个锁,这样即使运行到中间被替换了,另一个线程也不会执行这段代码,因为这段代码加锁了,而钥匙在给代码加锁的那个线程手里,只有加锁的线程运行完这段代码,才会给代码解锁.然后其他线程才能执行这段代码。

    修饰一个静态方法

    静态方法是属于整个类的,所以synchronized修饰静态方法锁定的就是这个类的所有对象。

    public class Test5 implements Runnable {
        private static int count = 0;
        @Override
        public void run() {
            addCount();
        }
        public static synchronized void addCount(){
            for(int i = 0; i < 8; i++){
                System.out.println(Thread.currentThread().getName()+ ":" + count++);
            }
        }
        public static void main(String[] args) {
            Test5 test5 = new Test5();
            Thread thread1 = new Thread(test5,"thread1");
            Thread thread2 = new Thread(test5,"thread2");
            thread1.start();
            thread2.start();
        }
    }
    

    执行结果:



    修改Test5的main(),再次执行

    public static void main(String[] args) {
            Thread thread1 = new Thread(new Test5(),"thread1");
            Thread thread2 = new Thread(new Test5(),"thread2");
            thread1.start();
            thread2.start();
    }
    

    可以发现执行结果还是上图,new Test5()创建了两个不同的对象,但thread1、thread2的执行确实线程安全的,原因是synchronized修饰的是静态方法,静态方法是属于类的,两个对象用的是同一个锁,所以同步。

    修饰一个类

    修改Test5的addCount()方法,修改如下:

    public static void addCount(){
        synchronized (Test5.class){
            for(int i = 0; i < 8; i++){
                System.out.println(Thread.currentThread().getName()+ ":" + count++);
            }
        }
    }
    

    执行结果(线程安全的):

    synchronized不支持继承

    synchronized可以修饰方法,但不属于方法定义的一部分,不能够被继承。如果父类的一个方法使用了synchronized,子类中重写了此方法,那么在子类中的这个方法默认是不同步的。如果要达到同步效果,一是在子类方法前也加上synchronized,二是在子类方法中调用父类的同步方法。

    总结

    synchronized是java中的关键字,是种同步锁。
    ①对于同步的方法或代码块,要先获得对象锁才能进入同步方法或代码块进行操作;
    ②synchronized可修饰方法,分为静态方法和非静态方法,修饰静态方法锁定的是整个类,修饰非静态方法锁定的是一个对象;
    ③synchronized可修饰类,修饰的是这个类的所有对象;
    ④代码块synchronized(a),对象锁就是a。
    ⑤synchronized修饰方法不支持继承。

    略陈固陋,如有不当之处,欢迎各位看官批评指正!

    相关文章

      网友评论

          本文标题:Synchronized关键字的使用

          本文链接:https://www.haomeiwen.com/subject/vlvusxtx.html