阅读 65

《Java多线程编程核心技术》(二)

非线程安全会在多个线程对同一个对象中的实例变量进行并发访问时发生。产生的后果是脏读,也就是取到的数据其实是被更改过的。 线程安全就是获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

非线程安全问题存在于实例变量中,如果是方法内部的私有变量,则不存在非现场安全问题,所得的结果也就是线程安全的了。

synchronized同步方法

关键字synchronized取得的都是对象锁,而不是把一段代码和方法当做锁。 当多个线程访问同一个对象时,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock

如果多个线程访问多个对象,则JVM会创建多个锁。

调用关键字synchronized声明的方法一定是排队运行的。 既然使用了synchronized,那么肯定是对“共享”资源的读写访问。

如何查看Lock锁对象的效果呢?

public class MyObject {

    synchronized public void methodA(){
        try {
            System.out.println("begin 执行methodA, 线程为:"+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("end  执行methodA, 线程为:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void methodB(){
        try {
            System.out.println("begin 执行methodB, 线程为:"+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("end  执行methodB, 线程为:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {

    public static void main(String[] args){
	    //同一个实例对象
        MyObject object = new MyObject();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                object.methodA();
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                object.methodB();
            }
        });
        //启动两个线程,分别执行methodA和methodB方法
        threadA.start();
        threadB.start();
    }
}
/**
begin 执行methodA, 线程为:Thread-0
begin 执行methodB, 线程为:Thread-1
end  执行methodA, 线程为:Thread-0
end  执行methodB, 线程为:Thread-1
*/
复制代码

结论: threadA线程调用了synchronizedmethodA,持有了对象锁。但是threadB完全可以异步调用synchronizedmethodB。说明没有满足上述所说的排队执行。

改进:methodB增加synchronized关键字

public class MyObject {

    synchronized public void methodA(){
        try {
            System.out.println("begin 执行methodA, 线程为:"+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("end  执行methodA, 线程为:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void methodB(){
        try {
            System.out.println("begin 执行methodB, 线程为:"+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("end  执行methodB, 线程为:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
begin 执行methodA, 线程为:Thread-0
end  执行methodA, 线程为:Thread-0
begin 执行methodB, 线程为:Thread-1
end  执行methodB, 线程为:Thread-1
*/
复制代码

结论: threadA线程调用了synchronizedmethodA,持有了对象锁。前者执行完后,threadB才开始执行methodB。满足了上述所说的排队执行。

A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法

A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程X方法执行完,也就是释放对象锁后才可以调用。

synchronized锁重入

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

“可重入锁”的概念是:自己可以再次获取自己的内部锁。

比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

public class Service {

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

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

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

    }
}
public class Run {
    public static void main(String[] args){
        Service service = new Service();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.service1();
            }
        }).start();
    }
}
/**
service1
service2
service3
*/
复制代码

可重入锁也支持在父子类继承的环境中,子类的同步方法可以调用父类的同步方法。

synchronized同步语句块

使用synchronized同步代码块,一方面缩小了需要同步的范围,提高了程序的执行效率。另一方面,可以指定锁对象。

使用格式:synchronized(this){ .... }

public class Task {
    private String getData1;
    private String getData2;

    public void doLongTimeTask(){
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName="
                    + Thread.currentThread().getName();
            String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName="
                    + Thread.currentThread().getName();
            synchronized (this) {
                getData1 = privateGetData1;
                getData2 = privateGetData2;
            }
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
复制代码

如果使用同步方法,则每个线程都需要排队执行,导致每个线程最少执行3秒。而真正需要同步的地方只有getData1和getData2的赋值操作。耗时的操作没必要同步执行。

任意对象作为对象监视器

使用格式:synchronized(非this对象){ .... }

优点: 如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。

对象监视器不同,运行结果就是异步调用了。

public class MyObject {
    synchronized public void methodC(){
        try {
            System.out.println("methodC ____getLock time="
                    + System.currentTimeMillis() + " run ThreadName="
                    + Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("methodC releaseLock time="
                    + System.currentTimeMillis() + " run ThreadName="
                    + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Service {
    public void testMethod1(MyObject object) {
        synchronized (object) {
            try {
                System.out.println("testMethod1 ____getLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("testMethod1 releaseLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Run {
    public static void main(String[] args){
        MyObject object = new MyObject();
        Service service = new Service();
        // 同步方法和同步代码块使用的是同一个对象锁object,结果同步打印
        new Thread(new Runnable() {
            @Override
            public void run() {
                object.methodC();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.testMethod1(object);
            }
        }).start();
    }
}
/**
methodC ____getLock time=1528446201224 run ThreadName=Thread-0
methodC releaseLock time=1528446203224 run ThreadName=Thread-0
testMethod1 ____getLock time=1528446203224 run ThreadName=Thread-1
testMethod1 releaseLock time=1528446208225 run ThreadName=Thread-1
*/
复制代码

静态同步synchronized方法与synchronized(class)代码块

synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

证明不是同一个锁:

public class Service {
    public synchronized void testMethod1() {
        try {
            System.out.println("testMethod1 ____getLock time="
                    + System.currentTimeMillis() + " run ThreadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("testMethod1 releaseLock time="
                    + System.currentTimeMillis() + " run ThreadName="
                    + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run {

    public static void main(String[] args){
	    //异步执行
        Service service = new Service();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.testMethod1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.printA();
            }
        }).start();
    }
}
/**
testMethod1 ____getLock time=1528448721070 run ThreadName=Thread-0
线程名称为:Thread-1在1528448721070进入printA
线程名称为:Thread-1在1528448724070离开printA
testMethod1 releaseLock time=1528448726070 run ThreadName=Thread-0
*/
复制代码
文章分类
后端