多线程:线程状态、synchronized关键字、读写锁、条件对象、Volatile、阻塞队列等小结

多线程:线程状态、synchronized关键字、读写锁、条件对象、Volatile、阻塞队列等小结

关于多线程,在这里做下总结,也方便以后自己查阅。线程和进程的关系应该都知道了,这里不细说。在线程出现以前,进程是资源分配和处理机调度的基本单位,有了线程以后,进程只是资源分配的单位,而线程是处理机调度的单位。进程只分配资源,线程不分配或者说只包含极少的必要的资源。进程之下有线程,线程共享进程的资源。

这里说下线程的几种状态,在操作系统里是这样划分的:

线程状态有五种,分别是:新建、就绪、运行、阻塞、终止。其线程生命周期过程如下:

 

 

 

 

 

 

 

 

 

这里简单说明下各种状态以及之间的转换:

新建:指的是线程正在被创建,尚未尚未转到就绪状态。

就绪:已经处于准备运行的状态,即获得了处理机之外的所有资源,一旦拥有处理机,即可运行。

运行:正在处理机上运行,由就绪状态转变而来,也可以变为就绪状态和阻塞状态,此时时间片用尽或线程剥夺处理机,或资源被别的线程剥夺。

阻塞:正在等待某一事件或资源,一旦拥有后变为阻塞状态。

下面说下Java中线程的六种状态:在Java中,线程分为新创建、可运行、被阻塞、等待、计时等待六种。




新创建:NEW 、可运行:RUNNABLE(并不一定在运行、被阻塞:BLOCKED、  

等待:WAITING、计时等待:TIMED_WAITING

针对上图,可知,被阻塞是因为没有得到锁而引起的,等待是线程线程在等待某个事件通知,而计时等待是等待超时。其实在操作系统里,阻塞和等待是同一个概念,并没有这样细化分。

这里简单说下守护线程,守护线程唯一的用途就是为其他线程提供服务,如计时器线程,守护线程应该永远不去访问固定资源,因为它在任何一个操作之间都可能会发生终端,将一个线程转变为守护线程: t.setDaemon(true) 

还要解释下一直困扰我的一个问题,就是线程和代码的关系,如果弄不清楚这个问题,就很难理清线程并发的问题。我的理解是这样的,线程在操作系统中拥有一定的数据结构,如进程的PCB一样,在这个结构中,拥有一个代码入口,所以我们的线程才会执行一定的代码。这里就产生了多线程的问题,比如说,我们写了一个类或者说是方法,其中包含全局变量,有多个线程拥有此代码块的地址,所以会有多线程在一段时间同时执行,如果都是局部变量则不会有问题,但是对于全局变量,在内存会有一个内存区存储此变量,而多个线程之间运行此代码块的同时,操作的是同一个地址,所以产生了竞争的问题。

锁对象和条件对象:Java锁对象是JDK5之后新增内容,在java.util.concurrent包下,大概有ReentrantLockReentrantReadWriteLock等。条件对象指的是当满足一定条件时,调用await()方法使当前线程阻塞,并放弃锁,此时线程进入等待集,当锁可用时,此线程不能马上解除,相反,它处于阻塞状态,直到有线程调用同一条件上的signalAll()方法

简单用法如下:

Class myClass{

private Condition condition;

private ReentrantLock lock = new ReentrantLock();

public myClass(){

 Condition = lock.newCondition();

public void test(){

Try{

lock.lock();

while(...)

condition.await();

condition.signalAll();

}finally{

 

lock.unlock()

}

}

}

此处,signalAll()不会立即激活一个等待线程,它仅仅解除等待线程的阻塞,以便这些线程在当前线程退出同步方法后,通过竞争实现对对象的访问。而signal()方法则是随机选取一个线程激活,所以要使用signalAll()方法。对于notifyAll()也是同样的道理。 

对于读写锁ReentrantReadWriteLock,用法如下:

1)构造一个读写锁ReentrantReadWriteLock对象

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

2)获取读写锁

private Lock readLock = rwl.readLock();

private Lock writeLock = rwl.writeLock();

3)对所有的获取方法加读写锁

Public void transfer(...){

writeLock.lock();

try{....}

finally{

writeLock.unlock();

}

}

Lock readLock(): 得到一个可以被多个读操作共用的读锁,但会排斥所有写操作

Lock writeLock():得到一个写锁,排斥所有其他的读操作和写操作

synchronized关键字:这个应该不用多说,就是同步代码块和同步方法,以及加锁方式,可以用对象加锁,也可以用.class加锁,取决于线程共享范围。另外说明一下,对应于条件对象的signalAll()singnal()方法,同步关键及加锁用的是notifyAll()notify()方法,分别是唤醒全部等待此锁的线程和随机唤醒一个线程。 

注:synchroinzed关键字锁不可中断,ReentrentLock可中断(JDK5新特性)

Volatile关键字:对于这个关键字,一直有些争议,其实说的简单些,就是实现线程之间共享数据的可见性,所以就有人说,既然是可见的,那不就是线程安全的吗,这里并不是这样的,volatile关键字的原理,我理解是这样的,多个线程读取同一个内存地址,获得了数据,并对持有了这个数据(每个线程都有一份,或者说,在多处理器的机器上,存储在本地处理器缓存中),当线程修改了这个数据之后,使用此关键字会强制对内存缓冲区进行刷新,将任何一次修改都同步到临界内存地址,所以说,能否满足线程安全同步是要看不同的业务需求的。(V简而言之,Volatile域会被立即写入内存,而读操作就发生在内存中)锁和同步关键字的区别在于,锁不仅保持了线程之间的可见性,还提供了互斥独占的效果。比如说,如果要对一个资源进行同步,我们不仅要对写方法加锁,还要对其读方法加锁,这样才能实现同步,因为单纯的加写锁,只是实现了互斥的写操作,而并没有保证读之间的可见性,加锁之后,可以让线程能够看到最后上一个线程修改内容,这样才能有效实现安全同步。

对于多线程安全,建议优先使用并发包下的数据结构,i比如阻塞队列等,其次考虑同步关键字,最后考虑锁以及条件对象。就简单的总结这么多吧,本文是总结性的东西,并不是对每一点详细解释,不当之处,敬请指出。

注:条件对象的await方法、 Object的wait方法会使阻塞线程释放锁,而sleep方法、yield方法不释放锁!

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值