线程学习个人总结

本文详细探讨了多线程的实现方式,包括Runnable接口和Thread类的使用,synchronized关键字的作用,ThreadLocal的工作原理,线程间的通讯机制,Lock接口及ReentrantLock的运用,CyclicBarrier的特性和优势,以及线程池的参数配置和执行流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程学习个人总结

1. 实现多线程的方式

  • 实现Runnable接口,重写run方法
  • 继承Thread类,重写run方法

2. synchronized关键字

  • synchronized 修饰非静态方法时,取得是对象锁,因此不同线程访问,获取到的是不同的锁,数据互不影响。如需共享资源,需要定义该方法为静态方法。
  • 当一个线程运行时,已进入一个被synchronized修饰的方法中时,调用另一个被sychoronized修饰的方法,叫做 锁重入
  • 被修饰方法出现异常时,该线程锁持有的锁会自动释放
  • 使用同步方法块方式(sychoronized(){})可以监视任意对象
  • 懒汉式双重锁模式
  • Java运行时,JVM会进行指令重排,提高程序运行效率,在单线程程序运行的时候,尽可能提高并行度,但多线程时,指令重排可能会导致 线程间变更通知未及时传递,对象未加载完成已传递、调用导致系统报错。该问题可通过在共享资源声明中加入volatile解决。

3. ThreadLocal

总结:

(1)ThreadLocal只是操作Thread中的ThreadLocalMap对象的集合;

(2)ThreadLocalMap变量属于线程的内部属性,不同的线程拥有完全不同的ThreadLocalMap变量;

(3)线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的;

(4)使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key来存储value值;

(5) ThreadLocal模式至少从两个方面完成了数据访问隔离,即纵向隔离(线程与线程之间的ThreadLocalMap不同)和横向隔离(不同的ThreadLocal实例之间的互相隔离);

(6)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中;

(7)线程死亡时,线程局部变量会自动回收内存;

(8)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量,key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;

(9)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收;

个人总结:

  • ThreadLocal在添加的时候,实际上是将当前ThreadLocal对象作为Key,将数据作为Value存入一个Entry(ThreadLocal.ThreadLocalMap的内部类,Key、value结构)
  • 多个线程间的ThreadLocal互不影响(纵向隔离),单个线程间不同的ThreadLocal互不影响(横向隔离)
  • ThreadLocal.set()时,通过自己实现的算法(key.threadLocalHashCode & (len - 1))计算出存储位置

4. 线程通讯

  1. wait()
    • 线程调用wait方法后,立即进入阻塞状态,代码停止在wait()处
    • wait()的调用,必须在同步代码块中才能使用
    • wait()被唤醒后,重新竞争锁,竞争到锁后才会执行wait()后面的代码
  2. notify()
    • notify() 必须在同步方法或同步方法块中执行
    • notify()方法是执行完方法后才会释放锁,被notify()唤醒的线程也是在执行后才竞争
    • 如果有多个线程在等待阻塞状态,系统会随机挑选一个唤醒、竞争资源
    • notifyAll()通知所以后等待线程开始竞争

5. Lock

5.1. 基本使用
Lock lock = new ReentrantLock();
lock.lock(); // 使用ReentrantLock加锁
…… 逻辑代码
lock.unlock(); // 释放锁
5.2 使用Lock对象实现线程通信
public class LockConditionDemo {

    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {

        LockConditionDemo demo = new LockConditionDemo();

        new Thread(() -> demo.await(demo.conditionA), "thread1_conditionA").start();
        new Thread(() -> demo.await(demo.conditionB), "thread2_conditionB").start();
        new Thread(() -> demo.signal(demo.conditionA), "thread3_conditionA").start();
        System.out.println("稍等5秒再通知其他的线程!");
        Thread.sleep(5000);
        new Thread(() -> demo.signal(demo.conditionB), "thread4_conditionB").start();

    }

    private void await(Condition condition) {
        try {
            lock.lock();
            System.out.println("开始等待await! ThreadName:" + Thread.currentThread().getName());
            condition.await();
            System.out.println("等待await结束! ThreadName:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private void signal(Condition condition) {
        lock.lock();
        System.out.println("发送通知signal! ThreadName:" + Thread.currentThread().getName());
        condition.signal();
        lock.unlock();
    }
}
5.3 ReentrantReadWriteLock

5.3.1 特性

(1)读读共享;

(2)写写互斥;

(3)读写互斥;

(4)写读互斥;

5.3.2 代码演示

public class ReentrantReadWriteLockDemo {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

        new Thread(() -> demo.read(), "ThreadA").start();
        Thread.sleep(1000);
        new Thread(() -> demo.write(), "ThreadB").start();
    }

    private void read() {
        try {
            try {
                lock.readLock().lock();
                System.out.println("获得读锁" + Thread.currentThread().getName()
                        + " 时间:" + System.currentTimeMillis());
                Thread.sleep(3000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void write() {
        try {
            try {
                lock.writeLock().lock();
                System.out.println("获得写锁" + Thread.currentThread().getName()
                        + " 时间:" + System.currentTimeMillis());
                Thread.sleep(3000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

6. CyclicBarrier(循环屏障)

  • 构造方法参数(int 需要多少线程等待,Runnable实现类(线程数满足后执行的代码))
  • CyclicBarrier callMasterBarrier = new CyclicBarrier(num,() -> {})
  • 每个线程运行完逻辑后加上以下代码,表示该线程已到屏障等待,callMasterBarrier.await();
  • 相较CountDownLatch的优势:
    • CountDownLatch只能使用一次,而CyclicBarrier可以使用reset()方法重置int
    • CountDownLatch会阻塞主线程,而CyclicBarrier只会阻塞子线程
    • CyclicBarrier的Api相对灵活(待自学)

7. 线程池

7.1. ThreadPoolExecutor 参数说明
  • acc : 获取调用上下文

  • corePoolSize: 核心线程数量,可以类比正式员工数量,常驻线程数量。

  • maximumPoolSize: 最大的线程数量,公司最多雇佣员工数量。常驻+临时线程数量。

  • workQueue:多余任务等待队列,再多的人都处理不过来了,需要等着,在这个地方等。

  • keepAliveTime:非核心线程空闲时间,就是外包人员等了多久,如果还没有活干,解雇了。

  • threadFactory: 创建线程的工厂,在这个地方可以统一处理创建的线程的属性。每个公司对员工的要求不一样,恩,在这里设置员工的属性。

  • handler:线程池拒绝策略,什么意思呢?就是当任务实在是太多,人也不够,需求池也排满了,还有任务咋办?默认是不处理,抛出异常告诉任务提交者,我这忙不过来了。

  • 创建实例:
    ExecutorService executorService = new ThreadPoolExecutor(5, 5,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(10),
    ® -> new Thread(r, r.getClass().getName()),
    new ThreadPoolExecutor.AbortPolicy());

    		for (int i = 0; i < 5; i++) {
                int index = i;
                executorService.submit(() -> System.out.println(index););
            }
            executorService.shutdown();
    
    // 注意!!!submit在报错时不会输出报错信息,有两种解决方案:
    // 1. 改用execute方法,但无返回值
    // 2. 获取submit的返回值.var = Future对象,在调用Future对象的get()方法获取返回值
    
7.2. Execute源码分析:
  • 比较线程池当前的数量和创建时设置的核心线程数(见上文参数说明),如果未达到设定值,会创建一个线程,并以传进来的Runable线程对象作为第一个执行任务执行;

  • 如果线程正在运行,并且任务队列有能力接收任务,则插入线程,并重新监测线程池是否在运行,不在运行的话,将删除任务、执行拒绝策略;

  • 如果添加临时线程失败,则执行拒绝策略

7.3. addWorker源码分析(循环执行)

——参数列表:1. Runnable对象 2. 是否核心线程

  • 判断当前线程池状态,大等于SHUTDOWN状态,则不处理任务,返回False

  • 通过传参判断添加核心还是临时线程,添加

  • 判断执行到此处线程的状态,如线程状态与之前不一直,则跳过当前循环

  • 用传递过来的Runable,作为参数创建Worker对象,获取线程、锁对象,通过ReentrantLock保证接下来将线程添加至线程池的过程的线程安全。

  • 添加线程(本质是添加到HashSet)、关闭同步锁、执行线程

7.4. worker线程处理队列任务
  • 如果当前传递进来的worker中第一次执行任务对象或者从队列中获取任务队伍不为空,利用Worker将执行过程加锁同步

  • 执行前置钩子函数(允许用户继承线程池,重写钩子函数)

  • 执行任务

  • 执行后置钩子函数(允许用户继承线程池,重写钩子函数)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值