线程同步

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

  我现在银行账户里有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()执行完了。

 

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

posted @ 2017-05-17 12:55  汪神  阅读(152)  评论(0)    收藏  举报