zoukankan      html  css  js  c++  java
  • JAVA 多线程(3)

    再讲线程安全:

    一、脏读

    脏读:在于读字,意在在读取实例变量时,实例变量有可能被另外一个线程更改了,导致获取到的数据出现异常。

    在非线程安全的情况下,如果线程A与线程B 共同使用对象实例C中的方法method,如果实例C存在实例变量,同时在method中会操作这个实例变量a,则有可能出现脏读的情况。

    也就是期望值不同,读取的数据不同,因为线程A与B会同时使用实例C的方法。

    例如:

    private String name;
    
        public static void main(String[] args){
            Test2 test2 = new Test2();
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
    //                System.out.println(Thread.currentThread().getName());
                    test2.testUnsafe("a","我是A");
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
    //                System.out.println(Thread.currentThread().getName());
                    test2.testUnsafe("b","我是B");
                }
            });
    
            t.start();
            t2.start();
        }
    
        public void testUnsafe(String name,String param){
            this.name = name;
            try {
          Thread.sleep(1000);
          System.out.println(this.name+":"+param);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
    }

     执行结果如下:

    代码中加上了sleep 为了模拟运算所需的时间。

    可以看出来,大概是这样的过程:线程B在run时首先抢到了资源,并运行了实例方法,并把实例变量name修改为b,

    然后继续执行,这时候线程A抢回了资源(因为不是同步的),它把实例变量那么又修改为b,这个是时候开始执行其他逻辑操作(这里用sleep模拟),

    然后2个线程轮番执行最后的操作-打印。

    因为这个时候实例变量name 已经变为a了, 所以线程B 出现脏读,和期望输出的  b:我是B  结果出现差异。

    如果操作的是不同的对象实例(在synchronized 同步里 jvm 会创建多个锁,下面的例子控制2个实例,也就是创建了2个锁),就不会出现这个问题了,还有如果name不是实例变量,只是私有变量的话也不会出现这种情况。

    修改一下看看:

     public static void main(String[] args){
    
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    Test2 test2 = new Test2();
                    test2.testUnsafe("a","我是A");
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    Test2 test2 = new Test2();
                    test2.testUnsafe("b","我是B");
                }
            });
    
            t.start();
            t2.start();
        }

    输出结果:

    如果想要保证使用实例变量而又不出现这种问题,怎么办,同步~ 可以使用 synchronized 方法或 synchronized代码块。

    例如:

    public synchronized void testUnsafe(String name,String param){
            this.name = name;
            try {
                Thread.sleep(1000);
                System.out.println(this.name+":"+param);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    

     输出结果:

     关于synchronized 想看的可以下看之前的随笔(《JAVA 多线程(1)》)。

    这里我想记录一下类与对象和实例的个人理解():

    Class 是类,编写时候我们称为类,编译好的class我们可以称为class对象,或者说类对象,由类对象new出来的是实例或者说叫对象实例也就是instance。

    它们分时期,分关系。类是抽象的概念,对象为具体的事物,人是类,张三是人,张三是人这个类具象,是一个对象实例的存在,它的嘴巴是实例变量,吃饭是实例方法,米饭和菜是吃饭这个对象方法中的方法私有变量,

    打比方张三、李四在一起去吃饭(2个线程),都调用了吃饭这个方法(吃饭这个方法是人类共有的),分布是土豆A与土豆B,如果不做同步,有可能2个人会夹到同一条土豆丝。

    好吧,上面的比方看看就好。

    二、可重入锁

    如果一个线程获取了或者说抢到了cpu资源,拿到了实例对象锁,那么在实例方法中在调用其他同步方法时,依然会获取到同一个锁,因为已经抢到了,

    比方说,一个屋子有3个房间(方法),张三(线程),抢到了房间1的锁,由于李四和王五压根就没进入这个屋子,所有他们俩必须等张三出来才行,这样的话

    张三进过房间1后,还能获得房间2、3的锁。

    这个锁指的是对象锁,或者说一个实例对象只有一把锁会更好理解。因为李四和王五想进房间2,虽然不是房间1,但是只有一把锁,这个锁在张三手上。

    可重入锁个人感觉主要贡献在于可继承,如果B基础了A,那么如果操作B,获取到了实例对象B的锁,那么也可以继续获得A的锁。

    三、异常释放锁

    当线程出现异常时,锁会自动释放。

    四、同步不具有继承

    如果类A 有同步方法 methodA,类B继承了类A并重写了methodA 方法,但是没有加上同步关键字,那么实际上,B类中重写的methodA 并不是同步方法。

    五、同步方法与同步代码块

    之前在1中写过,这里再提及,同步代码块同样时获得的是对象锁,当不同的实例方法各自有各自的同步代码块,当线程A访问methodA 时获得实例对象锁,如果线程B访问的实例对象与线程A相同,那么线程B如想

    访问methodB中的同步代码块时同样需要等线程A使用完methodA,释放对象锁,才能执行。

    六、非当前实例对象锁(任意对象监视器)

    优点:同一个类中的多个方法或者同一个方法中有多个代码块,为了提高性能,使用多个对象锁,来加快运行速度,或者说减少阻塞。

    例如:

    private Object object = new Object();

    public static void main(String[] args){
    Test2 test2 = new Test2();
    Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
    test2.test();
    }
    });

    Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
    test2.test2();
    }
    });

    t.start();
    t2.start();
    }
    public void test2(){
    synchronized (object){
    System.out.println("我是第二个块"+Thread.currentThread().getName());
    }
    }

    public void test(){
    synchronized (this){
    try {
    System.out.println("我是第一个块 开始:"+Thread.currentThread().getName());
    Thread.sleep(100);
    System.out.println("我是第一个块 结束:"+Thread.currentThread().getName());
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }

    输出:

    通过控制台打印结果可以看出,在A线程访问test方法执行的同时,B线程同样访问可test2方法。

    所以,他们互不干扰,因为不是一把锁。

    但是问题又出来,就因为这样提高了性能,但是又有可能会导致脏读,如果methodA与methodB 中有对同一个实例变量的写操作,那么及有可能出现脏读。

    如下:

    private Object object = new Object();
        private static List<String> list = new ArrayList<>();
        public static void main(String[] args){
            Test2 test2 = new Test2();
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.test();
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.test2();
                }
            });
    
            t.start();
            t2.start();
            try {
                Thread.sleep(6000);
                System.out.println(list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public void test2(){
            synchronized (object){
                judge("test2");
            }
        }
    
        public void test(){
            synchronized (this){
                judge("test");
    
            }
        }
    
        public void judge(String what){
            try {
                if(list.size() < 1){
                    Thread.sleep(2000);
                    list.add(what);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    

     输出结果:

    出现了,以为是异步的,所以线程A和线程B都可以同时访问到judge方法,又同时进入到了if语句中,导致最终结果输出为2,而不是我们期望的1。

    所以,方法就是,给judge加上同步锁:

    synchronized (list){
                    if(list.size() < 1){
                        Thread.sleep(2000);
                        list.add(what);
                    }
                }
    

    这样拿到list对象锁的操作就变成同步的了~

    明儿个继续~

    成灰之前,抓紧时间做点事!!
  • 相关阅读:
    ZOJ 3765 Lights (zju March I)伸展树Splay
    UVA 11922 伸展树Splay 第一题
    UVALive 4794 Sharing Chocolate DP
    ZOJ 3757 Alice and Bod 模拟
    UVALive 3983 捡垃圾的机器人 DP
    UVA 10891 SUM游戏 DP
    poj 1328 Radar Installatio【贪心】
    poj 3264 Balanced Lineup【RMQ-ST查询区间最大最小值之差 +模板应用】
    【转】RMQ-ST算法详解
    poj 3083 Children of the Candy Corn 【条件约束dfs搜索 + bfs搜索】【复习搜索题目一定要看这道题目】
  • 原文地址:https://www.cnblogs.com/jony-it/p/10770342.html
Copyright © 2011-2022 走看看