Live2D

juc并发编程

线程上下文

概念
我们都知道,jvm由堆、栈、方法区组成,其中栈内存就是给线程用的,每个线程启动后,虚拟机就会为其分
配一块栈内存。而每个栈又由多个栈帧组成,对应着每次方法调用时所占用的内存。每个线程只能有一个
活动栈帧,对应着当前正在执行的那个方法。而线程上下文就是由于一些原因导致cpu不再执行当前线程,
转而执行另一个线程的代码。

产生的原因

- 线程的cpu时间片运行完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法

当出现了上下文切换,在java中,java会使用程序计数器来保存当前线程的状态,是是线程私有的。
上下文切换频繁发生会影响性能

join方法

首先先看一个图
image
如果想让main线程等待t执行完再执行,就需要用到join方法。
image
join方法的作用就是等待线程的执行,要等待谁执行,就用谁.join()!

join的再理解

image

join限时同步

image

interrupt
打断阻塞

image

打断正常(不会直接打断程序的运行,只会把打断标识置为true)

image

守护线程

概念

默认情况下,java进程需要等待所有的线程运行完才结束,如下图:
image
当有守护线程,即使是守护线程的代码没有执行完,也会强制结束(垃圾回收就是守护线程的体现)

线程安全-局部变量

局部变量存放于在jvm栈中,在多线程情况下,会为每一个线程开辟一块栈内存,因此不存在线程安全的问题。
但是局部变量引用的对象未必,如果对象没有逃离方法的作用范围,它是线程安全的,如果逃离了,则需要考虑线程安全。

线程安全问题-暴露引用
image

线程安全-组合调用

虽然有一些线程安全的集合,提供了一些线程安全的方法,但是又的组合调用会出现线程不安全的问题,如图:
image

此时key的值成为了1,发生了这样的问题,其实主要是第一个线程在执行get方法完后,释放锁,还没 开始执行put方法的时候,第二个线程拿到了cpu分到的时间片,开始执行get方法,在释放锁后,可能 cpu分配的时间片用完了,第一个线程拿到执行权,进行put操作,在执行完put操作后,第二个线程拿到 执行权,执行put方法将第一个线程的值覆盖了。

Monitor

Java 对象头(以 32 位虚拟机为例)
普通对象

|--------------------------------------------------------------| 
 			Object Header (64 bits) 
|------------------------------------|-------------------------| 
            Mark Word (32 bits)		 Klass Word (32 bits)
|------------------------------------|-------------------------|

数组对象

|---------------------------------------------------------------------------------|
|                            Object Header (96 bits)
|--------------------------------|-----------------------|------------------------|
| 	Mark Word(32bits)        |  Klass Word(32bits)   |array length(32bits)
|--------------------------------|-----------------------|------------------------|

其中 Mark Word 结构为

|-------------------------------------------------------|--------------------|
|			 Mark Word (32 bits)            | State
|-------------------------------------------------------|--------------------|
|         hashcode:25 | age:4 | biased_lock:0 | 01      | Normal
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | 01      | Biased
|-------------------------------------------------------|--------------------|
|            ptr_to_lock_record:30            | 00      | Lightweight Locked
|-------------------------------------------------------|--------------------|
|         ptr_to_heavyweight_monitor:30       | 10      | Heavyweight Locked
|-------------------------------------------------------|--------------------|
|                                             |11       | Marked for GC
|-------------------------------------------------------|--------------------|

64 位虚拟机 Mark Word
image

Monitor的工作原理

image

synchronized-Monitor(监视器)

在jdk6以前,在多线程的情况下,我们在使用synchronized的时候,对象头里面的的MarkWord会存储指向
Monitor的指针,从而获取Monitor,线程1成为monitor的主人,此时线程2来了,尝试获取monitor锁,但是
发现minitor已经有主人了,于是在EntityList(阻塞队列)中等待,等待线程1执行完。

1synchronized-Lightweight Locked(轻量级锁)

static final Object obj = new Object();
public static void method1() {
 synchronized( obj ) {
 // 同步块 A
 method2();
 }
}
public static void method2() {
 synchronized( obj ) {
 // 同步块 B
 }
}
  • 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
    image
  • 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录
    image
  • 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下
    image
  • 如果 cas 失败,有两种情况
    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数
      image
  • 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
    image
  • 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程
      8.2锁膨胀
      概念
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量
级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

如以下代码:

static Object obj = new Object();
public static void method1() {
 synchronized( obj ) {
 // 同步块
 }
 }
  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

image

  • 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
    • `即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
    • 然后自己进入 Monitor 的 EntryList BLOCKED

image

  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程
    8.3 自旋优化
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出
了同步块,释放了锁),这时当前线程就可以避免阻塞。

- 自旋重试成功的情况:在自旋的某一次,当持有锁的线程解锁了,这个时候,就可以得到锁并成功上锁
- 自旋重试失败的情况:在自旋的过程中,持有锁的线程一直不释放锁,于是竞争的线程就会进入到
  EntiryList中阻塞

注意:

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会
  • 高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能
偏向锁
  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
  • Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
    例如:
static final Object obj = new Object();
public static void m1() {
 synchronized( obj ) {
 // 同步块 A
 m2();
 }
}
public static void m2() {
 synchronized( obj ) {
 // 同步块 B
 m3();
 }
}
public static void m3() {
 synchronized( obj ) {
 }
 }

如图解释偏向锁:
image

偏向锁的状态

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值
    -在上面测试代码运行时在添加 VM 参数 -XX:-UseBiasedLocking 禁用偏向锁
偏向锁的撤销
  • 调用对象的hascode:调用对象的hascode后,因为此对象的对象头的markword此时存储的是线程的ThreadId,所以调用hacode会导致偏向锁被撤销。
  • 调用 wait/notify

wait/notify

image

  • Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态

  • BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片

  • BLOCKED 线程会在 Owner 线程释放锁时唤醒

  • WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争

  • obj.wait(): 让进入 object 监视器的线程到 waitSet 等待

  • obj.notify(): 在 object 上正在 waitSet 等待的线程中挑一个唤醒

  • obj.notifyAll(): 让 object 上正在 waitSet 等待的线程全部唤醒
    必须获得锁对象,成为monitor的owner 才可以调用以上三个方法。`

    虚假唤醒

    有两个正在waitSet中等待的线程,我们想要唤醒一个线程,为了防止随机性,使用notifyAll()唤醒,但是产生了一个弊端,就是第二个不想唤醒的线程也被唤醒被执行了。解决方法就是换成while循环,循环判断条件

    10.设计模式-保护性暂停模式

    1. 定义
    即 Guarded Suspension,用在一个线程等待另一个线程的执行结果
    要点

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject

  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)

  • JDK 中,join 的实现、Future 的实现,采用的就是此模式因为要等待另一方的结果,因此归类到同步模式
    image

    线程的活跃性-死锁

    image

    ReentranLock锁

    锁的超时
    image

JMM(Java Memory Model)

JMM 即 Java Memory Model,它定义了主存(共享信息存储的位置)、工作内存(私有信息存储的位置)抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、
CPU 指令优化等。
JMM 体现在以下几个方面
原子性 - 保证指令不会受到线程上下文切换的影响
可见性 - 保证指令不会受 cpu 缓存的影响
有序性 - 保证指令不会受 cpu 指令并行优化的影响
可见性

1. 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
image
2.因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
image
3.1秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
image

voliate关键字
禁止指令重排
保证可见性
不保证原子性

无锁实现保护共享资源

package com.wl.Juc.Mehod;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class RunTest11 {
    public static void main(String[] args) {
        // 加锁的方式实现
        Account accountUnsafe = new AccountUnsafe(10000);
        Account.demo(accountUnsafe);
        // 不加锁的方式实现(使用cas)
        Account accountCas = new AccountCas(10000);
        Account.demo(accountCas);
    }
}
// 无锁实现保护共享资源
class AccountCas implements Account {
    private AtomicInteger balance;

    public AccountCas(Integer balance) {
        this.balance =new  AtomicInteger(balance);
    }

    @Override
    public void withdraw(Integer money) {
        while (true) {
            int prev = balance.get();
            int next = prev - money;
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
        // 可以简化为下面的方法
        // balance.addAndGet(-1 * amount);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }
}


class AccountUnsafe implements Account {
    private Integer balance;

    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public void withdraw(Integer money) {
        synchronized (this) {
            this.balance -= money;
        }
    }

    @Override
    public Integer getBalance() {
        synchronized (this) {
            return balance;
        }

    }
}

// 账户接口
interface Account {

    void withdraw(Integer money);

    Integer getBalance();

    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance()
                + " cost: " + (end - start) / 1000_000 + " ms");
    }
}

CAS

在保护共享资源,我们使用最多的方式是加锁。但是加锁有的时候会使程序的运行效率比较低,这个时候我们可以使用CAS来实现对共享资源的保护。
其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作。

image
CAS和Synchronized

  • CAS是基于乐观锁的实现,它认为别人不会修改数据,它会在每次修改数据前判断是否有人修改了当前数据,如果修改了,会下次再尝试。它的执行效率比synchronized高的原因是,他不会陷入阻塞,会一直持续执行。
  • synchronized是基于悲观锁的实现,它认为别人会修改数据,于是在每次操作数据的时候上锁,直到自己操作完成释放锁,才会让别人修改数据。这样会发生上下文切换,进入阻塞

    原子整数(是java提供的线程安全的操作)

	AtomicInteger ac = new AtomicInteger(100);
        System.out.println(ac.incrementAndGet()); // ++i
        System.out.println(ac.getAndIncrement()); // i++
        System.out.println(ac.addAndGet(-10)); //负数代表减,正数代表+
        System.out.println(ac.get()); //获取最新值
        System.out.println(ac.updateAndGet(s->s*10));//可以自己选择运算

原子引用

package com.wl.Juc.Mehod;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

public class RunTest12 {
    public static void main(String[] args) {
        DecimalAccountCas decimalAccountCas = new DecimalAccountCas(new BigDecimal("10000"));
        DecimalAccount.demo(decimalAccountCas);
    }
}

interface DecimalAccount {
    // 获取余额
    BigDecimal getBalance();

    // 取款
    void withdraw(BigDecimal amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(DecimalAccount account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(BigDecimal.TEN);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(account.getBalance());
    }
}

class DecimalAccountCas implements DecimalAccount {
    AtomicReference<BigDecimal> ref;

    public DecimalAccountCas(BigDecimal ref) {
        this.ref = new AtomicReference<BigDecimal>(ref);
    }

    @Override
    public BigDecimal getBalance() {
        return ref.get();
    }

    @Override
    public void withdraw(BigDecimal amount) {
        while (true) {
            // 获取最新值
            BigDecimal pre = ref.get();
            // 获取修改后的值
            BigDecimal next = pre.subtract(amount);
            if (ref.compareAndSet(pre, next)) {
                break;
            }

        }
    }
}

原子引用ABA问题
在执行CAS操作的时候,只能比较并更新值,无法判断在此期间,别人是否修改过共享变量值。于是就存在线程1修改共享变量的值A->B,线程2修改共享变量的值B->A。

package com.wl.Juc.Mehod;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicReference;
@Slf4j
public class RunTest13 {
    static AtomicReference<String> arf=new AtomicReference<>("A");
    public static void main(String[] args) throws InterruptedException {
        RunTest13.other();
        Thread.sleep(1000);
        arf.compareAndSet(arf.get(),"C");
        log.debug("change         A->C");

    }
    public static void other() throws InterruptedException {
        new Thread(
                ()->{
                   log.debug("change         A->B", arf.compareAndSet(arf.get(),"B"));
                },"t1"
        ).start();
        Thread.sleep(500);
        new Thread(
                ()->{
                    log.debug("change         B->A", arf.compareAndSet(arf.get(),"A"));
                },"t2"
        ).start();
    }
}

解决ABA问题-AtomicStampedReference
主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望:

  • 只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再一个版本号
  • 解决ABA问题主要是加版本号,每次修改让版本号加1
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
 log.debug("main start...");
 // 获取值 A
 String prev = ref.getReference();
 // 获取版本号
 int stamp = ref.getStamp();
 log.debug("版本 {}", stamp);
 // 如果中间有其它线程干扰,发生了 ABA 现象
 other();
 sleep(1);
 // 尝试改为 C
 log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() {
 new Thread(() -> {
 log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
 ref.getStamp(), ref.getStamp() + 1));
 log.debug("更新版本为 {}", ref.getStamp());
 }, "t1").start();
 sleep(0.5);
 new Thread(() -> {
 log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
 ref.getStamp(), ref.getStamp() + 1));
 log.debug("更新版本为 {}", ref.getStamp());
 }, "t2").start();
}
原子数组
AtomicIntegerArray 原子整型数组
AtomicLongArray 原子长整型数组
AtomicReferenceArray 原子引用数组
#####案例如下
package com.wl.Juc.Mehod;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public class RunTest15 {
    public static void main(String[] args) {
        // 原始数组
        demo(
                ()->new int[10],
                (arr)->arr.length,
                (arr,index)->arr[index]++,
                (arr)-> System.out.println(Arrays.toString(arr))
        );
        // 使用原子数租
        demo(
                ()->new AtomicIntegerArray(10),
                (arr)->arr.length(),
                (arr,index)->arr.getAndIncrement(index),
                (arr)-> System.out.println(arr)

        );
    }
    /**
     参数1,提供数组、可以是线程不安全数组或线程安全数组
     参数2,获取数组长度的方法
     参数3,自增方法,回传 array, index
     参数4,打印数组的方法
     */
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer ) {
        List<   Thread> ts = new ArrayList<>();
        T array = arraySupplier.get();
        int length = lengthFun.apply(array);
        for (int i = 0; i < length; i++) {
            // 每个线程对数组作 10000 次操作
            ts.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j%length);
                }
            }));
        }
        ts.forEach(t -> t.start()); // 启动所有线程
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }); // 等所有线程结束
        printConsumer.accept(array);
    }
}
原子更新器
AtomicReferenceFieldUpdater 原子引用类型字段更新
AtomicIntegerFieldUpdater 原子整数类型字段更新
AtomicLongFieldUpdater 原子长整型字段更新
#####代码如下
package com.wl.Juc.Mehod;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class RunTest16 {
    public static void main(String[] args) {
        Student stu = new Student();
        // 字段更新器
        AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
        System.out.println(updater.compareAndSet(stu,null,"李四"));
        System.out.println(stu);
    }


}
class Student{
    volatile String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}
UnSafe类

UnSafe类是不可以直接获取的,只能通过反射来进行操作。

import lombok.Data;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class RunTest17 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // Unsafe是可以直接操作内存的一个类,不是不安全的类。
        Field theUnsafe= Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        // 求出People中 id和name的偏移值
        long idOffset = unsafe.objectFieldOffset(People.class.getDeclaredField("id"));
        long nameOffset = unsafe.objectFieldOffset(People.class.getDeclaredField("name"));
        // 修改People对象中的值
        People people = new People();
        unsafe.compareAndSwapInt(people,idOffset,0,10);
        unsafe.compareAndSwapObject(people,nameOffset,null,"张三");
        // 输出people对象
        System.out.println(people);
    }
}
@Data
class People{
     volatile int id;
     volatile String name;

}

自己模仿写出AtomicInteger

package com.wl.Juc.Mehod;

import sun.misc.Unsafe;

public class RunTest18 {
    public static void main(String[] args) {
        Account.demo(new MyAtomicInteger(10000));
    }
}

class MyAtomicInteger implements Account{
    private volatile int value;
    // Unsafe类
    private static final Unsafe unsafe;
    // 偏移值
    private static long offsetValue;

    public MyAtomicInteger(int value) {
        this.value = value;
    }

    static {
        // 初始化UnSafe类
        unsafe = UnSafeUtils.getUnsafe();
        try {
            // 获取value的偏移值
            offsetValue=unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
    public int getValue() {
        return value;
    }
    public void decrement(int amount){
        while (true){
            int pre=this.value;
            int next=pre-amount;
            if (unsafe.compareAndSwapInt(this,offsetValue,pre,next)){
                break;
            }
        }
    }

    @Override
    public void withdraw(Integer money) {
        this.decrement(money);
    }

    @Override
    public Integer getBalance() {
        return getValue();
    }
}

工具类

package com.wl.Juc.Mehod;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class UnSafeUtils {
    private  static  Unsafe unsafe;
    private UnSafeUtils(){}

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
             unsafe= (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    public static Unsafe getUnsafe(){
        return unsafe;
    }
}

Account接口

// 账户接口
interface Account {

    void withdraw(Integer money);

    Integer getBalance();

    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        long start = System.nanoTime();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance()
                + " cost: " + (end - start) / 1000_000 + " ms");
    }
}

不可变对象

我们在操作共享资源的时候,可以使用加锁或者CAS无锁保证线程安全,还有一种就是使用不可变的对象保证线程安全。

以下是不可变时间日期保证线程安全:

package com.wl.Juc.Mehod;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;

/**
 * 不可变类的使用
 */
public class RunTest20 {
    public static void main(String[] args) {
        // 使用不可变类保证线程安全
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(
                    () -> {
                        try {
                            TemporalAccessor parse = dtf.parse("2021-01-09");
                            System.out.println(parse);

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

            ).start();

        }
    }
    /**
     * 加锁实现
     */
    private static void Lock () {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(
                    () -> {
                        try {
                            synchronized (sdf) {
                                Date parse = sdf.parse("2021-01-09");
                                System.out.println(parse);
                            }
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }

            ).start();
        }
    }
}

线程池

Executor线程连接池
    4. 线程池的7大参数


     public ThreadPoolExecutor(
     int corePoolSize,     // 核心线程池大小
     int maximumPoolSize,  // 最大核心线程池大小
     long keepAliveTime,   // 超时了没有人会调用就会释放
     TimeUnit unit,        // 超时单位
     BlockingQueue<Runnable> workQueue,  // 阻塞队列
     ThreadFactory threadFactory,        // 线程工厂,创建线程的,一般不用动
     RejectedExecutionHandler handler)   // 拒绝策略



     5.四大策略
            AbortPolicy()                抛出异常 不处理
            CallerRunsPolicy             不抛出异常  哪里来的去哪里
            DiscardOldestPolicy          队列满了,尝试和最早的竞争,如果竞争成功则执行,否则被抛弃,不会抛出异常
            DiscardPolicy                队列满了,丢掉任务,不会抛出异常
常见的线程池
FixedThreadPool(固定线程池)
  • 这种线程池的特点就是一个线程池中的线程数量是固定的。如果此线程池数量过少,当线程池任务过多时,会导致大量线程等待,如果过少,会浪费资源。
  • 使用的是 LinkedBlockingQueue(双向链表),阻塞队列是无界的。
SingleThreadPool(单线程池)
  • 这种线程池的特点就是一个线程池里只有一个线程。当线程任务过多,会造成大量线程等待。
  • 使用的是 LinkedBlockingQueue(双向链表),阻塞队列是无界的。
CachedThreadPool(弹性缓存线程池)

-这种线程池的特点是线程池在创建的时候是一个线程都没有的,当来了线程任务的时候,会自动新建线程,如果线程池有空余线程,则不会新建,这种线程池一般可以容纳几万个线程,线程空余60s会被回收,但是这样的写法也会带来一个缺点,一旦线程无限增长,到导致内存OOM。

  • 使用的是 SynchronousQueue(同步队列),它没有容量,没有线程来取是放不进去的
    Fork/Join
  • Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型
    运算
  • 所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解
  • Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运
    算效率
  • Fork/Join 默认会创建与 cpu 核心数大小相同的线程池

    13.AQS原理

    全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点:

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - cas 机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

  • tryAcquire
  • tryRelease
  • tryAcquireShared
  • tryReleaseShared
  • isHeldExclusively

ReentrantLock底层也用到了AQS,在ReentrantLock中有一个Sync(同步器),他继承了AQS。

posted @ 2021-07-27 21:39  没有梦想的java菜鸟  阅读(118)  评论(0编辑  收藏  举报