锁 同步器 多线程等

锁 同步 多线程

sun.misc.Unsafe 可以获得对象的地址,以及每个字段的偏移地址,有了地址就可以线程安全的取得和设定值。

例子:参照AbstractQueuedSynchronizer同步器源码
static {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField(“state”)); //在实例化的时候,执行static块,取得state变量的实际内存地址
}
执行方法
unsafe.compareAndSwapInt(this, stateOffset, expect, update);

CountDownLatch中我们主要用到两个方法一个是await()方法,调用这个方法的线程会被阻塞,另外一个是countDown()方法,调用这个方法会使计数器减一,当计数器的值为0时,因调用await()方法被阻塞的线程会被唤醒,继续执行。

和thread 的join差不多,但是 更灵活
可以在thread中 run中间执行 countDownLatch.countDown();,而不必等待这个线程完全结束

Semaphore 停车位

原子操作是指多线程可以安全操作

并发容器
LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列 适用于 生产消费多线程访问
两个锁,可同时生产和消费 高并发 ,但是消耗大,每个元素是一个节点,add 要 new del 要gc
 ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列 有界不能扩容,环形数组
一个锁,不能同时生产和消费
 PriorityBlockingQueue:按优先级排序的队列

LinkedBlockingQueue
↳ReentranLock lock接口的一个实现
↳AbstractQueuedSynchronizer reentranlock持有的一个类
封装了CAS判断states+线程队列管理 Node(violatile) +
LockSupport.uppark /park 唤醒/阻塞

1.park和wait的区别。wait让线程阻塞前,必须通过synchronized获取同步锁。
2.先notify在wait 会一起死锁阻塞,而park和unpark无论什么顺序都不会引起阻塞 因为
如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。

下面说的都是synchronized
synchronized 和violite都可以保证可见性

自旋锁 lock实现SpinLock TicketLock CLHLock

Condition
可以有选择的进行线程通知 在lock之内做

调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
  Conditon中的signal(),从阻塞队列唤醒下一个。

自旋锁 原理是利用了cas
JAVA CAS封装
 sun.misc.Unsafe是JDK里面的一个内部类,这个类当中有三个CAS的操作
Jdk1.5以后,提供了java.util.concurrent.atomic

主存里面有个值code=A,线程在操作的时候把code的值copy到自己的线程副本,
然后执行更改操作,在更改操作之前,拿这个副本和主存的code值进行比较,如果相同则更新,如果不
同则重试,直到一定时间后,如果还不行就退出失败,释放cpu
所谓自旋就是,为了减少线程的唤醒,用户态和内核态的切换,遇到有锁后,不释放cpu资源,一直
循环判断,是不是锁已经释放,这样的话 在锁竞争不激烈的情况下,性能很好

偏向锁
建立一个vector对象,因为vector是线程安全的,add方法外边加了synchronized同步块
偏向锁有效是jvm配置的-XX:+UseBiasedLocking  -XX:-UseBiastedLocking(禁用)
public static List numberList = new Vector();
public static void main(String[] args) {
long begin = System.currentTimeMillis();
int count = 0;
int startnum = 0;
while(count<10000000){
numberList.add(startnum);
startnum+=2;
count++;
}
long end = System.currentTimeMillis();
System.out.println(end-begin);
}
}
单线程,不停的操作vector的同步代码add,效率很快,因为只有第一次做cas
如果偏向锁,失败,这个时候有另一个线程访问这个vector,就升级成轻量级锁

每一个线程在准备获取共享资源时:
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,
之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,
把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord,
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己

锁升级
偏向锁,其实是无锁竞争下可重入锁的简单实现。流程是这样的 偏向锁->轻量级锁(cas,自旋锁)->重量级锁

ArrayBlockingQueue 有界 使用数组,不扩容 一个锁
LinkedBlockingQueue 同步容器,使用了两个lock
第二点,锁使用的比较。ArrayBlockingQueue内部使用1个锁来控制队列项的插入、取出操作,而LinkedBlockingQueue则是使用了2个锁来控制,一个名为putLock,另一个是takeLock,但是锁的本质都是ReentrantLock。因为LinkedBlockingQueue使用了2个锁的情况下,所以在一定程度上LinkedBlockingQueue能更好支持高并发的场景操作,这里指的是并发性上,不是吞吐量。

第三点,吞吐性能上的比较。这里其实会涉及到里面具体的操作差异的问题。在ArrayBlockingQueue内部,因为是直接使用数组空间的,而且都是预先分配好的,所以操作没有那么复杂,而在LinkedBlockingQueue中,是通过链表进行维护的,而且每次插入的对象还要转为Node<>(e)对象,相当于多做了一步操作,但是根据LinkedBlockingQueue的官方描述,它是具有更好吞吐性能的。

而lock实现原理是
AbstractQueuedSynchronizer

所谓线程池不是你线程跑完了就关闭了
是为了减少创建新线程而导致的性能开销
说白了就是重复利用线程
如果你的线程是非外部条件而永远不结束的,那么就没必要用线程池了
一般来说,线程池是不需要shutdown的
另外一个ExecutorService 的execute方法传入的是Runnable,不是已经创建好的Thread

重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性的实现需要解决以下两个问题。
1)线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
2)锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值