Java程序栈Java Web知识

三、并发安全

2019-10-10  本文已影响0人  一直想上树的猪

线程间的共享

一、synchronized 内置锁

Java语言的关键字
作用:多个线程在同一时刻只能有一个线程进入这个方法或者代码块中。可以保证线程对于变量或者属性的原子性和可见性、排他性。
用处:作用于代码块或者方法上,进行修饰
synchronized关键字本质上是把对象做了一把锁,在代码块中需要对当前对象进行加锁,在方法上加锁缺省是对这个类的当前实例加锁,所以synchronized加的锁不是在方法上也不是在代码块上,本质上是在类的当前实例上。

加锁
synchronized关键字锁的是对象,锁的对象不同,线程就可以并行地执行

二、对象锁

首先先来运行两段代码

代码1
package com.tinner.thread;

/**
 * @Author Tinner
 * @create 2019/9/20 17:33
 */
public class DiffObj {
    private static class Obj1 implements Runnable{

        private DiffObj diffObj;

        public Obj1(DiffObj diffObj) {
            this.diffObj = diffObj;
        }

        @Override
        public void run() {
            System.out.println("TestObj1 is running ...." + diffObj);
            diffObj.instance();
        }
    }

    private static class Obj2 implements Runnable{

        private DiffObj diffObj;

        public Obj2(DiffObj diffObj) {
            this.diffObj = diffObj;
        }

        @Override
        public void run() {
            System.out.println("TestObj2 is running ...." + diffObj);
            diffObj.instance2();
        }
    }

    private synchronized void instance(){
        try {
            Thread.sleep(3000);
            System.out.println("synInstance1 is going..." + this.toString());
            Thread.sleep(3000);
            System.out.println("synInstance1 ended + " + this.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private synchronized void instance2(){
        try {
            Thread.sleep(3000);
            System.out.println("synInstance2 is going..." + this.toString());
            Thread.sleep(3000);
            System.out.println("synInstance2 ended + " + this.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DiffObj instance1 = new DiffObj();
        Thread t3 = new Thread(new Obj2(instance1));
        DiffObj instance2 = new DiffObj();
        Thread t4 = new Thread(new Obj1(instance1));
        //Thread t4 = new Thread(new Obj1(instance2));
        t3.start();
        t4.start();
        Thread.sleep(1000);
    }
}

运行结果1
同一实例
代码2

修改main方法

public static void main(String[] args) throws InterruptedException {
        DiffObj instance1 = new DiffObj();
        Thread t3 = new Thread(new Obj2(instance1));
        DiffObj instance2 = new DiffObj();
        //Thread t4 = new Thread(new Obj1(instance1));
        Thread t4 = new Thread(new Obj1(instance2));
        t3.start();
        t4.start();
        Thread.sleep(1000);
    }
运行结果2
不同实例
总结

比较两个运行结果,可以发现第一个方式是第一个线程执行完毕之后才执行第二个线程;第二个方式基本上是两个线程同时开始同时结束的。锁的实例不一样,也是可以并行的,只有两个线程锁的对象的实例是相同的时候,才能达到synchronized 那种效果。

三、类锁

将synchronized关键字加在一个类的static方法上的时候,才算是一个类锁。
类锁和对象锁之间还是可以相互的并行执行的。

代码
/**
 *类说明:演示实例锁和类锁是不同的,两者可以并行
 */
public class InstanceAndClass {
    
    private static class SynClass extends Thread{
        @Override
        public void run() {
            System.out.println("TestClass is running...");
            synClass();
        }
    }

    private static class ObjSyn implements Runnable{
        private InstanceAndClass SynClassAndInstance;

        public ObjSyn(InstanceAndClass SynClassAndInstance) {
            this.SynClassAndInstance = SynClassAndInstance;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..."+SynClassAndInstance);
            SynClassAndInstance.instance();
        }
    }

    //实例方法
    private synchronized void instance(){
        SleepTools.second(1);
        System.out.println("synInstance is going..."+this.toString());
        SleepTools.second(1);
        System.out.println("synInstance ended "+this.toString());
    }

    //静态方法
    private static synchronized void synClass(){
        SleepTools.second(1);
        System.out.println("synClass going...");
        SleepTools.second(1);
        System.out.println("synClass end");
    }

    public static void main(String[] args) {
        InstanceAndClass synClassAndInstance = new InstanceAndClass();
        Thread t1 = new SynClass();
        Thread t2 = new Thread(new ObjSyn(synClassAndInstance));
        t2.start();
        SleepTools.second(1);
        t1.start();
    }
}
运行结果
类锁

可以发现,这两个进程是可以并行执行的。

注意:

synchronized关键字只能锁对象,但是在类锁中,synchronized关键字明明用到了static方面,那么它锁的是这个class对象。当我们需要创建一个对象的实例的时候,虚拟机会进行一个类加载的过程,每一个类在虚拟机里面都有一个唯一的class对象。synchronized本质上锁的是每个类所独有的class对象。
那么就可以知道,instance方法锁的是实例对象,synClass方法锁的是类的对象,本质上他们锁的也是两个完全不同的对象,所以可以并行执行。

结论

从严格意义上来讲,类锁只是一个概念上的东西,并不是真实存在的。本质上锁的是类的class对象。而且类锁和对象锁之间也是互不干扰的。

四、volatile关键字,最轻量的同步机制

保证了变量的可见性。但是并没有提供变量的原子性,不能够保证复杂计算的时候数据的正确性。

代码1
/**
 * 类说明:演示Volatile的提供的可见性
 */
public class VolatileCase {
    private static boolean ready;
    private static int number;

    //
    private static class PrintThread extends Thread{
        @Override
        public void run() {
            System.out.println("PrintThread is running.......");
            while(!ready);//无限循环
            System.out.println("number = "+number);
        }
    }

    public static void main(String[] args) {
        new PrintThread().start();
        SleepTools.second(1);
        number = 51;
        ready = true;
        SleepTools.second(5);
        System.out.println("main is ended!");
    }
}

运行这段代码,可以看到内存飙升,程序根本停不下来,原因就是主方法中修改了ready变量的值之后,在线程中检测不到ready的变化,所以程序会一直运行下去

稍作修改

当我们给ready变量加一个volatile关键字之后

private static volatile boolean ready;

可以看到系统正常运行了,正常停止


volatile关键字作用
题外话

当我们不加volatile关键字,而是在无限循环中去加入一条打印语句的时候,看代码:

public class VolatileCase {
    private static boolean ready;
    private static int number;

    //
    private static class PrintThread extends Thread{
        @Override
        public void run() {
            System.out.println("PrintThread is running.......");
            while(!ready){
                //无限循环
                System.out.println("jinping");

            }
            System.out.println("number = "+number);
        }
    }

    public static void main(String[] args) {
        new PrintThread().start();
        SleepTools.second(1);
        number = 51;
        ready = true;
        SleepTools.second(5);
        System.out.println("main is ended!");
    }
}
运行结果
i题外话运行结果
可以看到程序正常的停止了。
为什么会出现这种现象呢?我并没有加volatile关键字啊
我们仅仅在无限循环中加了一个普通的打印语句,这个打印语句中牵涉到了synchronized关键字的内存语义。
image.png
说起来就要扯到JMM的内存模型中去了,synchronized关键字在内存语义上面强制要求把共享变量刷回到主内存,以及强制将使用的这个变量读到当前工作的内存上去。
最常见的适用场景:一个线程写,多个线程读

五、什么是线程安全?怎么才能做到线程安全?

什么是线程安全
如果说有多个线程访问同一个类的实例的时候,不管运行环境如何,我们的类的实例都能表现出正确的预期结果及行为,这就是线程安全。
实现线程安全的方式
其实本质上线程安全就是解决“修改--共享变量”的问题

/**
 * 类不可变--事实不可变
 */
public class ImmutableClassToo {
    private final List<Integer> list = Arrays.asList(1,2,3);

    public boolean isContain(int i){
        return list.contains(i);
    }
}
/**
 * 不安全的发布
 */
public class UnsafePublish {
    private List<Integer> list = new ArrayList<>(3);
    
    public UnsafePublish() {
        list.add(1);
        list.add(2);
        list.add(3);
    }
    
    public List getList() {
        return list;
    }

    public static void main(String[] args) {
        UnsafePublish unSafePublish = new UnsafePublish();
        List<Integer> list = unSafePublish.getList();
        System.out.println(list);
        list.add(4);
        System.out.println(list);
        System.out.println(unSafePublish.getList());
    }
}

/**
 * 安全的发布
 */
public class SafePublishToo {
    private List<Object> list
            = Collections.synchronizedList(new ArrayList<>(3));

    public SafePublishToo() {
        list.add(1);
        list.add(2);
        list.add(3);
    }

    public List getList() {
        return list;
    }

    public static void main(String[] args) {
        SafePublishToo safePublishToo = new SafePublishToo();
        List<Integer> list = safePublishToo.getList();
        System.out.println(list);
        list.add(4);
        System.out.println(list);
        System.out.println(safePublishToo.getList());
    }
}

Collections.synchronizedList()包装好的线程安全的。
自己封装

/**
 * 仿Collections对容器的包装,将内部成员对象进行线程安全包装
 */
public class SoftPublicUser {
    private final UserVo user;

    public UserVo getUser() {
        return user;
    }

    public SoftPublicUser(UserVo user) {
        this.user = new SynUser(user);
    }

    private static class SynUser extends UserVo{
        private final UserVo userVo;

        private final Object lock = new Object();

        public SynUser(UserVo userVo){
            this.userVo = userVo;
        }

        public int getAge() {
            synchronized (lock){
                return userVo.getAge();
            }
        }

        public void setAge(int age) {
            synchronized (lock){
                userVo.setAge(age);
            }
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读