方法内的变量为线程安全

  上篇已经接触 “线程安全” 与 “非线程安全” 相关的技术点,“非线程安全” 其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是 “脏读”,也就是取到的数据其实是被更改过的。而 “线程安全” 就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。   “非线程安全” 问题存在于 “实例变量” 中,如果是方法内部的私有变量,则不存在 “非线程安全” 问题,所得结果也就是 “线程安全” 的了。

实例变量非线程安全

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

多个对象多个锁

 用两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式运行的。

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void addr (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();
        }
    }
}
public class ThreadA extends Thread {
    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public ovid run() {
        super.run();
        numRef.addI("a");
    }
}

public class ThreadB extends Thread {
    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public ovid run() {
        super.run();
        numRef.addI("b");
    }
}
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

ThreadA athread = new ThreadA(numRef1);
ThreadB bthread = new ThreadB(numRef2);
athread.start();
bthread.start();

  运行结果如下:

a set over
b set over
b num = 200
a num = 100

  本示例由于创建了2个业务对象,在系统中产生了出了2个锁,所以运行结果是异步的。   从程序运行的结果来看,虽然在 HasSelfPrivateNum.java 中使用了 synchronized 关键字,但打印的顺序却是不同步的,是交叉的,为什么是这样的结果呢?   关键字 synchronized 取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以在上面的示例中,哪个线程先执行带 synchronized 关键字的方法,哪个线程就会持有该方法所属对象的锁 Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。   但如果多个线程访问多个对象,则 JVM 会创建多个锁,上面的示例就是创建了2个 HasSelfPrivateNum 类的对象,所以就产生了2个锁。

脏读

  脏读一定会出现操作实例变量的情况下,这就是不同线程 “争抢” 实例变量的结果。在多个线程调用同一方法时,为了避免数据出现交叉的情况,要使用 synchronized 关键字来进行同步。

synchronized 锁重入

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

public class Service {
    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 MyThread extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}
MyThread thread = new MyThread();
thread.start();

  运行结果如下:

service1
service2
service3

  “可重入锁” 的概念是:自己可以再次获取自己的内部锁。比如有1个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。可重入锁也支持在父子类继承的环境中。

出现异常,锁自动释放

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

同步不具有继承性

  同步不可以继承。

更新于2019年04月28日

results matching ""

    No results matching ""