> Java多线程(一) - Yuyy
Yuyy
Yuyy
Java多线程(一)

一、线程

1. 线程方法

  1. new T1().run() 调用run方法,同步的

  2. Thread.yield() 让一下CPU,线程进入等待队列,从RUNNING变为RUNABLE状态

  3. t.join() 等待t线程运行结束再运行

2. 线程状态

http://bed.yuyy.info/image-20210220103749081.png

注意:Wating的原因

二、关键字

1. volatile

  1. 保证线程可见性
  • MESI
  • 缓存一致性协议
  1. 禁止指令重排序
  • loadfence原语指令(读屏障)
  • storefence原语指令(写屏障)

  • DCL单例(Double Check Lock)

  • DCL指令重排序可能造成的问题

    • new对象的过程
      1. 分配内存(数据为默认值)
      2. 初始化数据
      3. 引用变量指向对象的内存地址

      http://bed.yuyy.info/image-20210220142925423.png

    • 指令重排序后,上诉步骤可能变成1-3-2,执行完3时,假如另一个线程来获取实例,通过Double Check的第一个Check检查时,发现引用变量不为NULL(此时引用变量指向的是刚分配内存但未初始化数据的对象),便拿去使用了,造成事故

  • 单例模式,饥汉式的缺点,程序一启动就创建对象,占内存。饿汉式是需要时再创建对象。

三、锁

1. synchronized

  1. Hotspot:synchronized通过对象头里的两位(总共64位)来标记的

  2. Synchronized加到非静态方法上等效于,synchronized(this){}

  3. Synchronized加到静态方法上等效于,synchronized(T.class){}

  4. synchronized异常会释放锁,可能造成数据异常(其他线程拿到的数据可能是没处理完的)

  5. synchronized的底层实现

    jdk早期:重量级-os

    后来改进:锁升级(只升不降)

    没错,我就是厕所所长!

  • 坑位偏向锁一把,先到先得,本所小线程负责贴标签(记录线程id)

  • 人少请自旋,其他线程为RUNABLE状态,占cpu,但不访问操作系统,在用户态解决,不经过内核态

  • 打转儿的次数够多(JDK1.6规定为10次),或者打转儿的人够多(JDK目前规定为自旋线程超过CPU内核数的一半),我们小线程们负责找重量级老大申请重量级锁-os,其他线程为WATTING状态,不占cpu,适合临界区执行时间长的场景

  1. synchronized(Object)不能锁String常量、Integer、Long,因为可能在不同的地方对同一个对象上了锁

  2. 不阻止指令重排序

  3. 锁的颗粒度尽量小

  4. 锁对象时,尽量使用final来定义引用变量,避免使用过程中,引用变量指向其他对象,造成线程不同,同一个锁,锁的对象不同,引发事故

2. CAS

Compare And Set,无锁优化,自旋

  1. CPU原语支持,运行期间不被打断

  2. cas(V,Expected,NewValue)
    if V==E
        V=New
    otherwise 
        try again or fail
    
  3. ABA问题

    线程1读取的共享变量为A,进行CAS操作,期间其他线程将该变量修改为B后,又修改为A。CAS认为此变量符合预期。

    解决方式:加版本号

  4. java.util.concurrent.atomic包下都是CAS原理

  5. atomicLong在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但是,高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong的自旋会成为瓶颈。

  6. AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。

    LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

    这种做法有没有似曾相识的感觉?没错,ConcurrentHashMap中的“分段锁”其实就是类似的思路。

3. ReentrantLock

  1. 需要手动lock和unlock,一般将unlock写到finally里。synchronized遇到异常会自动释放锁,而ReentrantLock不会。

  2. 底层为CAS加等待队列

  3. trylock:尝试获取锁

    boolean locked = false;
    try {
       locked=lock.tryLock(time:5,TimeUnit.SECONDS);
    System.out.println("m2..."+Locked);
    } catch(InterruptedException e){
    e.printStackTrace();
    } finally {
    if(locked) lock. unlock();
    }
    
  4. lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。

  5. 公平锁

    参数为true表示为公平锁,请求锁的线程在等待队列里FIFO

    ReentrantLock lock=new ReentrantLock(true);
    

4. CountDownLatch

CountDownLatch countDownLatch = new CountDownLatch;
countDownLatch.countDown();
try {
     countDownLatch.await();
} catch (InterruptedException e) {
     e.printStackTrace();
}

发表评论

textsms
account_circle
email

Yuyy

Java多线程(一)
一、线程 1. 线程方法 new T1().run() 调用run方法,同步的 Thread.yield() 让一下CPU,线程进入等待队列,从RUNNING变为RUNABLE状态 t.join() 等待t线程运行结束再运行 2. 线程状…
扫描二维码继续阅读
2021-02-20
友情链接
标签
归档
近期文章
分类
近期文章