首页 > 技术文章 > 2.1synchronized同步方法

lilinzhiyu 2017-12-05 18:41 原文

 

由前言:

在第一章已经出现了非线程安全的情况。“非线程安全”其实会发生在多个线程同时对同一个对象中的实例变量进行访问时发生。产生的结果就是脏读(读到被修改过的数据)。

“线程安全”获得的实例变量是经过同步处理的,不会出现脏读的情况。

2.1.1方法内的变量为线程安全:

“非线程安全”的问题存在于实例变量中,如果将实例变量私有,则不存在“非线程安全”问题,所得就是线程安全了。

在方法内部声明变量,是不存在“非线程安全”问题的。

变量所在类:

public class SafeVariable {
    public void addI(String username) {
        try {
            int num = 0;
            if(username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程代码1:

public class Thread1 extends Thread {
    private SafeVariable sv ;
    public Thread1(SafeVariable sv) {
        this.sv = sv;
    }

    @Override
    public void run() {
        sv.addI("a");
    }
}

线程代码2:

public class Thread2 extends Thread {
    private SafeVariable sv ;
    public Thread2(SafeVariable sv) {
        this.sv = sv;
    }

    @Override
    public void run() {
        sv.addI("b");
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        SafeVariable sv = new SafeVariable();
        Thread1 thread1 = new Thread1(sv);
        thread1.start();
        Thread2 thread2 = new Thread2(sv);
        thread2.start();
    }
}

执行结果:

可以看出,方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。

2.1.2实例变量非线程安全:

如果多个线程同时访问一个对象的实例变量,则可能会出现“非线程安全问题”。

有多个实例变量可能会出现交叉的情况,如果仅有一个实例变量时可能会出现覆盖的情况。

实例变量所在类:

public class SafeVariable1 {
    private int num = 0;
    public void addI(String username) {
        try {
            if ("a".equals(username)) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程代码1:

public class Thread3 extends Thread {
    private SafeVariable1 sv ;
    public Thread3(SafeVariable1 sv) {
        this.sv = sv;
    }

    @Override
    public void run() {
        sv.addI("a");
    }
}

线程代码2:

public class Thread4 extends Thread {
    private SafeVariable1 sv ;
    public Thread4(SafeVariable1 sv) {
        this.sv = sv;
    }

    @Override
    public void run() {
        sv.addI("b");
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        SafeVariable1 sv = new SafeVariable1();
        Thread3 thread3 = new Thread3(sv);
        thread3.start();
        Thread4 thread4 = new Thread4(sv);
        thread4.start();
    }
}

执行结果:

本例中,两个线程同时访问了一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量。就有可能出现“非线程安全”问题。

此处:若想解决“非线程安全问题”,只需要在addI()方法前加关键字synchronized即可实现。

实例变量所在类:

public class SafeVariable1 {
    private int num = 0;
    synchronized public void addI(String username) {
        try {
            if ("a".equals(username)) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

从上述可以看出,在两(多)个线程同时访问同一个对象中的同步方法时,一定是线程安全的。

2.1.3多个对象多个锁:

实例变量所在类:

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

线程代码1:

public class Thread5 extends Thread {
    private SafeVariable2 sv;
    public Thread5(SafeVariable2 sv) {
        this.sv = sv;
    }

    @Override
    public void run() {
        sv.addI("a");
    }
}

线程代码2:

public class Thread6 extends Thread {
    private SafeVariable2 sv;
    public Thread6(SafeVariable2 sv) {
        this.sv = sv;
    }

    @Override
    public void run() {
        sv.addI("b");
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        SafeVariable2 sv1 = new SafeVariable2();
        SafeVariable2 sv2 = new SafeVariable2();
        Thread5 thread5 = new Thread5(sv1);
        thread5.start();
        Thread6 thread6 = new Thread6(sv2);
        thread6.start();
    }
}

执行结果:

该示例:两个线程分别访问了一个类的两个实例,效果却是异步的。

原因:关键字synchronized取得的锁都是对象锁,而不是把一段代码或者方法(函数)当作锁。

所以要想实现同步效果,必须是多个线程访问同一个对象时才能够实现,而此示例中却是两个线程访问各自的对象,创建的也是各自对象的锁,所以展现的结果自然为异步的。

 

同步的单词:synchronized      异步的单词:asynchronized

 

2.1.4synchronized方法与锁对象:

证明synchronized加锁的是对象。

对象类:

public class MyObject {
    public void methodA() {
        try {
            System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程代码1:

public class Thread7 extends Thread {
    private MyObject object;
    public Thread7(MyObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        object.methodA();
    }
}

线程代码2:

public class Thread8 extends Thread {
    private MyObject object;
    public Thread8(MyObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        object.methodA();
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        MyObject object = new MyObject();
        Thread7 thread7 = new Thread7(object);
        thread7.setName("A");
        Thread8 thread8 = new Thread8(object);
        thread8.setName("B");
        thread7.start();
        thread8.start();
    }
}

执行结果:

修改后的对象类:

public class MyObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

修改后的结果:

上述对比发现:调用关键字synchronized声明的方法一定是排队运行的。那现在有猜想:同一个对象中的其他非synchronized声明的方法被调用时会怎么样呢?

对象类:

public class MyObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void methodB() {
        try {
            System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程代码1:

public class Thread9 extends Thread {
    private MyObject object;
    public Thread9(MyObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        object.methodA();
    }
}

线程代码2:

public class Thread10 extends Thread {
    private MyObject object;
    public Thread10(MyObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        object.methodB();
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        MyObject object = new MyObject();
        Thread9 thread9 = new Thread9(object);
        thread9.setName("A");
        Thread10 thread10 = new Thread10(object);
        thread10.setName("B");
        thread9.start();
        thread10.start();
    }
}

执行结果:

可以看出,虽然线程A先持有了object对象的锁,但是线程B仍可以完全调用非synchronized类型的方法。

继续测试,此时将线程methodB()也加上同步锁synchronized关键字。

修改对象类:

public class MyObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void methodB() {
        try {
            System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

综上得出结论:

  1. A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
  2. A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法时,需要等待,也就是同步。

2.1.5脏读:

在2.1.4中通过synchronized关键字在赋值的时候使用了同步,但是在取值时也有可能遇到意想不到的意外。这种情况就是脏读,发生脏读的情况是:在读取实例变量时,此值已经被其他线程修改过了。

公共类:

public class PublicVar {
    public String username = "A";
    public String password = "AA";
    synchronized public void setValue(String username,String password) {
        try {
            this.username = username;
            Thread.sleep(1000);
            this.password = password;
            System.out.println("setValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void getValue() {
        System.out.println("getValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password );
    }
}

线程代码:

public class Thread11 extends Thread {
    public PublicVar pv;
    public Thread11(PublicVar pv) {
        this.pv = pv;
    }

    @Override
    public void run() {
        pv.setValue("B","BB");
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        try {
            PublicVar pv = new PublicVar();
            Thread11 thread11 = new Thread11(pv);
            thread11.start();
            Thread.sleep(5000);//打印结果受此值大小影响
            pv.getValue();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

出现脏读的原因在于getValue()方法不是同步的,所以可以在任意时刻进行调用。解决方法是加上synchronized关键字。

总结:

当A线程调用了anyObject对象声明了synchronized关键字的X方法时,A线程就获得了X方法锁,准确来说是获得了对象的锁,所以其他线程必须等待A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。

当A线程调用anyObject对象声明了synchronized关键字的X方法时,A线程就获得了X方法所在的对象的锁,所以其他线程必须等A线程执行完毕才可以去调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,也就是说,username和password已经被同时赋值,不存在脏读的基本环境。

2.1.6synchronized锁重入:

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明了在一个synchronized方法/块内部调用本地其他synchronized方法/块时,是永远可以得到锁的。

服务类代码:

public class Myservice {
    synchronized public void service1() {
        System.out.println("service1");
        service2();
    }

    synchronized public void service2() {
        System.out.println("service2");
        service3();
    }
    synchronized public void service3() {
        System.out.println("service3");
    }
}

线程代码:

public class Thread12 extends Thread {
    @Override
    public void run() {
        Myservice myservice = new Myservice();
        myservice.service1();
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        Thread12 thread12 = new Thread12();
        thread12.start();
    }
}

执行结果:

可重入锁概念:自己可以再次获得自己的内部锁,比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获得这个对象的锁的时候还是可以获得的,如果不可锁重入,会导致进入死循环。

 可重入锁也可以出现在继承中。

父类:

public class Father {
    public int i = 10;
    synchronized public void fatherWay() {
        try {
            i--;
            System.out.println("father print i = " + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

子类:

public class Son extends Father {
    synchronized public void sonWay() {
        try {
            while (i > 0) {
                i--;
                System.out.println("son print i = " + i);
                Thread.sleep(1000);
                this.fatherWay();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程代码:

public class Thread13 extends Thread {
    @Override
    public void run() {
        Son son = new Son();
        son.sonWay();
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        Thread13 thread13 = new Thread13();
        thread13.start();
    }
}

执行结果:

上述示例表明:当存在父子类继承时,子类完全可以使用锁重入调用父类的同步方法。

2.1.7出现异常,锁自动释放:

当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

业务类代码:

public class Service1 {
    synchronized public void testMethod() {
        if ("a".equals(Thread.currentThread().getName())) {
            System.out.println("ThreadName = " + Thread.currentThread().getName() + " run beginTime = " + System.currentTimeMillis());
            int i = 1;
            while (i == 1) {
                if(("" + Math.random()).substring(0,8).equals("0.123456")) {
                    System.out.println("ThreadName = " + Thread.currentThread().getName() + " run  exceptionTime = " + System.currentTimeMillis());
                    Integer.parseInt("a");
                }
            }
        } else {
            System.out.println("Thread B run Time = " + System.currentTimeMillis());
        }
    }
}

线程代码1:

public class Thread14 extends Thread {
    private Service1 service1;
    public Thread14(Service1 service1) {
        this.service1 = service1;
    }

    @Override
    public void run() {
        service1.testMethod();
    }
}

线程代码2:

public class Thread15 extends Thread {
    private Service1 service1;
    public Thread15(Service1 service1) {
        this.service1 = service1;
    }

    @Override
    public void run() {
        service1.testMethod();
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        try {
            Service1 service1 = new Service1();
            Thread14 a = new Thread14(service1);
            a.setName("a");
            a.start();
            Thread.sleep(5000);
            Thread15 b = new Thread15(service1);
            b.setName("b");
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

可以看到的是,当出现异常后,锁就被释放了,线程B能够进入方法正常打印。

 

2.1.8同步不具有继承性:

同步是不可以被继承的:

父类业务类:

public class FatherService {
    synchronized public void fatherService() {
        try {
            System.out.println("int father 下一步 sleep begin  threadName = " + Thread.currentThread().getName()
            + " time = " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int father 下一步 sleep end  threadName = " + Thread.currentThread().getName()
                    + " time = " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

子类业务类:

public class SonService extends FatherService {
    @Override
    public  void fatherService() {
        try {
            System.out.println("int son 下一步 sleep begin  threadName = " + Thread.currentThread().getName()
                    + " time = " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int son 下一步 sleep end  threadName = " + Thread.currentThread().getName()
                    + " time = " + System.currentTimeMillis());
            super.fatherService();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程代码1:

public class Thread16 extends Thread {
    private SonService sonService;
    public Thread16(SonService sonService) {
        this.sonService = sonService;
    }

    @Override
    public void run() {
        sonService.fatherService();
    }
}

线程代码2:

public class Thread17 extends Thread {
    private SonService sonService;
    public Thread17(SonService sonService) {
        this.sonService = sonService;
    }

    @Override
    public void run() {
        sonService.fatherService();
    }
}

执行代码:

public class Main {
    public static void main(String[] args) {
        SonService sonService = new SonService();
        Thread16 a = new Thread16(sonService);
        a.setName("A");
        a.start();
        Thread17 b = new Thread17(sonService);
        b.setName("B");
        b.start();
    }
}

执行结果:

 由结果可以看到,代码的执行是非同步的,这也就说明了synchronized关键字是不可以继承的,此时为了实现同步,必须在子类的方法上加上关键字synchronized才行。

修改后的子类业务类:

public class SonService extends FatherService {
    @Override
    public synchronized void fatherService() {
        try {
            System.out.println("int son 下一步 sleep begin  threadName = " + Thread.currentThread().getName()
                    + " time = " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int son 下一步 sleep end  threadName = " + Thread.currentThread().getName()
                    + " time = " + System.currentTimeMillis());
            super.fatherService();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 修改后的执行结果:

 

源码地址:https://github.com/lilinzhiyu/threadLearning

本文内容是书中内容兼具自己的个人看法所成。可能在个人看法上会有诸多问题(毕竟知识量有限,导致认知也有限),如果读者觉得有问题请大胆提出,我们可以相互交流、相互学习,欢迎你们的到来,心成意足,等待您的评价。

推荐阅读