JAVA线程[Thread]--同步篇
JAVA线程[Thread]--同步篇
Candy_Desire 发表于3年前
JAVA线程[Thread]--同步篇
  • 发表于 3年前
  • 阅读 915
  • 收藏 4
  • 点赞 2
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

一、线程简介:

线程(Thread)是进程中某个单一顺序的控制流。也被称为轻量进程(lightweight processes)。计算机科学术语,指运行中的程序的调度单位。

二、共享资源:

所谓共享资源是指可以被一个以上任务使用的资源叫做共享资源。

为了防止数据被破坏,每个任务在与共享资源打交道时,必须独占该资源。而多个线程之间有可能共享一些资源如:内存,文件,数据库等。 当多个线程同时读写同一份共享资源的时,可能会引起冲突。这时候就需要引入线程“同步”机制。 

三、同步机制:

同步(synchronized)实际含义和字面相反。同步是指有序,各线程之间要有序对共享资源进行操作,而不是同时进行操作。 

对于线程同步容易混淆和需要注意的地方:

(1)、同步是指有序的执行共享资源。

(2)、只有共享的资源才需要同步。

(3)、共享的常量资源不需要同步,只有变量资源需要同步。

(4)、无论是否执行同一段代码,只要共享可变资源就需要同步。

四、同步锁:

线程同步的基本实现思路是给共享资源加锁,只有线程获取了这把锁,才有权利访问该共享资源。 

既然是共享资源需要同步,所以同步锁固然是要加在共享资源上,对于一些比较完善的共享资源如:文件系统,数据库系统等,他们自身就提供了比较完善的同步锁机制,我们就不必再添加锁了。

对于同步锁容易混淆和需要注意的地方:

(1)、一般说线程同步的实现方式是给共享资源加锁,这样的表述容易让人误以为锁是要加在共享资源上面的,而实际上同步锁是要加在访问共享资源的代码段上的。

(2)、访问同一份共享资源的不同代码段,应该加同一个锁。如果不是同一个锁就没有同步的意义啦。就是说同步锁本身也是多线程之间的共享对象。

五、实现方式:

同步锁的声明可以不必为static或public,但是一定要保证同步代码之间要使用同一个锁。在Java中任何一个Object Reference都可以作为同步锁,可以理解为对象在内存分配系统中的内存地址,因此要实现同步就必须保证同步锁指向同一个地址。

同一时刻,只有一个线程可以获得lock的所有权,其他竞争lock的线程只能暂停运行,进入到该同步锁的就绪(Ready)队列。一般同步锁都挂有几个线程的队列,包括就绪队列(Ready)、待召队列(Waiting)等。就绪队列里的线程时刻准备的运行,而待召队列里的线程只能一直等待,直到等到某个信号的通知。才能加入到就绪队列中竞争同步锁。成功获得锁的线程执行完代码段后,会自动释放同步锁,就绪队列的线程可继续竞争锁。因此线程同步时非常消耗资源的,要控制好同步粒度。

【注:竞争同步锁失败的线程进入该锁的就绪队列(Ready)不是待召队列(Waiting)】

(1)、正确例子

public static final Object lock =new Object();
synchronized(lock){
//访问共享资源的代码段
}

注:同步锁也要是共享的对象】

(2)、错误例子

void synTest(){
Object lock =new Object();
    synchronized(lock){
    //访问共享资源的代码段
    }
}

【注:由于同步锁是由函数体内部产生的,每个线程调用这段代码都会新建一个同步锁,因此没有同步意义】

六、同步粒度:

synchronized关键字可以直接加在函数的定义上。

void synchronized synTest() { 
//代码段 
}

上面代码等价于

void  synTest() { 
    synchronized(this){
    //对象本身就是锁
    //代码段 
    }
}

//同样适用于静态函数
void  synTest() { 
    synchronized(Class.forName(...)){
    //类本身就是锁
    //代码段 
    }
}

【注:尽量避免把synchronized直接加载函数上,要控制好同步的粒度,同步的代码段越小越好】

在控制同步粒度的同时也要细分好同步锁。例如要同步不同的共享资源时就没有必要公用同一个锁。

例如:

public static final Object lockOne = new Object(); 

void threadOne(){
    synchronized(lockOne){
    //共享资源A
    }
}

void threadTwo(){
    synchronized(lockOne){
    //共享资源B
    }
}

上段代码不是共享一个资源但是都要竞争同一个锁,这样会降低程序的效率,可重新定义个新锁用于B资源的同步。

七、信号量:

信号量模型比同步锁模型复杂许多可跨进程同步,还有计数和控制同事运行线程数等功能,用于处理更复杂的同步模型如:读写同步、生产者、消费者模型等。

信号量的运行方式是可以主动停下正在运行的线程,等待某个信号量的通知后,此时该线程进入到待召队列(Waiting)等到通知后该线程再次进入到就绪队列(Ready)和其他线程竞争同步锁。

信号量的基本模型是等待/通知模型,其他较为复杂的模型都是在此基础上衍生出来的。这里我们先将就基本模型,其他模型后续再更新。

在Java中任何一个Object Reference都可以作为同步锁和信号量。我们知道Object对象有wait()和notify()两个方法,这两个方法分别就是等待通知和发出通知的方法。

(1)、wait的用法:

public static final Object signal = new Object(); 

void waitTest(){
    //获取信号量(同步锁)
    synchronized(signal){
    //该线程进入signal对象的待召队列(Waiting)
    signal.wait();
    //本线程放弃同步锁,后续代码不在执行。
    }
}

【注:signal.wait()不是signal开始等待,而是当前线程开始等待这个signal对象】

wait其实还可以定义等待时间,当时间到了以后处于待召队列中的线程就不在等待直接进入到就绪队列。

(2)、notify的用法:

public static final Object signal = new Object(); 

void notifyTest(){
    //获取信号量(同步锁)
    synchronized(signal){
    //通知signal对象中待召队列中等待的线程进入到就绪队列
    signal.notify();
    //本线程依然持有同步锁,后续代码继续执行。
    }
}

【注:signal.notify()不是通知signal这个对象本身,而是通知正在等待信号量的其他线程进入到就绪队列,线程本身继续持有同步锁,并正常执行后续代码】

还有一个notifyAll()方法,该方法是通知待召队列里面的所有线程。用法类似这里不做介绍了


共有 人打赏支持
粉丝 30
博文 71
码字总数 84592
×
Candy_Desire
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: