zoukankan      html  css  js  c++  java
  • Java并发编程:线程的同步

    Java并发编程:线程的同步

    Java并发编程:线程的同步

    线程的同步是通过锁来实现的。在我们使用Concurrent包的锁之前,我们先来了解Java原生的同步锁。Java的每个对象都有一个锁,这个锁具有排他性。只要任一一个线程获得了锁,其他的线程就不能再获得该锁,只能阻塞排队来获取该对象的锁。有锁的这个对象有个专门的名称叫监视对象,它会记录它所监视的所有线程,谁获得了锁,谁在等待锁。
    Java中所有的操作都是通过线程来实现的,即使我们不主动创建线程,也是使用进程自动创建的主线程来完成操作的,与进程一起创建的还有一个垃圾回收线程。
    线程获取锁的方式,是通过使用synchronized的修饰符来得到的。synchronized修饰符可以使用在几个不同的地方,但它们的作用都是获得一个对象的锁。
    synchronized关键字并不是方法签名的一部分,因此,子类不能继承父类的锁。但子类方法的锁和父类方法的锁是一样的,就是说子类锁定的方法可以调用父类中锁定的方法。但是,内部类的同步锁和其外部类的锁是不一样的,要想中内部类中锁住外部类的方法:

    synchronized(OuterClass.this) { /* body */ }
    

    1 synchronized 修饰方法

    这种使用是最常见的,当我们不获取锁时:

    public class SyncDemo implements Runnable {
        private int count = 0;
        @Override
        public void run() {
            count++;
            System.out.println(count);
        }
        public static void main(String[] args) {
            SyncDemo runnable = new SyncDemo();
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(runnable);
                thread.start();
            }
        }
    }
    
    2
    4
    3
    5
    6
    2
    7
    8
    9
    10
    

    可以看到,输出的结果完全是不对的。
    那么,我们在方法run()前面加上synchronized修饰,再试一下:

    synchronized public void run() {
        count++;
        System.out.println(count);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    结果就是完全正确的了。
    所以,synchronized修饰方法之后,可以让线程获得该对象的锁,这样其他线程想要调用该函数的时候,就需要先获得这个对象的锁,从而达到同步的目的。

    2 synchronized 修饰代码块

    修饰代码块,运行得到的结果与修饰方法是一样的:

    public void run() {
        synchronized(this) {
            count++;
            System.out.println(count);
       }
    }
    

    如果使用synchronized(Object)这种方式来修饰代码块,可以起到和修饰方法类似的效果。只是这里的对象可以是其他的对象,而不限于当前对象。也就是说可以使用这种方法获得任意对象的锁,只要能把对象传进来。同时,也可以只锁定其中的部分代码,只锁定会使用到共享资源的代码,比如,修改对象的成员变量的代码等。而对其他的代码放开,相当于其他代码是并发运行的,只有锁定的部分代码需要排队运行。

    3 synchronized 修饰static方法

    当我们使用synchronized修饰方法时,获得的是该对象的锁,那么,当我们修饰static的方法时,获取的是不是还是对象的锁呢?

    public class SyncDemo implements Runnable {
        private static int count = 0;
        synchronized public void instanceMethod() {
            count++;
            System.out.println("instance: " + count);
        }
        synchronized public static void staticMethod() {
            count++;
            System.out.println("static: " + count);
        }
        @Override
        public void run() {
            instanceMethod();
            staticMethod();
        }
        public static void main(String[] args) {
            SyncDemo runnable = new SyncDemo();
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(runnable);
                thread.start();
            }
        }
    }
    

    我们用synchronized分别修饰普通instance方法和static方法,然后,运行:

    instance: 1
    static: 3
    instance: 3
    static: 4
    instance: 5
    static: 6
    instance: 7
    static: 8
    instance: 9
    static: 10
    instance: 11
    static: 12
    instance: 13
    static: 14
    instance: 15
    static: 16
    instance: 17
    static: 18
    instance: 19
    static: 20
    

    我们发现它们锁住的不是同一个对象。那么,当修饰static方法时,锁住的是什么对象呢?当修饰static方法时,所有的类的对象的该方法都是同步的,所以,获取的是class对象的锁。每个类在加载完成之后,都会生成一个.class的类对象。我们用两个例子来分别证明一下。
    首先,当修饰static方法时,所有的类的对象的该方法都是同步的:

    public void run() {
        staticMethod();
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            SyncDemo runnable = new SyncDemo();
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }
    
    static: 1
    static: 2
    static: 3
    static: 4
    static: 5
    static: 6
    static: 7
    static: 8
    static: 9
    static: 10
    

    创建不同的对象,然后,调用synchronized修饰的static方法,结果是同步的。
    然后,我们再对instance方法中的代码块,通过获取.class对象的锁再来运行:

    public class SyncDemo implements Runnable {
        private static int count = 0;
        public void instanceMethod() {
            synchronized(SyncDemo.class) {
                count++;
                System.out.println("instance: " + count);
            }
        }
        synchronized public static void staticMethod() {
            count++;
            System.out.println("static: " + count);
        }
        @Override
        public void run() {
            instanceMethod();
            staticMethod();
        }
        public static void main(String[] args) {
            SyncDemo runnable = new SyncDemo();
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(runnable);
                thread.start();
            }
        }
    }
    

    注意其中的这里:

    synchronized(SyncDemo.class) {
        count++;
        System.out.println("instance: " + count);
    }
    
    instance: 1
    instance: 2
    static: 3
    instance: 4
    static: 5
    static: 6
    instance: 7
    static: 8
    instance: 9
    static: 10
    instance: 11
    static: 12
    instance: 13
    static: 14
    instance: 15
    static: 16
    instance: 17
    static: 18
    instance: 19
    static: 20
    

    我们可以看到,两个方法是同步执行的,说明它们获取的是同一个锁,所以,修饰static方法时,获取的是.class锁。在锁定代码块时,我们使用this.getClass()和SyncDemo.class是一样的。

    Date: 2017-07-05 21:14

    Author: WEN YANG

    Created: 2017-07-08 Sat 19:45

    Emacs 25.2.1 (Org mode 8.2.10)

    Validate

  • 相关阅读:
    JAVA8 之 Stream 流(四)
    关于iphone 6s 页面功能不能正常使用问题
    关于ES6语法的 一些新的特性
    微信授权一直跳转
    js 一道题目引发的正则的学习
    关于this在不同使用情况表示的含义
    详细解析arry.map() ,function.apply() 方法
    关于服务器无法在已发送http表头之后设置状态问题
    七牛上传视频并转码
    使用 v-cloak 防止页面加载时出现 vuejs 的变量名
  • 原文地址:https://www.cnblogs.com/yangwen0228/p/7123781.html
Copyright © 2011-2022 走看看