博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程同步
阅读量:5142 次
发布时间:2019-06-13

本文共 5005 字,大约阅读时间需要 16 分钟。

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

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

 

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

转载于:https://www.cnblogs.com/wangshen31/p/6850719.html

你可能感兴趣的文章
Windows10实用技巧-固定快捷方式到磁贴菜单方式
查看>>
mime.go
查看>>
微信公众平台接口配置问题
查看>>
SQL查询记录添加序号(HANA)
查看>>
如何与资源管理器互动剪切/拷贝/粘贴文件
查看>>
微信开发(1) :网页授权获取用户的基本信息 实现微信登录(转)
查看>>
linux用命令行编译使用函数库
查看>>
关于数学学习
查看>>
第九篇 AJAX
查看>>
CSS的一些总结
查看>>
C# 多线程,异步,并行编程
查看>>
面对对象初识
查看>>
隐私条款-77Studio
查看>>
flare3d_animation
查看>>
GridView行背景色改变效果
查看>>
Krpano教程 生成平面图
查看>>
常见的 JavaScript 内存泄露
查看>>
在Salesforce中实现对Object的增删改查操作
查看>>
火狐浏览器中js获取event对象
查看>>
【观点】“马云:金融是要为外行人服务",这个观点其实并不新鲜
查看>>