• 多线程知识点总结


    一、volatile关键字

    volatile关键字的目的是告诉虚拟机:
    1.每次访问变量时,总是获取主内存的最新值;
    2.每次修改变量后,立刻回写到主内存。

    volatile关键字解决的是可见性问题:当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值。
    volatile关键字解决了共享变量在线程间的可见性问题。

    二、守护线程

    Java程序入口就是由JVM启动main线程,main线程又可以启动其他线程。当所有线程都运行结束时,JVM退出,进程结束。
    守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
    Thread t = new MyThread();
    t.setDaemon(true);
    t.start();
    在守护线程中,编写代码要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。

    三、同步
    多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized同步;
    同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
    注意加锁对象必须是同一个实例;
    对JVM定义的单个原子操作不需要同步。

    四、synchronized的使用

    * 我们来概括一下如何使用synchronized:
    * 找出修改共享变量的线程代码块;
    * 选择一个共享实例作为锁;
    * 使用synchronized(lockObject) { ... }

    五、可重入锁

    * Java的线程锁是可重入的锁。
    * 对同一个线程,能否在获取到锁以后继续获取同一个锁?
    * 答案是肯定的。JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。
    * 由于Java的线程锁是可重入锁,所以,获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。
    * 每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁。
    * Java的synchronized锁是可重入锁;
    * 死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待;
    * 避免死锁的方法是多线程获取锁的顺序要一致。

    六、wait和notify的使用

    * wait()方法的执行机制非常复杂。
    * 首先,它不是一个普通的Java方法,而是定义在Object类的一个native方法,也就是由JVM的C代码实现的。
    * 其次wait()方法调用时,会释放线程获得的锁,wait()方法返回后,线程又会重新试图获得锁。
    * 因此,只能在锁对象上调用wait()方法。

    * 必须在已获得的所对象上调用notify()或者notifyAll()方法。

    * 已唤醒的线程还需要重新获得锁后才能继续执行。

    多线程协调运行的原则就是:当条件不满足时,线程进入等待状态;当条件满足时,线程被唤醒,继续执行任务。

    七、使用ReentrantLock

    /**
    * java.util.concurrent.locks包提供的ReentrantLock用于替代synchronized加锁
    * 因为synchronized是Java语言层面提供的语法,所以我们不需要考虑异常,
    * 而ReentrantLock是Java代码实现的锁,我们就必须先获取锁,然后在finally中正确释放锁。
    * ReentrantLock是可重入锁,它和synchronized一样,一个线程可以多次获取同一个锁。
    * 下述代码在尝试获取锁的时候,最多等待5秒。如果5秒后仍未获取到锁,tryLock()返回false,程序就可以做一些额外处理,而不是无限等待下去。
    * 所以,使用ReentrantLock比直接使用synchronized更安全,线程在tryLock()失败的时候不会导致死锁。
    * 必须先获取到锁,再进入try {...}代码块,最后使用finally保证释放锁;
    * 可以使用tryLock()尝试获取锁
    */
    class TestReentrantLock{
    private final Lock lock = new ReentrantLock();
    private int count;

    public void add(int n){
    try {
    if(lock.tryLock(5, TimeUnit.SECONDS)){//5秒内尝试去获取锁
    count += n;
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally{
    lock.unlock();//释放锁
    }
    }
    }

    八、使用Condition

    /**
    * ReentrantLock使用Condition对象来实现wait和notify的功能
    * 使用Condition时,引用的Condition对象必须从Lock实例的newCondition()返回,这样才能获得一个绑定了Lock实例的Condition实例
    * Condition提供的await()、signal()、signalAll()原理和synchronized锁对象的wait()、notify()、notifyAll()是一致的,并且其行为也是一样的
    * await()会释放当前锁,进入等待状态
    * signal()会唤醒某个等待线程;
    * signalAll()会唤醒所有等待线程;
    * 唤醒线程从await()返回后需要重新获得锁。
    */
    class TestCondition{
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private Queue<String> queue = new LinkedList<>();

    public void addTask(String task){
    lock.lock();
    try {
    queue.add(task);
    condition.signalAll();
    } finally {
    lock.unlock();
    }
    }

    public String getTask() {
    lock.lock();
    try {
    while (queue.isEmpty()) {
    try {
    condition.await();
    /*if(condition.await(1, TimeUnit.SECONDS)){
    //被其他线程唤醒
    }else{
    //指定时间内没有被其他线程唤醒
    }*/
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    return queue.remove();
    } finally {
    lock.unlock();
    }
    }
    }

    九、使用ReadWriteLock

    /**
    * 读写锁接口:ReadWriteLock,它的具体实现类为:ReentrantReadWriteLock
    * ReentrantReadWriteLock--是可重入的读写锁,允许多个读线程获得ReadLock,但只允许一个写线程获得WriteLock
    * ReadWriteLock只允许一个线程写入(其他线程既不能写入也不能读取)
    * ReadWriteLock允许多个线程在没有写入时同时读取(提高性能)
    * ReadWriteLock适合读多写少的场景
    */
    class TestReadWriteLock{

    private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
    private Lock rlock = rwlock.readLock();
    private Lock wlock = rwlock.writeLock();

    private int[] counts = new int[10];

    public void increase(int index){
    wlock.lock();//加写锁
    try {
    counts[index] += 1;
    } finally {
    wlock.unlock();//释放写锁
    }

    }

    public int[] get(){
    rlock.lock();//加读锁
    try {
    return Arrays.copyOf(counts, counts.length);
    } finally {
    rlock.unlock();//释放读锁
    }

    }
    }

    十、使用StampedLock

    /**
    * StampedLock和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。
    * 乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。
    * 悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。
    * 显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
    * StampedLock提供了乐观读锁,可取代ReadWriteLock以进一步提升并发性能;
    * StampedLock是不可重入锁,不能在一个线程中反复获取同一个锁
    */

    class TestStampedLock{
    private final StampedLock stampedLock = new StampedLock();
    private Double x;
    private Double y;

    public void move(Double x1,Double y1){
    Long stamp = stampedLock.writeLock();// 获取写锁
    try {
    x += x1;
    y += y1;
    } finally {
    stampedLock.unlockWrite(stamp);//释放写锁
    }
    }

    public Double distanceFromOrigin(){
    //获取一个乐观读锁
    long stamp = stampedLock.tryOptimisticRead();//有竞争返回0

    Double currentX = x;
    Double currentY = y;
    //如果在发行给定的戳记时未独占获取锁,则返回true。
    //如果stamp为零,则始终返回false。
    //如果戳记表示正确持有的锁,则始终返回true。
    if(!stampedLock.validate(stamp)){
    stamp = stampedLock.readLock();//获取悲观读锁
    try {
    currentX = x;
    currentY = y;
    } finally {
    stampedLock.unlockRead(stamp);//释放悲观读锁
    }
    }
    //返回正确舍入的双精度值的正平方根
    return Math.sqrt(currentX*currentX + currentY*currentY);
    }

    }

    十一、使用线程池

    /**
    * JDK提供了ExecutorService实现了线程池功能:
    * 线程池内部维护一组线程,可以高效执行大量小任务;
    * Executors提供了静态方法创建不同类型的ExecutorService;
    * 必须调用shutdown()关闭ExecutorService;
    * ScheduledThreadPool可以定期调度多个任务。
    */
    class TestThreadPool{
    public static void main(String[] args) {
    Runnable task1 = new Runnable() {

    @Override
    public void run() {
    System.out.println("任务1");

    }
    };

    Runnable stask1 = new Runnable() {

    @Override
    public void run() {
    System.out.println("定时输出:任务1");

    }
    };

    ExecutorService executor1 = Executors.newFixedThreadPool(5);//定长线程池
    executor1.submit(task1);
    executor1.shutdown();

    ExecutorService executor2 = Executors.newSingleThreadExecutor();//单线程池
    executor2.submit(task1);
    executor2.shutdown();

    ExecutorService executor3 = Executors.newCachedThreadPool();//单线程池
    executor3.submit(task1);
    executor3.shutdown();

    //还有一种任务,需要定期反复执行,例如,每秒刷新证券价格。这种任务本身固定,需要反复执行的,可以使用ScheduledThreadPool。放入ScheduledThreadPool的任务可以定期反复执行。
    ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
    // 2秒后开始执行定时任务,每3秒执行一次:
    ses.scheduleAtFixedRate(stask1, 2, 3, TimeUnit.SECONDS);




    //创建指定动态范围的线程池
    int corePoolSize = 5;//核心线程池大小
    int maximumPoolSize = 10;//最大线程池大小
    long keepAliveTime = 60L;//线程池中超过corePoolSize数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间
    TimeUnit unit = TimeUnit.SECONDS;//keepAliveTime时间单位
    BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(10);//阻塞任务队列
    ExecutorService es = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);//线程池的大小限制在5~10个之间动态调整
    ExecutorService es0 = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());//动态创建

    /**
    * 1、构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,不会由RejectedExecutionHandler处理
    */
    ExecutorService es1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    /**
    * 2、构造一个缓冲功能的线程池,配置corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,keepAliveTime=60s,以及一个无容量的阻塞队列 SynchronousQueue,因此任务提交之后,将会创建新的线程执行;线程空闲超过60s将会销毁
    */
    ExecutorService es2 = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    /**
    * 3、构造一个只支持一个线程的线程池,配置corePoolSize=maximumPoolSize=1,无界阻塞队列LinkedBlockingQueue;保证任务由一个线程串行执行
    */
    ExecutorService es3 = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

    }
    }

    十二、使用ThreadLocal

    在一个线程中,横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context)。
    Java标准库提供了一个特殊的ThreadLocal,它可以在一个线程中传递同一个对象。
    ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰。

    ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的;
    ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递);

    特别注意ThreadLocal一定要在finally中清除:因为当前线程执行完相关代码后,很可能会被重新放入线程池中,如果ThreadLocal没有被清除,该线程执行其他代码时,会把上一次的状态带进去。使用ThreadLocal要用try ... finally结构,并在finally中清除。

  • 相关阅读:
    Android10_内容提供者_内容观察者_标题栏
    Android08_广播接受者_服务
    Android09_远程服务_系统服务_aidl
    Android07_多界面_Activity生命周期
    Android06_getpost提交_文件上传_多线程下载
    Android_handler_网络请求_img框架
    Android开发04
    java 正则表达式验证邮箱
    postman 基本用法
    java 微信申请退款的开发
  • 原文地址:https://www.cnblogs.com/wueryuan/p/12050226.html
Copyright © 2020-2023  润新知