多线程与高并发

个人学习笔记,内容仅供参考!

1、线程创建的两种方式

  • 继承Thread类,重写run()方法
  • 实现Runnable接口,实现run()方法
  • 实现Callable接口,实现call()方法,有返回值,在线程池中使用
import java.util.concurrent.*;

/**
 * @author IT00ZYQ
 * @Date 2021/3/3 21:15
 **/
public class HowToCreateThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 方式1:继承Thread类
        new Thread01().start();
        // 方式2:实现Runnable接口
        new Thread(new Run()).start();
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        System.out.println("执行main线程,结果: " + sum);
        // 方式3:Callable接口,但需要线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> submit = executorService.submit(new Call());
        System.out.println("结果: " + submit.get());
        executorService.shutdown();
    }


}

class Thread01 extends Thread {
    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        System.out.println("创建线程的第一种方式,继承Thread类,重写run()方法,结果: " + sum);
    }
}

class Run implements Runnable {
    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        System.out.println("创建线程的第二种方式,实现Runnable接口,实现run()方法,结果: " + sum);
    }
}

class Call implements Callable<String> {

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        System.out.println("创建线程的第三种方式,实现Callable接口,实现call()方法");
        return String.valueOf(sum);
    }
}

2、Sleep、Join、Yield

  • Sleep:线程睡眠一定时间,进入TIMED_WAITING状态。不释放锁。
  • Join:在T1线程中执行T2.join();,则T1线程必须等待T2线程结束后才继续执行。
  • Yield:线程让出资源,此时处于RUNNABLE状态中的就绪状态。如果没有其他等待的线程,该线程重新进入运行状态。

3、线程状态

  • NEW 新建
  • RUNNABLE 就绪/执行
  • BLOCKED 阻塞
  • WAITING 等待
  • TIMED_WAITING 等待一定时间
  • TERMINATED 停止

4、synchronize关键字

  • 给对象加锁(并不是给代码块加锁),每个对象头部都有一个monitor(监视器),当线程获取锁时,monitor中的count+1,当线程释放锁时,monitor中的count-1,用count来标志对象锁释放被线程持有。
  • 当synchronize加在普通方法与静态方法上时,用ACC_SYNCHRONIZED标识符来标志该方法是一个同步方法。
  • 当synchronize加在代码块上时,字节码同步代码块前后分别有monitorenter和monitorexit指令
  • 默认给当前对象加锁,也可以给class对象加锁。
  • 同步方法与非同步方法可以同时执行
  • 不能所在String、Integer等基本类型的包装类对象上
  • 锁升级:当只有一个线程时,只是在对象头部标志了线程ID,为偏向锁。当有第二个线程想要获取锁时,自旋获取锁(默认10次),此时为自旋锁;当自旋无法获取锁时,升级为重量级锁,进入等待队列。

5、volatile关键字

  • 保证线程间可见,线程是共享堆内存的,线程也有自己的内存,当其中一个线程修改了共享变量时,另一个线程的值可能没有及时更新,加了volatile标志变量为易变的,CPU会实时监听。缓存一致性协议。
  • 禁止指令重排,添加内存屏障(例:单例模式中的DCL),Load/Store指令屏障。

6、CAS(Compare And Set 无锁优化 自旋)

  • CAS操作CPU原语支持,原子性操作
  • 可能出现ABA问题,如果是基本数据类型不影响结果,如果是对象引用,可能出现问题。解决ABA问题可以加上版本号的方法解决。
CAS(Expected, NewValue) {
	// 判断当前值是否为期望值
	if V == Expected
		// 赋予新值
		V = NEW
	else
		// 当前值不为期望值,重新尝试(自旋)或者操作失败
		try again or fail
}
  • AtomicInteger等Atomic就是使用了CAS锁,使用了Unsafe类,Unsafe类是直接操作虚拟机内存的。
  • 加锁代码执行时间长,竞争线程多,使用synchronized,需要系统调用进行加锁
  • 加锁代码执行时间短,竞争线程少,使用自旋锁,自旋时需要消耗CPU资源,但是不需要进行用户态和内核态的切换

7、各种锁

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 10:45
 **/
public class SleepUtil {
    public static void sleepSecond(int second) {
        try {
            Thread.sleep(second * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
7.1、ReentrantLock
  • 可以有多个等待队列
  • lock.newCondition()
/**
 * @author IT00ZYQ
 * @Date 2021/3/4 10:29
 **/
public class T01_ReentrantLock {

    synchronized void m1() {
        for (int i = 0; i < 20; i++) {
            SleepUtil.sleepSecond(1);
            System.out.println("m1 running ...");
        }
    }

    synchronized void m2() {
        System.out.println("m2 running ...");
    }

    public static void main(String[] args) {
        T01_ReentrantLock t = new T01_ReentrantLock();
        // m1方法加了synchronized锁,当前对象已被锁住
        new Thread(t::m1).start();
        SleepUtil.sleepSecond(1);
        // m2方法需要获取当前对象锁,必须等待上面的线程运行结束释放锁
        new Thread(t::m2).start();
    }
}

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 10:29
 **/
public class T02_ReentrantLock {
    Lock lock = new ReentrantLock();


    void m1() {
        try {
            lock.lock(); // 相当于synchronize(this)
            for (int i = 0; i < 20; i++) {
                SleepUtil.sleepSecond(1);
                System.out.println("m1 running ...");
            }
        }finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock(); // 相当于synchronize(this)
            System.out.println("m2 running ...");
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        T02_ReentrantLock t = new T02_ReentrantLock();
        // m1方法加了调用了lock(),当前对象已被锁住
        new Thread(t::m1).start();
        SleepUtil.sleepSecond(1);
        // m2方法需要调用lock(),必须等待上面的线程运行结束释放锁
        new Thread(t::m2).start();
    }
}

7.2、CountDownLatch(倒数阀门)
import java.util.concurrent.CountDownLatch;

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 11:05
 **/
public class T01_CountDownLatch {
    static int COUNT = 100;

    public static void main(String[] args) {
        // 倒数阀门,需要count为0时才能通过
        CountDownLatch latch = new CountDownLatch(COUNT);
        Thread[] threads = new Thread[COUNT];

        // 创建COUNT个线程
        for (int i = 1; i <= COUNT; i++) {
            int t = i;
            threads[i - 1] = new Thread(() -> {
                System.out.println("执行倒数, 当前线程: " + t);
                latch.countDown();
            });
        }
        // 启动COUNT个线程
        for (int i = 1; i <= COUNT; i++) {
            threads[i - 1].start();
        }

        for (int i = 0; i < COUNT; i++) {
            System.out.println("CountDownLatch倒数结束,阀门未开启" + i);
        }


        try {
            // 添加阀门,只有countdown为0时才能通过
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 10; i++) {
            System.out.println("CountDownLatch倒数结束,阀门开启");
        }

    }
}

7.3、CyclicBarrier(循环障碍)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 11:16
 **/
public class T01_CyclicBarrier {

    static int COUNT = 100;
    static int BARRIER = 20;

    public static void main(String[] args) {
        // 循环障碍 线程计数达到20才能通过
        CyclicBarrier cb = new CyclicBarrier(BARRIER, () -> {
            System.out.println("已满20人,通过");
        });
        for (int i = 0; i < COUNT; i++) {
            int t = i;
            new Thread(() -> {
                System.out.println("我是线程: " + t + ", 当前等待数" + cb.getNumberWaiting());
                try {
                    cb.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
7.4、Semaphore(信号量)
import java.util.concurrent.Semaphore;

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 11:36
 **/
public class T01_Semaphore {
    /**
     * 信号量,只有信号量大于0时才能拿到锁
     */
    static Semaphore s = new Semaphore(4);

    static void testRun() {
        try {
            System.out.println(Thread.currentThread().getName() + "判断信号量,判断是否能拿到锁,等待拿到锁...");
            // 拿到锁需要的信号量,默认为1
            s.acquire();
            System.out.println(Thread.currentThread().getName() + "成功拿到锁");
            SleepUtil.sleepSecond(5);
            System.out.println(Thread.currentThread().getName() + "运行结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放信号量
            s.release();
        }
    }

    public static void main(String[] args) {

        new Thread(() -> testRun(), "t1").start();
        new Thread(() -> testRun(), "t2").start();
        new Thread(() -> testRun(), "t3").start();
        new Thread(() -> testRun(), "t4").start();
        new Thread(() -> testRun(), "t5").start();

    }
}
7.5、ReadWriteLock(读写锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 11:24
 **/
public class T01_ReadWriteLock {
    /**
     * 读写锁
     * 读锁是共享锁
     * 写锁是排他锁
     */
    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    static void read(Lock lock) {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "开始Read操作");
            SleepUtil.sleepSecond(5);
            System.out.println(Thread.currentThread().getName() + "Read操作结束");
        } finally {
            lock.unlock();
        }
    }

    static void write(Lock lock) {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "开始Write操作");
            SleepUtil.sleepSecond(3);
            System.out.println(Thread.currentThread().getName() + "Write操作结束");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(() -> read(readLock), "Read-01").start();
        new Thread(() -> read(readLock), "Read-02").start();
        new Thread(() -> write(writeLock), "Write-01").start();
        new Thread(() -> write(writeLock), "Write-02").start();
        new Thread(() -> read(readLock), "Read-03").start();
        new Thread(() -> read(readLock), "Read-04").start();
    }
}

7.6、LockSupport
import java.util.concurrent.locks.LockSupport;

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 15:34
 **/
public class T07_LockSupport {

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("t1线程: " + i);
                if (i == 5) {
                    System.out.println("线程t1 park()等待线程t2运行结束...");
                    // 停车
                    LockSupport.park();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("t2线程: " + i);
            }
            System.out.println("线程t2运行结束, unPark() t1...");
            LockSupport.unpark(t1);
        });

        t1.start();
        t2.start();
    }
}

8、AQS

AbstractQuenedSynchronizer抽象的队列式同步器。基于CAS+volatile实现。
在这里插入图片描述

9、ThreadLocal(仅当前线程可见)

/**
 * @author IT00ZYQ
 * @Date 2021/3/4 18:50
 **/
public class T08_ThreadLocal {
    private static ThreadLocal<Integer> tl = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println("5秒后读取ThreadLocal中的变量...");
            SleepUtil.sleepSecond(5);
            System.out.println(tl.get());
        }).start();


        new Thread(() -> {
            System.out.println("3秒后设置ThreadLocal中的变量...");
            SleepUtil.sleepSecond(3);
            // 这里设置了ThreadLocal中的值,分析源码可知是设置到当前线程Thread对象中的,
            // 其他线程不可见
            tl.set(10);
            System.out.println("ThreadLocal变量设置完毕!");
        }).start();

        // ThreadLocal用完要remove,否则会造成内存泄露,对象一直不会被回收
        tl.remove();
    }
}

10、VarHandle

添加一个引用指向某个对象,通过VarHandle可进行原子性操作(CAS),比反射快,直接操纵二进制码

11、四种引用-强软弱虚

  • 强引用:一般的引用都是强引用,如new()的对象
  • 软引用:当内存不足时,gc会回收软引用指向的对象,可用作缓存
  • 弱引用:只要触发gc,只有弱引用指向的对象会被回收,例ThreadLocal中的ThreadLocalMap
  • 虚引用:构造函数有两个参数,一个是虚引用指向的对象,一个是队列,虚引用只要发生gc就会被回收,回收后会将对象入队到队列中。写JVM的人处理对外内存用的。
import java.lang.ref.SoftReference;

public class T02_SoftReference {
    public static void main(String[] args) {
        SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
        //m = null;
        System.out.println(m.get());
        System.gc();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(m.get());

        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
        byte[] b = new byte[1024*1024*15];
        System.out.println(m.get());
    }
}
import java.lang.ref.WeakReference;

public class T03_WeakReference {
    public static void main(String[] args) {
        WeakReference<M> m = new WeakReference<>(new M());

        System.out.println(m.get());
        System.gc();
        System.out.println(m.get());


        ThreadLocal<M> tl = new ThreadLocal<>();
        tl.set(new M());
        tl.remove();

    }
}
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;

public class T04_PhantomReference {
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();

    public static void main(String[] args) {


        PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);


        new Thread(() -> {
            while (true) {
                LIST.add(new byte[1024 * 1024]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                System.out.println(phantomReference.get());
            }
        }).start();

        new Thread(() -> {
            while (true) {
                Reference<? extends M> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
                }
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

12、线程池

12.1、为什么建议使用线程池?
  • 可以重复利用已有线程执行任务,减少线程的创建和销毁,减少资源消耗。
  • 通过线程池管理线程,可根据系统的承受能力调整线程池可运行的数量。
12.2、创建线程池的参数
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:非核心线程,空闲存活时间
  • TimeUnit:时间单位
  • workQueue:阻塞队列,用于保存任务的阻塞队列
  • threadFactory:创建线程的工程类
  • handler:拒绝策略
    常见的拒绝策略
  • AbortPolicy:丢弃任务并抛出RejectedExecutorException异常,默认的拒绝策略、
  • DiscardPolicy:丢弃任务但不抛出异常
  • DiscardOldestPolicy:丢弃队列最前面的任务,然后尝试重新执行任务。
  • CallerRunsPolicy:由调用线程处理任务
12.3、常见线程池

在这里插入图片描述

  • CachedThreadPool:线程数量没有固定,可达到Integer的最大值(Integer.MAX_VALUE);线程池中的线程可进行重复利用和回收,默认回收时间为1分钟;当线程池中没有空闲线程时,会创建一个新的线程。
  • FixThreadPool:线程的数量固定,可很好地控制线程的并发量;线程可以一直存在,直到显示关闭线程池之前;当有新的任务时且没有空闲的线程时,任务进入等待队列。
  • SingleThreadExecutor:只有一个线程。
  • ScheduledThreadPool:可安排在给定延迟后执行或定期地执行任务;线程池中有固定数量的线程,即便是没有任务也保留线程;
  • SingleThreadScheduledExecutor:类似ScheduledThreadPool,不过只有一个线程执行任务。
  • WorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时间内最多有几个线程在执行任务;默认为CPU个数。
12.4、 线程池的生命周期
  • NEW:处于新建的状态。
  • RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务。
  • SHUTDOWN:关闭状态,不再接受新的任务,但可以继续处理和执行阻塞队列中已存在的任务。
  • STOP:停止,不接受新的任务,也不处理阻塞队列中的任务,会中断正在处理任务的线程。
  • TIDYING:所有任务已经中止,且工作线程数量为0,最后变迁到这个状态的线程将要执行terminated()钩子方法,只会有一个线程执行这个方法
  • TERMINATED:中止状态,已经执行完terminated()钩子方法
12.5、阻塞队列
  • ArrayBlockingQueue:有界队列,先进先出
  • LinkedBlockingQueue:无界队列,先进先出
  • DelayQueue:无界队列,队列中的元素只有到期了才能被取走,有序队列,队首元素是最早到期的
  • PriorityBlockingQueue:无界队列,优先队列
  • SynchronousQueue:没有容量,每一个插入操作要阻塞等待一个删除操作,每一个删除操作要阻塞等待一个插入操作
12.6、execute()(submit())方法的执行逻辑
  • 如果当前有空闲的线程,则直接将任务交给空闲的线程执行
  • 如果当前运行线程数少于核心线程数,则创建新的线程来执行新的任务。
  • 如果执行的线程数大于或等于核心线程数,则将新的任务加入阻塞队列。
  • 如果阻塞队列已满,则创建新的线程来执行任务。
  • 如果线程个数已经超过了最大线程数,则会使用拒绝策略RejectedExecutionHandler来进行处理

13、面试题汇总

13.1、线程与进程的区别

进程是操作系统分配内存资源的基本单位,线程是执行和调度的基本单位。进程独享一块内存空间,一个进程中的线程共享一块内存空间。

13.2、sleep()与wait()的区别
  • sleep()不释放锁,wait()释放锁
  • sleep()是Thread类中的静态方法,wait()是Object类中的方法
  • wait()必须在同步代码块中使用,一般配合notify()使用
13.3、LongAdder(线程安全)
  • 使用了分段锁
13.4、如何提高锁的性能?
  • 减少锁的持有时间,只锁需要同步的代码块
  • 减小锁的粒度,分段锁,1)ConcurrentHashMap内部分成了若干个小的HashMap,称之为段,put()操作时,只锁将要操作的段。2)LongAdder,内部是一个数组,分段加锁,分段计算后再汇总。
  • 读写分离锁来替换独占锁,ReadWirteLock
  • 锁粗化,锁的请求、同步与释放也是需要资源的。
13.5、公平锁与非公平锁
  • 会存在获取锁的线程的等待队列,当有新的线程想要获取锁时,公平锁会先进入等待队列,非公平锁会直接尝试去获取锁。
  • ReentrantLock可设置公平与非公平,默认为非公平;synchronize是非公平锁。
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

it00zyq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值