zoukankan      html  css  js  c++  java
  • 线程同步

    什么是线程同步,先来举一个小例子吧:

      我现在银行账户里有3000块,一天我去柜台机那里想取款2000元,我向柜台机完成了操作后,机器自动检查我的账户,看看够不够钱,发现够,于是准备吐钱(但又还没吐)。就在这个时候,我的老婆在另一个银行也准备取款,她也想取2000,她输入后,机器也去检查我的账户够不够钱,(因为银行还没吐)发现也够,于是也准备吐钱。就这样,我们在3000元的账户中取了4000元。

      我和老婆就相当于两个线程,我们在用同一个方法访问相同的资源。这个时候,就很容易出现数据前后不一致的问题。

    所以,我们对线程访问同一份资源的多个线程之间来进行协调的东西,叫做线程同步。

      那么要怎么解决这个问题呢?

      当我在取款的过程中,也就是在调用方法的过程中,这个账户归我独占。就好比两个人不能占一个坑的时候。

    现在我们用程序来体现两个线程同一个方法访问相同的资源:

    public class TestSync implements Runnable { //主类直接实现runnable接口
        Timer timer = new Timer();      //注意,这个是个引用类型的成员变量
        public static void main(String[] args) {
            TestSync test = new TestSync();     //只new了一个TestSync对象,所以这个Timer对象也只有一个  
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            t1.setName("t1");    
            t2.setName("t2");
            t1.start();
            t2.start();
        }
        public void run() {  //既然实现了runnable接口就要重写run()方法
            timer.add(Thread.currentThread().getName());
        }
    }
    
    class Timer {//类Timer
        private static int num = 0;  //定义一个静态的成员变量
        public void add(String name) {
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                
            }
            System.out.println(name+"你是第"+num+"个使用Timer的线程");
        }
    }

    内存图:

    得出来的答案是:

    t1你是第2个使用Timer的线程
    t2你是第2个使用Timer的线程

    显然和想象的不一样,因为比如t1这个线程先开始,它num++之后,sleep了一下下,就这一下下,t2也开始了,然后num++,此时num(注意num位static,是公用的内存)已经为2了,可见,线程在执行这个add方法的过程之中,被另外一个线程打断了。注意,即使不写sleep,这个线程也可能被打断,sleep只是放大这个效果。就像检查账户和吐钱这个操作中间被打断了。

      那么怎么解决呢?在执行这句话的过程之中,把当前的对象锁住就行了。

    用关键字synchronized(this) 叫锁定当前对象

    在执行这个关键字后面两个大括号里面的内容的过程之中,一个线程执行的过程之中,不会被另一个线程打断。当然这时这个成员变量num也锁定了。

    还有一种写法:

    public synchronized void add(String name) {

    }

    意思是在执行这个方法的过程中,锁定当前对象。

    也就是说,synchronized这个关键字会锁定某一段代码,它的内部含义是当执行这段代码的时候锁定当前对象,如果另外一个人如果也想访问我这个对象的话,他只能等着。

    这就是锁

    讲了锁之后,多线程还会带来其他的问题,比如一个典型的死锁

    啥叫死锁?

    现在你有一个线程,左边的那个,有个方法,它在执行的过程中需要锁定某个对象,除此以外,它还要锁定另外一个对象,它要锁定两个对象才能把整个操作完成。   这个时候,另外一个线程,它也需要锁定两个对象,然后它首先锁定的是第二个(下面那个对象)。然后只要再拥有上面那个对象的锁,它就能继续完成了。显然,现在这两个个线程都完成不下去了,你得等我执行完了,我才可以放开锁,可是你不给我另一个锁,我也执行不完,我执行不完,你也执行不完。

    现在用代码体现:

    public class TestDeadLock implements Runnable {
        public int flag;
        static Object o1 = new Object();
        static Object o2 = new Object();//两个静态对象,专门用来锁的
    
        public void run() { //重写run()
            System.out.println("flag = "+flag);
            if(flag == 1) {
                synchronized(o1) {//锁o1
                    try {
                        Thread.sleep(500);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(o2) {//又锁o2,所以这个线程的这个方法一定要锁定两个对象才能结束
                        System.out.println("1");
                    }
                }
            } else if(flag == 2){
                synchronized(o2) {//锁o2
                    try {
                        Thread.sleep(500);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(o1) {//又锁o1,所以这个线程的这个方法一定要锁定两个对象才能结束
                        System.out.println("2");
                    }
                }
            }        
        }
    
        public static void main(String[] args) {
            TestDeadLock td1 = new TestDeadLock();
            TestDeadLock td2 = new TestDeadLock();
            td1.flag = 1;
            td2.flag = 2;
            Thread thread1 = new Thread(td1);
            Thread thread2 = new Thread(td2);
            thread1.start();
            thread2.start();
        }
    
    }

    如何避免呢~所以尽量只锁定一个对象,或者你干脆一次锁起所有的对象。 

    锁定了一个对象,锁定了一个方法,另外一个线程绝对不能执行这段话但是它可以访问没有锁定的方法。

    public class TT implements Runnable {
        int b = 100;
        
        public synchronized void m1() throws Exception {
            b = 1000;
            Thread.sleep(5000);
            System.out.println("b = "+b);
        }
    
        public void m2() {
            System.out.println(b);
        }
    
        public void run() {
            try {
                m1();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        
        public static void main(String[] args) throws Exception {
            TT tt = new TT();
            Thread t = new Thread(tt);
            t.start();
    
            Thread.sleep(1000);//保证线程tt已经开始
            tt.m2();
        }
    }

    如果主线程在t线程执行m1()的时候不能访问m2(),那么结果应该是100.(其实还不太明白),因为它那个t线程的m1()给锁住了,还没结束,所以m2()看到的是100.但是,结果是1000,说明主线程可以访问没有被锁定的方法m2()。

    我们再改一下这个例子,更好得理解:

    public class TT implements Runnable {
        int b = 100;
        
        public synchronized void m1() throws Exception {
            b = 1000;
            Thread.sleep(5000);
            System.out.println("b = "+b);
        }
    
        public void m2() throws Exception {
            Thread.sleep(2500);
            b = 2000;
        }
    
        public void run() {
            try {
                m1();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        
        public static void main(String[] args) throws Exception {
            TT tt = new TT();
            Thread t = new Thread(tt);
            t.start();
    
            tt.m2();
        }
    }

    那么会输出什么呢?—————b = 2000;

    这也说明m1()执行的过程中被打断了。

    如果你在m2()前面也上锁:public synchronized void m2()            那么输出的就是2000了,因为在m1()执行的过程中,加锁了的m2()也想锁定这个对象,但是它锁不住,所以不能够改变b,只能能m1()执行完了。

    所以,别的线程可以自由访问非同步的方法,并且可能对你已经同步的方法产生影响。                                               一般就是比如说有个账户,有读的方法也有写的方法,你看读的方法它就不用加锁,因为你可以很多人一起读,这没事,但写的方法就要加锁了,他在写的时候,别人不能写。

  • 相关阅读:
    Java 中的悲观锁和乐观锁的实现
    乐观锁和悲观锁的区别
    理解RESTful架构
    修复Linux下curl等无法使用 Let's Encrypt 证书
    呕心沥血 AR VR 好资源分享
    linux服务器出现大量TIME_WAIT的解决方法
    Ubuntu系统 无法删除 redis-server
    Python Flask jsonify a Decimal Error
    微信小程序 订阅消息 对接详细记录
    FTP时显示500 Illegal PORT command的解决
  • 原文地址:https://www.cnblogs.com/wangshen31/p/6850719.html
Copyright © 2011-2022 走看看