zoukankan      html  css  js  c++  java
  • 浅析Java中synchronized与static synchronized

    synchronized关键字

    synchronized是进行同步处理而保证线程安全。在一个方法中,如果是方法内的私有变量,那个这个变量是线程安全的,但是类中的实例变量是可能会出现线程安全问题的,当多个线程对这个实例变量进行修改,就可能会出现结果并不是我们期望的结果。

    线程安全问题

    下面一段代码就出现了线程安全问题。
    本来当username为a的时候,num应该为100,但是由于设置让t1休眠了2秒,导致num被刷新成了200,导致最后输出时a和b的num都是200。

    public class Service {
        private int num = 0;
        public void add(String username) {
            try {
                if (username.equals("a")) {
                    num = 100;
                    Thread.sleep(2000);
                } else {
                    num = 200;
                }
                System.out.println(username + " " + num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class ThreadA extends Thread {
        private Service service;
        public ThreadA(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.add("a");
        }
    }
    
    public class ThreadB extends Thread {
        private Service service;
        public ThreadB(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.add("b");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Service service = new Service();
            ThreadA t1 = new ThreadA(service);
            ThreadB t2 = new ThreadB(service);
            t1.start();
            t2.start();
        }
    }
    

    运行结果:

    synchronized锁

    下面给add方法加个synchronized关键字。
    可以看到输出正确。
    synchronized进行了同步,使得线程按照顺序进行访问,由于线程t1和t2的监视器都是同一个实例,相当于拥有同一个锁对象,所以可以进行同步访问。

    public class Service {
        private int num = 0;
        public synchronized void add(String username) {
            try {
                if (username.equals("a")) {
                    num = 100;
                    Thread.sleep(2000);
                } else {
                    num = 200;
                }
                System.out.println(username + " " + num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    输出结果:

    synchronized同步代码块

    上面的代码中是使用synchronized将整个方法进行上锁,只有当一个方法执行完毕后,另一个线程才可以执行这个方法,这样会导致性能损耗很大。
    返回到线程安全问题,目的其实是为了解决对临界资源访问的问题,所以其实只需要将临界资源进行上锁就可以了,其他部分其实是可以异步进行的。

    在下面代码中,doSomeTask方法里前半部分没有进行同步,后面使用了同步代码块进行加锁。
    从输出结果可以看到,前面部分A、B两个线程是异步进行访问的,后部分是同步进行访问的。

    public class Service {
        public  void doSomeTask(String username) {
            for (int i = 0; i < 5; i++) {
                System.out.println("没有同步 " + Thread.currentThread().getName() + " " + i);
            }
            System.out.println();
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("同步了" + Thread.currentThread().getName() + " " + i);
                }
            }
        }
    }
    
    public class ThreadA extends Thread {
        private Service service;
        public ThreadA(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.doSomeTask("a");
        }
    }
    
    public class ThreadB extends Thread {
        private Service service;
        public ThreadB(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.doSomeTask("b");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Service service = new Service();
            ThreadA t1 = new ThreadA(service);
            ThreadB t2 = new ThreadB(service);
            t1.setName("A");
            t2.setName("B");
            t1.start();
            t2.start();
        }
    }
    

    运行结果:

    所以使用synchronized同步代码块可以将需要加锁的部分进行上锁就行了,这样可以提高性能。
    可以使用synchronized同步代码块加锁临界资源,这样就可以避免出现线程安全问题。
    在JDK1.8中的ConcurrentHashMap也使用synchronized锁,synchronized锁的性能已经有了很大的提高。

    static synchronized

    static方法为类方法,那么对static方法加上synchronized锁后呢?加锁的究竟是什么呢?
    其实这时候监视器上锁的对象为这个类对象,而不是一个具体的实例对象,就是所有该类的实例访问这个方法都会进行加锁。

    从下面实例可以看出,虽然是两个实例四个线程访问该方法,但是还是进行了同步,因为所有实例访问的是同一把锁,也就是Service类的对象锁,只要是监视器锁对象是同一个,那么都是会进行上锁同步的。

    public class Service {
        public static synchronized void doSomeTask(String username) {
            for (int i = 0; i < 5; i++) {
                System.out.println("同步了" + Thread.currentThread().getName() + " " + i);
            }
        }
    }
    
    public class ThreadA extends Thread {
        private Service service;
        public ThreadA(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.doSomeTask("a");
        }
    }
    
    public class ThreadB extends Thread {
        private Service service;
        public ThreadB(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.doSomeTask("b");
        }
    }
    
    
    public class Test {
        public static void main(String[] args) {
            Service service = new Service();
            Service service1 = new Service();
            ThreadA t1 = new ThreadA(service);
            ThreadB t2 = new ThreadB(service);
            ThreadA t3 = new ThreadA(service1);
            ThreadB t4 = new ThreadB(service1);
            t1.setName("A");
            t2.setName("B");
            t3.setName("C");
            t4.setName("D");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    

    运行结果:

    使用同步代码块也可以实现上述功能。

    在同步代码块中,将监视的锁对象设置为Service.class,代表监视的是这个类锁,所以也对这个类进行加锁同步。

    public class Service {
        public void doSomeTask(String username) {
            synchronized (Service.class) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("同步了" + Thread.currentThread().getName() + " " + i);
                }
            }
        }
    }
    

    synchronized与static synchronized一起使用

    四个线程访问两个方法,线程A和C访问task1,是一个普通的synchronized方法,线程B和D访问task2,是一个static synchronized方法。

    从输出结果中可以看出,A、C进行了同步,B、D也进行同步,但是B和C在一开始是交替出现输出,代表B和C其实没有同步,就证明它们的锁不是同一把锁

    A和C线程获取的实例的对象锁,而B和D线程获取的是这个类的锁。

    public class Service {
        public synchronized void task1() {
            for (int i = 0; i < 5; i++) {
                System.out.println("task1 " + Thread.currentThread().getName() + " " + i);
            }
        }
    
        public static synchronized void task2() {
            for (int i = 0; i < 5; i++) {
                System.out.println("task2 " + Thread.currentThread().getName() + " " + i);
            }
        }
    }
    
    public class ThreadA extends Thread {
        private Service service;
        public ThreadA(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.task1();
        }
    }
    
    public class ThreadB extends Thread {
        private Service service;
        public ThreadB(Service service) {
            this.service = service;
        }
    
        @Override
        public void run() {
            super.run();
            service.task2();
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Service service = new Service();
            ThreadA t1 = new ThreadA(service);
            ThreadB t2 = new ThreadB(service);
            ThreadA t3 = new ThreadA(service);
            ThreadB t4 = new ThreadB(service);
            t1.setName("A");
            t2.setName("B");
            t3.setName("C");
            t4.setName("D");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    

    运行结果:

    综上:

    • synchronized同步代码块的性能比synchronized同步方法性能好。
    • 线程间的同步主要是看的获取的是什么锁,只有需要获取同一把锁的线程才会进行同步。
    • static synchronized获取的是类对象锁,而普通的synchronized获取的是实例对象的锁,所以其实他们不是同一把锁。
    • static synchronized的类对象锁可以对所有该类实例进行同步,因为所有该类实例获取的类锁都是同一把锁。
  • 相关阅读:
    为 ADO 程序员设计的 ADO.NET (转)
    MSN 历史纪录分页显示(XML + XSL + Javascript)
    python连接postgresql数据库
    centOS安装scikitlearn
    14. 页高速缓存 20100228 21:10 322人阅读 评论(0) 收藏
    18. 程序的执行 20100302 12:56 131人阅读 评论(0) 收藏
    ttt 20110118 11:49 71人阅读 评论(0) 收藏
    15. 访问文件 20100228 22:55 129人阅读 评论(0) 收藏
    17. 进程间通信 20100302 12:49 191人阅读 评论(0) 收藏
    19(终). 系统启动 20100302 13:00 191人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/liuyang0/p/6679169.html
Copyright © 2011-2022 走看看