Java基础之多线程编程

一、线程状态切换

线程数量多少合适:

CPU密集型一般是等于CPU内核数,IO密集型是根据阻塞系数,比如空闲率50%的IO密集的任务,线程数一般是CPU核心数的两倍

为什么不能用stop来停止线程:

stop方法会清除栈内信息,结束该线程,这也就导致了run方法的逻辑不完整,输出语句println代表的是一段逻辑,可能非常重要,比如子线程的主逻辑、资源回收、情景初始化等,但是因为stop线程了,这些就都不再执行,于是就产生了业务逻辑不完整的情况。stop方法会破坏原子逻辑多线程为了解决共享资源抢占的问题,使用了锁概念,避免资源不同步,但是正因此原因,stop方法却会带来更大的麻烦:它会丢弃所有的锁,导致原子逻辑受损

停止线程的办法:

使用自定义的标志位决定线程的执行情况,在线程体中判断是否需要停止运行,即可保证线程体的逻辑完整性,而且也不会破坏原子逻辑

可能有读者对Java API比较熟悉,于是提出疑问:Thread不是还提供了interrupt中断线程的方法吗?这个方法可不是过时方法,那可以使用吗?它可以终止一个线程吗?非常好的问题,interrupt,名字看上去很像是终止一个线程的方法,但是我可以很明确地告诉你,它不是,它不能终止一个正在执行着的线程,它只是修改中断标志而已,例如下面一段代码:

执行这段代码,你会发现一直有Running在输出,永远不会停止,似乎执行了interrupt没有任何变化,那是因为interrupt方法不能终止一个线程状态,它只会改变中断标志位(如果在t1.interrupt()前后输出t1.isInterrupted()则会发现分别输出了false和true),如果需要终止该线程,还需要自行进行判断,例如我们可以使用interrupt编写出更加简洁、安全的终止线程代码

总之,如果期望终止一个正在运行的线程,则不能使用已经过时的stop方法,需要自行编码实现,如此即可保证原子逻辑不被破坏,代码逻辑不会出现异常。当然,如果我们使用的是线程池(比如ThreadPoolExecutor类),那么可以通过shutdown方法逐步关闭池中的线程,它采用的是比较温和、安全的关闭线程方法,完全不会产生类似stop方法的弊端。

二、线程同步方法:

1、CountDownLatch

是同步工具类之一,可以指定一个计数值,在并发环境下由线程进行减1操作,当计数值变为0之后,被await方法阻塞的线程将会唤醒,实现线程间的同步。

2、CyclicBarrier

CountDownLatch不可以复用,CyclicBarrier可以复用, CountDownLatch是减计数方式,而CyclicBarrier是加计数方式

           

3、Semaphore

信号量许可, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。常用于限流

4、ReentrantLock

1)可以用配套的Codition做唤醒

2)ReentrantLock有非公平锁ReentrantLock和公平锁ReentrantLock(true),公平锁就是谁等的时间最长,谁就先获取锁,非公平锁那就随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁

3)ReentrantLock可以用interrupt,免得两个线程互相等待对方,造成死锁

4)限时等待,也就是通过我们的tryLock方法来实现,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以将这种方法用来解决死锁问题。在这个案例中,一个线程获取lock1时候第一次失败,那就等10毫秒之后第二次获取,就这样一直不停的调试,一直等到获取到相应的资源为止。

5、synchronized

A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
D.同步关键字锁的是对象

三、乐观锁和悲观锁

1、乐观锁,atomic原子类,用的是CAS的方式,在更新的时候,去对比在我修改的期间数据有没有被其他人改变过:
如果没有改变过,就去正常修改数据。
如果数据和我一开始拿到的不一样,就不去修改数据,会选择放弃、报错、重试等策略。

2、悲观锁,悲观锁才是真正的锁定,用的是AQS,双向链表加状态锁。

AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。
获取锁通过CAS,那么没有获取到锁,等待获取锁是如何实现的?我们可以看一下else分支的逻辑,acquire方法:

  1. tryAcquire:会尝试再次通过CAS获取一次锁。
  2. addWaiter:通过自旋CAS,将当前线程加入上面锁的双向链表(等待队列)中。
  3. acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。

可以看到,当当前线程到头部的时候,尝试CAS更新锁状态,如果更新成功表示该等待线程获取成功。从头部移除。
基本可以确认,释放锁就是对AQS中的状态值State进行修改。同时更新下一个链表中的线程等待节点。

3.3 开销对比

  • 悲观锁的原始开销要高于乐观锁(写多),但是特点是一劳永逸,临界区持锁时间就算越来越差,也不会对互斥锁的开销造成影响
  • 乐观锁一开始的开销比悲观锁小(读多),但是如果自旋时间很长或者不停重试,那么消耗的资源也会越来越多。

三、Retre线程间的数据交换

1、atomic原子类,

2、Exchanger类:只能用于两个现场交换对象,超过之后不保证正确

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值