线程之间的同步

3 篇文章 0 订阅

线程同步

线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。


需要牢牢记住的第一点是:线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。


需要牢牢记住的第二点是 “共享”这两个字。只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。


需要牢牢记住的第三点是,只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。

 

需要牢牢记住的第四点是:多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。


如何才能线程同步
我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。
这一点一定要记住,同步锁是加在代码段上的。 




现在我们就来仔细分析“同步锁加在代码段上”的线程同步模型。 
首先,我们已经解决了同步锁加在哪里的问题。我们已经确定,同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。 


其次,我们要解决的问题是,我们应该在代码段上加什么样的锁。这个问题是重点中的重点。这是我们尤其要注意的问题:访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。 
这就是说,同步锁本身也一定是多个线程之间的共享对象。


Java语言里面用synchronized关键字给代码段加锁。整个语法形式表现为 
synchronized(同步锁) { 
// 访问共享资源,需要同步的代码段 
}


这里尤其要注意的就是,同步锁本身一定要是共享的对象。


… f1() {


Object lock1 = new Object(); // 产生一个同步锁


synchronized(lock1){ 
// 代码段 A 
// 访问共享资源 resource1 
// 需要同步 

}


上面这段代码没有任何意义。因为那个同步锁是在函数体内部产生的。每个线程调用这段代码的时候,都会产生一个新的同步锁。那么多个线程之间,使用的是不同的同步锁。根本达不到同步的目的。 
同步代码一定要写成如下的形式,才有意义。


public static final Object lock1 = new Object();


… f1() {


synchronized(lock1){ // lock1 是公用同步锁 
// 代码段 A 
// 访问共享资源 resource1 
// 需要同步 
}


你不一定要把同步锁声明为static或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。 




同步粒度 
在Java语言里面,我们可以直接把synchronized关键字直接加在函数的定义上。 
比如。 
… synchronized … f1() { 
// f1 代码段 
}


这段代码就等价于 
… f1() { 
synchronized(this){ // 同步锁就是对象本身 
// f1 代码段 

}


同样的原则适用于静态(static)函数 
比如。 
… static synchronized … f1() { 
// f1 代码段 
}


这段代码就等价于 
…static … f1() { 
synchronized(Class.forName(…)){ // 同步锁是类定义本身 
// f1 代码段 

}




信号量
信号量模型的工作方式如下:线程在运行的过程中,可以主动停下来,等待某个信号量的通知;这时候,该线程就进入到该信号量的待召(Waiting)队列当中;等到通知之后,再继续运行。 


在Java语言里面,任何一个Object Reference都可以作为同步锁。同样的道理,任何一个Object Reference也可以作为信号量。 
Object对象的wait()方法就是等待通知,Object对象的notify()方法就是发出通知。




(1)等待某个信号量的通知 
public static final Object signal = new Object();


… f1() { 
synchronized(singal) { // 首先我们要获取这个信号量。这个信号量同时也是一个同步锁


// 只有成功获取了signal这个信号量兼同步锁之后,我们才可能进入这段代码 
signal.wait(); // 这里要放弃信号量。本线程要进入signal信号量的待召(Waiting)队列


// 可怜。辛辛苦苦争取到手的信号量,就这么被放弃了


// 等到通知之后,从待召(Waiting)队列转到就绪(Ready)队列里面 
// 转到了就绪队列中,离CPU核心近了一步,就有机会继续执行下面的代码了。 
// 仍然需要把signal同步锁竞争到手,才能够真正继续执行下面的代码。命苦啊。 
… 

}


需要注意的是,上述代码中的signal.wait()的意思。signal.wait()很容易导致误解。signal.wait()的意思并不是说,signal开始wait,而是说,运行这段代码的当前线程开始wait这个signal对象,即进入signal对象的待召(Waiting)队列。




(2)发出某个信号量的通知 
… f2() { 
synchronized(singal) { // 首先,我们同样要获取这个信号量。同时也是一个同步锁。


// 只有成功获取了signal这个信号量兼同步锁之后,我们才可能进入这段代码 
signal.notify();

 // 这里,我们通知signal的待召队列中的某个线程。
// 如果某个线程等到了这个通知,那个线程就会转到就绪队列中 
// 但是本线程仍然继续拥有signal这个同步锁,本线程仍然继续执行 
// 嘿嘿,虽然本线程好心通知其他线程, 
// 但是,本线程可没有那么高风亮节,放弃到手的同步锁 
// 本线程继续执行下面的代码 
… 

}




需要注意的是,signal.notify()的意思。signal.notify()并不是通知signal这个对象本身。而是通知正在等待signal信号量的其他线程。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值