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的类对象锁可以对所有该类实例进行同步,因为所有该类实例获取的类锁都是同一把锁。
  • 相关阅读:
    与众不同 windows phone (50)
    与众不同 windows phone (49)
    重新想象 Windows 8.1 Store Apps (93)
    重新想象 Windows 8.1 Store Apps 系列文章索引
    重新想象 Windows 8.1 Store Apps (92)
    重新想象 Windows 8.1 Store Apps (91)
    重新想象 Windows 8.1 Store Apps (90)
    重新想象 Windows 8.1 Store Apps (89)
    重新想象 Windows 8.1 Store Apps (88)
    重新想象 Windows 8.1 Store Apps (87)
  • 原文地址:https://www.cnblogs.com/liuyang0/p/6679169.html
Copyright © 2011-2022 走看看