栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程(一):基础知识

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程(一):基础知识

多线程基础笔记
  • 学习视频地址:https://www.bilibili.com/video/BV1Z54y1j7JT?p=3&share_source=copy_web
一、创建线程的3种方式
  • 1.继承 Thread 类
  • 2.实现Runnable接口
  • 3.实现 Callable 接口
二、获取线程相关信息 1. 获取当前正在执行的线程
  • Thread 类的静态方法: currentThread( )
  • 底层 toString 打印内容
2. 获取和设置线程的名称
  • 获取名称—— getName
  • 设置名称——setName

3. 获取和设置线程优先级

线程优先级范围:1~10

  • 设置: setPriority
  • 获取:getPriority
三、操作线程的基本方法 1. 使当前正在执行的线程进入休眠状态
  • Thread 的静态方法: sleep(long millis 毫秒数)
2. run 方法 和 start 方法的区别
  1. 位置

  2. 类型

  3. 作用

  4. 线程数量

  5. 调用次数:方法调用多次,是否会出现新的问题

3. 让线程优雅地停止
  • Thread 类的 interrupt 方法
  • 1.用于停止正在运行的线程
  • 2.如果停止休眠中的线程,则会抛出线程中断异常

使用

问题:线程没有停下来,程序也并未结束

  • 该方法只是将线程标记为中断状态,实际并未中断
  • 需要在 run 方法中通过 isInterrupted 方法主动去判断线程是否被中断(是否有中断标记)

4. 使当前线程放弃执行权

Thread.yield( ) 方法

  • 用于一些需要一定条件才执行的任务
  • 未满足条件时,调度到了该线程就放弃执行权

1、编写打印任务和赋值任务

  • 打印线程

  • 赋值线程

2、 主线程

3、比较代码段的不同

  • 空循环较为浪费资源
  • yield 可以主动放弃执行权,而非被动地被调度
5. 等待另一条线程死亡( join 方法)

Thread.join( ) :等待调用该方法的线程死亡

用于使多个线程按顺序执行

例子

  • 线程1
  • 线程2
  • 线程3
  • 执行
6. 守护线程(Daemon)

使 A 线程随着 B 线程的结束而结束——设置 A 线程 为 B 线程的守护进程

1、设置后台线程

测试

2、判断线程是否为后台线程

3、主线程

  • 由执行结果可知,守护线程确实随着主线程的结束而结束了,并未受死循环影响。
7. 如何得知线程是否执行完

通过判断线程是否存活

测试

8. 线程组

1、获取线程组

  • thread.getThreadGroup( )

2、创建线程组

3、设置线程组

4、线程组常用方法

四、线程同步:synchronized 1. synchronized 关键字
  • 可用来给对象和方法或者代码块加锁
  • 被synchronized 修饰的代码,,同一时刻最多只有一个线程执行这个方法/这段代码。
同步代码块

同步方法

静态同步方法

测试 测试1:无同步操作

出现问题:有重复票

修改建议:

  • 需要添加同步操作
  • 但不要将整个 while 循环放入同步代码块中,否则会出现一个线程把票买完的情况
测试2:同步代码块_无双检

问题:出现错票

问题原因:只剩 1 个资源,但有多个线程同时判断到有资源可用

测试3:同步代码块_有双检

修改:进入同步代码块后,再进行一次条件检测

记住问题
  • 重复票:无同步操作
  • 错票:有同步操作,但没有同步操作内外双重判断
2. 同步锁
  • 同步锁是为了保证每个线程都能正常执行原子不可更改操作,【同步监听对象/同步锁/同步监听器/互斥锁】的一个标记锁。
  • 同一时刻,最多只有一个线程拿到同步锁,然后执行同步代码。
按锁的类型分类
  • 对象类型
  • 类类型
同步类型对应的锁类型 3. 多个线程争夺同一把锁和不同锁的场景

结果说明:当线程 sleep 时,是不会释放锁的

修改测试:Task.class → Main.class

总结

  • 线程的 sleep 方法,不会释放锁
  • 争夺同一把锁,线程会阻塞
4. 死锁 1、什么是死锁
  • 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
  • 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
  • 两个或两个以上的线程争夺彼此的锁,造成阻塞,程序永远处于阻塞状态。
2、死锁产生的条件
  • 资源互斥:一个资源只能同时被一个线程占有
  • 请求与保存:线程持有其资源的情况下,能申请获取另一个资源
  • 不剥夺:线程任务未完成时,不主动释放自己的资源
  • 循环等待:两个以上线程,构成环
3、死锁示例
  • 实现思路

    • 两个或两个以上的线程
    • 两个或两个以上的锁(资源)
    • 两个或两个以上的线程去持有不同的锁
    • 持有着不同锁的线程(即在锁住的代码内容中)去争夺对方的锁
  • 然后程序在输出 A、B之后,进入死锁。

五、线程间通讯 1. 等待(wait)、唤醒机制(notify)
  • 首先需要有一个锁对象:lock
  • 然后某个线程用 synchronized ( lock ) 获得该锁后,可调用 lock.wait( ) 方法,此时发生
    • 让出这个锁资源:lock
    • 同时线程开始无限期等待(如果没设置等待时间)
    • 等待 lock.notify() (如果只有1条与该锁相关的线程,则被唤醒)或 lock.notifyAll 被唤醒继续执行
1、等待 wait 2、唤醒单个线程 notify
  • 如果有多个线程执行 task ,则 notify 只唤醒1个线程
3、唤醒所有线程 notifyAll
4、总结

2. wait 与 sleep 的区别
  1. 位置
  2. 是否需要当前线程拥有锁
  3. 是否支持手动唤醒?
  4. 是否支持自动唤醒?
  5. 是否支持中断?
  6. 是否释放锁?
  7. 线程状态

位置

  • sleep:是 Thread 类的静态方法
  • wait:是 Object 类的方法

总结

thread.getState( ) :获取线程状态

3. 线程间通讯(wait & notify 应用) 1、数据类
public class Data {

    
    private String message;

    
    public void setMessage(String message){
        // 设置数据信息
        this.message = message;
    }

    
    public String getMessage() {
        return message;
    }
}
2、生产者类
package lab;


public class Producer implements Runnable {

    
    private Data data;

    
    private int count = 0;

    
    public Producer(Data data) {
        // 设置数据
        this.data = data;
    }

    
    @Override
    public void run() {
        // 无限循环
        while (true) {

            try {
                // 让当前线程睡1秒钟
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 同步对象为data的同步代码块
            synchronized (data) {

                // 当数据信息不为空时,即还有数据信息未消费
                while (data.getMessage() != null) {
                    try {
                        // 使当前生产者线程等待
                        data.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 数据信息
                String message = "你好!" + count++;

                // 设置数据信息
                data.setMessage(message);

                // 唤醒正在此对象监视器上等待的所有线程
                data.notifyAll();
            }
        }
    }
}
3、消费者类
package lab;


public class Consumer implements Runnable {

    
    private Data data;

    
    public Consumer(Data data) {
        // 设置数据
        this.data = data;
    }

    
    @Override
    public void run() {
        // 无限循环
        while (true){
            // 同步对象为data的同步代码块
            synchronized (data) {
                // 当数据信息为空时
                while (data.getMessage() == null) {
                    try {
                        // 等待
                        data.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 消费数据信息
                System.out.println("数据信息:" + data.getMessage());

                // 将数据对象里面的数据信息设置为null
                data.setMessage(null);

                // 唤醒正在此对象监视器上等待的所有线程
                data.notifyAll();
            }
        }
    }
}
4、主线程测试
package main;

import lab.Consumer;
import lab.Data;
import lab.Producer;

public class Main {
    
    public static void main(String[] args) {
        // 数据
        Data data = new Data();

        // 创建生产者任务和消费者任务
        Producer producer = new Producer(data);
        Consumer consumer = new Consumer(data);

        // 创建生产者线程和消费者线程
        Thread producerThread1 = new Thread(producer);
        Thread consumerThread1 = new Thread(consumer);

        // 启动线程
        producerThread1.start();
        consumerThread1.start();
    }
}
六、线程同步:Lock 锁

具备和synchronized一样的作用,可以实现线程同步,但比synchronized 更强大。

Lock接口定义了 6 个方法

常用实现类

使用

  • package main;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Main {
    
        
        public static void main(String[] args) {
            // 创建显式锁对象
            Lock lock = new ReentrantLock();
    
            // 通过匿名内部类方式实现Runnable接口
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    // 获取锁
                    lock.lock();
    
                    try {
                        // 使当前线程睡1秒钟
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    // 输出语句
                    System.out.println(Thread.currentThread().getName() + " --- run");
                }
            };
    
            // 创建线程
            Thread thread1 = new Thread(runnable);
            Thread thread2 = new Thread(runnable);
            Thread thread3 = new Thread(runnable);
    
            // 启动线程
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    

总结

七、锁比较 1. 非阻塞式获取锁 1、阻塞式获取锁
  • 发现锁未释放,则一直等待

2、非阻塞式获取锁
  • 发现锁未释放,则先去做其他事

使用格式

测试

package main;

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

public class Task implements Runnable {

    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 非阻塞式获取锁
        try {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    // 使当前线程休眠1秒钟
                    Thread.sleep(1000);
                    // 输出当前线程名称
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            } else {
                System.out.println("锁被占用,执行其他任务。");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package main;

public class Main {

    public static void main(String[] args) {
        // 创建任务
        Task task = new Task();
        // 创建线程
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        // 启动线程
        thread1.start();
        thread2.start();
    }
}

3、尝试定时获取锁 2. 中断等待锁的线程

可中断测试

拿到锁持有 3 秒钟

thread0持有锁时,还未释放,主线程此时主动中断 thread1

发生异常

  • 注意:该异常是 thread1 获取到锁之后抛出的
  • 即 3 s 后,线程 thread1 已经拿到了锁,但发现自己已经被中断了,所以抛出异常

lockInterruptibly
  • 当一个线程获取了锁之后,是不会被interrupt()方法中断的。
  • 因为调用 interrupt( ) 方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程,而进入阻塞状态前是需要判断获取锁的
  • 因此当通过 lockInterruptibly( ) 方法获取某个锁时,如果不能获取到,则可通过抛出异常来响应中断。
  • 对比 lock 方法,该方法操作性更强
3. Lock 锁的等待唤醒机制
  • 通过 Condition 实现的

  • 每次返回的都是新的 Condition 实例

1、Condition类的方法

方法对应

常用方法

2、测试
package main;

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

public class Main {
    public static void main(String[] args) {
        // 创建显式锁
        Lock lock = new ReentrantLock();
        // 获取Condition对象
        Condition condition = lock.newCondition();
        
        // 创建线程
        Thread thread = new Thread(){
            @Override
            public void run() {
                try {
                    // 获取锁
                    lock.lock();
                    // 输出语句
                    System.out.println(Thread.currentThread().getName() + " --- 等待");
                    // await()方法造成当前线程在等待,直到它被唤醒,通常由被通知或中断。
                    condition.await();
                    // 输出语句
                    System.out.println(Thread.currentThread().getName() + " --- 被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };

        // 启动线程
        thread.start();
    }
}

测试唤醒

  • 主线程添加代码
  • 注:需要主线程先获取到 Lock 显式锁

测试唤醒全部

  • 主线程添加代码
3、生产者与消费者(Condition应用)

1、数据类

  • 定义两个不同的 Condition

  • package lab;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    
    public class Data {
    
        
        private String message;
    
        
        private int count = 0;
    
        
        private boolean empty = true;
    
        
        private Lock lock = new ReentrantLock(true);
    
        
        private Condition setCondition = lock.newCondition();
    
        
        private Condition getCondition = lock.newCondition();
    
        
        public void setMessage(String message) throws InterruptedException {
            // 获取锁
            lock.lock();
            try {
                // 当数据信息不为空时,即还有数据信息未消费
                while (!empty) {
                    // 使当前生产者线程等待
                    setCondition.await();
                }
                // 设置数据信息
                this.message = message + Thread.currentThread().getName() + " --- " + count++;
                // 数据信息没有被消费
                empty = false;
    
                try {
                    // 让当前线程睡1秒钟
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 唤醒正在此对象监视器上等待的单个线程
                getCondition.signal();
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        
        public String getMessage() throws InterruptedException {
            // 获取锁
            lock.lock();
            try {
                // 当数据信息为空时
                while (empty) {
                    // 使当前生产者线程等待
                    getCondition.await();
                }
                // 数据信息已经被消费
                empty = true;
                // 唤醒正在此对象监视器上等待的单个线程
                setCondition.signal();
            } finally {
                // 释放锁
                lock.unlock();
            }
            // 返回数据信息
            return message;
        }
    }
    

2、生产者

package lab;


public class Producer implements Runnable {

    
    private Data data;

    
    public Producer(Data data) {
        // 设置数据
        this.data = data;
    }

    
    @Override
    public void run() {
        try {
            // 无限循环
            while (true) {
                // 设置数据信息
                data.setMessage("你好!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、消费者

package lab;


public class Consumer implements Runnable {

    
    private Data data;

    
    public Consumer(Data data) {
        // 设置数据
        this.data = data;
    }

    
    @Override
    public void run() {
        try {
            // 无限循环
            while (true){
                // 消费数据信息
                System.out.println("数据信息:" + data.getMessage());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试

package main;

import lab.Consumer;
import lab.Data;
import lab.Producer;

public class Main {

    
    public static void main(String[] args) {
        // 数据
        Data data = new Data();

        // 创建生产者任务和消费者任务
        Producer producer = new Producer(data);
        Consumer consumer = new Consumer(data);

        // 创建生产者线程和消费者线程
        Thread producerThread1 = new Thread(producer);
        Thread consumerThread1 = new Thread(consumer);

        // 启动线程
        producerThread1.start();
        consumerThread1.start();
    }
}

利用await、signal、signalAll等待唤醒机制,让线程间友好协作完成任务,更高效地使用资源。

4. 可重入锁和不可重入锁
  • (不)可:(不)可以
  • 重入:重复进入同步作用域(使用同步锁)
  • 锁:同步锁

测试 synchronized 可重入锁

说明可重复使用持有的锁

同步方法上的同步锁,也可重复使用(毕竟锁住了整个类)

测试 lock 可重入锁

测试不可重入锁

自定义不可重入锁的思路

  • 实现 Lock 接口——着重实现 lock 和 unlock 方法即可
  • 绑定已获取锁的线程
  • 实现获取锁的方法
  • 实现释放锁的方法
package lab;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


public class NotReentrantLock implements Lock {

    
    private Thread thread;

    
    @Override
    public void lock() {
        // 获取当前线程
        Thread currentThread = Thread.currentThread();
        // 当当前线程为已经拿到锁的线程时
        while (currentThread == thread) {
            // 同步对象为this的同步代码块
            synchronized (this) {
                try {
                    // 使当前线程等待
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        // 记录当前线程
        thread = currentThread;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    
    @Override
    public void unlock() {
        // 获取当前线程
        Thread currentThread = Thread.currentThread();
        // 当当前线程不为绑定线程时
        while (currentThread != thread) {
            // 同步对象为this的同步代码块
            synchronized (this) {
                try {
                    // 使当前线程等待
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        // 将绑定线程置空
        thread = null;
        // 同步对象为this的同步代码块
        synchronized (this){
            // 唤醒等待该锁的所有线程
            notifyAll();
        }
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
5. 公平锁与非公平锁

线程获取锁的机会平等与否

  1. 非公平锁(synchronized)
  2. 非公平锁(ReentrantLock)——默认是非公平的 ( false )
  3. 公平锁(ReentrantLock)
  4. 判断锁是否公平(isFair)——只能判断 ReentrantLock 和 ReentranReadWriteLock
    • reentrantLock.isFair( )
    • reentranReadWriteLock.isFair()
6. 读(共享)锁与写(独占)锁 1、读锁

线程启动后,几乎可以同时拿到读锁

2、写锁
  • 三条线程依次输出(独占的,一个用完再给下一个)
3、读锁(共享锁)的意义
  • 能保证支持多个线程同时读取的时候,禁止修改
  • 能保证同一时刻只有一个线程在修改
7. 读写锁实战高并发容器

1、高并发容器

  • 键值对容器

  • 锁

    • 读锁——获取数据
    • 写锁——存储数据
  • 方法

    • 获取数据
    • 存储数据
    • 获取所有键
    • 清空容器
  • package lab;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.TreeMap;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    
    public class ConcurrentDictionary {
    
        
        private Map m = new TreeMap<>();
    
        
        private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);
    
        
        private ReentrantReadWriteLock.ReadLock r = rwl.readLock();
    
        
        private ReentrantReadWriteLock.WriteLock w = rwl.writeLock();
    
        
        public String put(String key, String value) {
            // 获取写锁
            w.lock();
            try {
                // 添加数据并将旧值返回给调用者
                return m.put(key, value);
            } finally {
                // 释放写锁
                w.unlock();
            }
        }
    
        
        public String get(String key) {
            // 获取读锁
            r.lock();
            try {
                // 返回键所对应的值
                return m.get(key);
            } finally {
                // 释放读锁
                r.unlock();
            }
        }
    
        
        public List allKeys() {
            // 获取读锁
            r.lock();
            try {
                // 返回容器中所有的key值
                return new ArrayList<>(m.keySet());
            } finally {
                // 释放读锁
                r.unlock();
            }
        }
    
        
        public void clear() {
            // 获取写锁
            w.lock();
            try {
                // 清空容器
                m.clear();
            } finally {
                // 释放写锁
                w.unlock();
            }
        }
    }
    

2、写任务

3、读任务

4、测试

8. synchronized 和 Lock 的区别 1、类型
  • synchronized 关键字
  • Lock 接口
2、格式

3、释放锁的方式

4、别名 5、连续释放锁的方式
  • 多个锁嵌套之后,释放的方式
6、(非)公平锁
  • synchronized 只有非公平锁

7、读写锁
  • ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
    //写锁
    Lock writeLock = readWriteLock.writeLock();
    //读锁
    Lock  readLock = readWriteLock.readLock();
    
8、总结

八、等待、唤醒工具类:LockSupport 1. LockSupport 引入

LockSupport

  • 功能

    • 使线程等待
    • 唤醒当前等待中的线程
  • 该工具类无法被实例化

1、方法分类
  • 按功能分类
  • 都是静态方法,可直接通过类名调用
2、等待 park ( ) 3、唤醒 unpark ( ) 2. LockSupport 实现互斥锁

思路:

  • 锁标志——记录锁是否已经被其他线程使用
  • 等待队列
  • 处理线程中断——有线程不想等了,需要处理

实现:

  • 先进先出互斥锁:FIFOMutex
    • 上锁方法的实现:lock( )
      • 互斥锁的实质:同一时刻只有1个线程能用该锁,所以需要一个等待队列辅助排队。
  • Task类
  • Main
九、读写锁扩展 1. 读写锁互斥情况 2. 读写锁升级版:StampedLock
  • 普通的读写锁,存在“线程饥饿”问题

  • 会导致写线程饥饿的情况:

    • 当线程 A 持有读锁读取数据时,线程 B 要获取写锁修改数据就只能到队列里排队。
    • 此时又来了线程 C 读取数据,那么线程 C 就可以获取到读锁,而要执行写操作线程 B 就要等线程 C 释放读锁。
    • 由于该场景下读操作的数量远远大于写操作的数量,此时可能会有很多线程来读取数据而获取到读锁,那么要获取写锁的线程 B 就只能一直等待下去,最终导致饥饿。
  • StampedLock是基于CLH锁原理实现的

    • CLH是一种基于排队思想实现的自旋锁,可以保证FIFO(先进先出)的服务顺序,所以会避免写线程饥饿问题
    • 其实就是其中实现了一个队列,每次不管是读锁也好写锁也好,未拿到锁就加入队列
    • 然后每次解锁后,队列头存储的线程节点获取锁,从而避免饥饿。
  • StampedLock 常用方法

3. StampedLock 的可重入性
  • 可重入性和普通的读写锁有区别
十、线程状态与线程周期

获取线程状态的方法

  • 线程状态
  • 线程周期
十一、ThreadLocal 1. ThreadLocal 线程本地变量

本质

  • Thread内部有一个ThreadLocalMap的成员变量,里面存放着和这个线程绑定的数据。
  • 虽然ThreadLocalMap叫map,但是并没有实现map接口,只是提供了几个和map接口一样的方法
  • ThreadLocal作为ThreadLocalMap的key
  • 所以说ThreadLocal本身并不存放数据,数据都在Thread对象中的ThreadLocalMap里
  • 所以同一个 ThreadLocal,在不同线程Thread的ThreadLocalMap中作为 key ,其 value 可以不同,相对独立。

ThreadLocal的定位是什么?

  • Thread 类的 ThreadLocalMap 属性的 key 值

  • key就是 ThreadLocal

  • ThreadLocal与Thread 的关系是什么?

    • ThreadLocal 与Thread是相互协作的关系。
    • Thread负责存储数据,ThreadLocal负责操作数据。

简单测试

  • 输出为 null
  • 修改

  • 删除—— remove 方法

2. 线程间如何共享 ThreadLocal

InheritableThreadLocal

  • 继承自 ThreadLocal,方法一样
  • 就当做是属性值一样来用
  • 并不是说,线程 B extends 自线程 A
  • 而是,【线程 B】 需要在【线程 A】 的 run 方法中创建
  • 然后即可共享线程 A 中的本地变量

测试

  • 线程B
  • 线程A
  • Main
3. 线程池之间如何共享 ThreadLocal

TransmitableThreadLocal

  • 继承自 InheritableThreadLocal
  • 该类非 JDK 自带的,而是 :https://github.com/alibaba/transmittable-thread-local

ThreadLocal 线程池测试

  • task

  • 提交到线程池的任务,执行时无法获取到 ThreadLocal

  • InheritableThreadLocal 也一样

  • 说明: ThreadLocal 和 InheritableThreadLocal 无法再线程池间共享

TransmitableThreadLocal 测试

发现仍然不一致;

需要按照规定使用:

  • 方法一:改变线程池任务提交方式

    • 借助 TtlRunnable 类,绑定任务 task
  • 方法二:改变线程池

    • 创建线程池:
      • ExecutorService executorService = Executors.newFixedThreadPool( 1 );
      • TtlExcutors.getTtlExecutorService( executorService );
    • 正常提交任务即可

总结

十二、多线程基础扩展 1. 内存可见性——volatile

专门解决内存可见性问题的关键字

  • 内存可见性

理解内存可见性

发现, thread 线程并未停下来。

原因:

volatile 解决该问题

修改代码:

实现内存可见性。

即使用 volatile 修饰的变量,会主动通知更新【对应线程的工作内存】

2. 原子性

一个或多个操作不可中断的,要么全部执行成功,要么全部执行失败。

对应的操作——原子操作——具备原子性的操作

i++ 是原子操作吗?

  • 不是

判断过程:让多个线程执行 i++ ,看最后的值是否为累加即可

发现,i++不是原子操作,其本质步骤分为:

每一步都有可能被其他线程干扰

  • 1.从主内存读取变量i
  • 2.执行i++
  • 3.将变量i写入主内存

使用 volatile 无法解决原子性问题

添加 同步代码 可以解决

使用原子类解决

具有原子性的类

总结

3. CAS (比较并交换)技术

比较并交换技术(CAS技术):CAS:Compare And Swap

CAS操作包含三个操作数:内存值、预期原值、新值

如果内存位置的值与预期原值相匹配,那么将内存值更新为新值;否则什么也不做

  • 和谁比较
  • 和谁交换

CAS操作包含三个值

  • 内存中
  • 预期原值
  • 新值

测试线程干扰

  • 线程干扰
  • 当线程未执行到变量值修改的语句时,对应的变量值先被其他线程修改

CAS 方法抗干扰

CAS的应用

  • 原子类里大多都是使用 CAS 方法来保证

总结

4. 原子类
  • 分类

5. ABA 问题

CAS算法存在的ABA问题

  • 假如一个值原来是A,现在变成了B,
  • 后来又变成了A,
  • 那么CAS检查时会发现它的值没有发生变化,
  • 但是实际上却变化了

解决方法

  • 打标记(version等)
  • 原子类


修改

总结

  • ABA问题:假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了
  • AtomicMarkableReference是一个带修改标记的引用类型原子类
  • AtomicStampedReference是一个带版本号的引用类型原子类

总结

3. CAS (比较并交换)技术

比较并交换技术(CAS技术):CAS:Compare And Swap

CAS操作包含三个操作数:内存值、预期原值、新值

如果内存位置的值与预期原值相匹配,那么将内存值更新为新值;否则什么也不做

  • 和谁比较
  • 和谁交换

CAS操作包含三个值

  • 内存中
  • 预期原值
  • 新值
  • [外链图片转存中…(img-LmiX1huB-1657340436259)]

测试线程干扰

  • 线程干扰
  • 当线程未执行到变量值修改的语句时,对应的变量值先被其他线程修改

CAS 方法抗干扰

[外链图片转存中…(img-cp5nN0uD-1657340436259)]

[外链图片转存中…(img-YTU9xp6Y-1657340436260)]

CAS的应用

  • 原子类里大多都是使用 CAS 方法来保证

总结

4. 原子类
  • 分类

    • [外链图片转存中…(img-7fJfXpFz-1657340436261)]
  • [外链图片转存中…(img-O03n1ML3-1657340436261)]

5. ABA 问题

CAS算法存在的ABA问题

  • 假如一个值原来是A,现在变成了B,
  • 后来又变成了A,
  • 那么CAS检查时会发现它的值没有发生变化,
  • 但是实际上却变化了

解决方法

  • 打标记(version等)
  • 原子类

[外链图片转存中…(img-4aG1DCZM-1657340436262)]


[外链图片转存中…(img-VG9A8qWg-1657340436262)]

修改

[外链图片转存中…(img-JWrtHnOI-1657340436263)]

[外链图片转存中…(img-xRvNxT75-1657340436264)]

[外链图片转存中…(img-6DyzF1A5-1657340436264)]

总结

  • ABA问题:假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了
  • AtomicMarkableReference是一个带修改标记的引用类型原子类
  • AtomicStampedReference是一个带版本号的引用类型原子类
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1007579.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号