Java并发(Java复习二)

线程生命周期

主要分为5个部分,新建、就绪、运行、阻塞和结束

  • 新建后通过start方法进入就绪阶段
  • 就绪阶段通过CPU抢到执行权进入运行阶段
  • 运行阶段遇到IO阻塞,线程等待方法,wait方法或者互斥锁进入阻塞阶段
  • 阻塞阶段通过相应的解除阻塞方法,如等待时间结束,被唤醒方法和抢到锁进入就绪阶段
  • 运行阶段进入结束阶段,通过运行结束或stop方法

在这里插入图片描述

线程的常用实现方式

  • 通过继承Thread实现
  • 通过实现Runable接口实现
  • 通过实现Callable接口实现,可以返回返回值
    建议:使用接口方式实现线程,原因是1.继承整个Thread开销过大;2.java不可多重继承,而可以实现多个接口

线程的阻塞方式和区别及应用

  • 无限期等待(Waiting):没有CPU执行时间,需要其他线程显示地唤醒。
    • 没有设置Timeout参数的Object.wait()
    • 没有设置Timeout参数的Thread.join()
    • LockSupport.park()
    • ReentrantLock
  • 期限等待(Timed Waiting):没有CPU执行时间,在到达时间后自动地唤醒。
    • 设置Timeout参数的Object.wait()
    • 设置Timeout参数的Thread.join()
    • Thread.sleep()
    • LockSupport.parkNanaol()
    • LockSupport.parckUntil
  • 阻塞(Blocked):线程被阻塞住了,需要获取锁才能执行,相对于等待状态不是等待一定时间
    • synchronized

sleep、yied、wait、join的区别,以及为何sleep和yied是线程静态方法,wait是Object的方法

  • sleep:释放CPU资源,与锁无关,阻塞一定时长
  • yied:释放CPU资源,与锁无关,不阻塞,调用后根据线程优先级争抢cpu
  • wait:释放CPU资源,释放锁资源,和synchronizer锁相关,且必须在synchronizer代码块中执行,调用后会对该对象的锁进行释放(notify和notifyAll,不会释放锁,需要等到synchronizer代码块执行完,才释放锁)
  • join:释放CPU资源,与锁无关,阻塞当前线程,直至调用线程执行完成或设置的等待时间完成

sleep、yield、wait、join的区别

AQS的实现方式

AbstractQueuedSynchronizer是一个模板的抽象类,开发人员只需要实现其中的什么条件下获取同步状态,什么条件下释放同步状态的方法即可,状态值的获取设置和比较也是由抽象类实现,且通过自旋保证值的线程安全。实现后,具体的同步状态如何获取以及如何释放,则由抽象类实现,开发人员只需要调用即可。

AQS的原理

AQS获取同步:首先会通过tryAcquire方法判断是否可以获取同步,可以则直接执行,若不可以,将构造节点加入到一个双休的CLH队列中,加入后,若处于第一个节点,则会进行自旋获取同步状态,若处于后续节点,则会被阻塞在队列中(通过LockSupport.park实现,其实是等待态)
AQS释放同步:当获取了同步状态的节点释放时,会唤醒后继节点,此节点将加入到同步状态的争夺中
在这里插入图片描述

AQS相关实现

ConcurrentHashMap

  • ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。
  • 此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。

ConcurrentLinkedQueue

  • ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。
  • LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。
public class BlockingQueueTest {

    public static void main(String[] args) {
        BlockingQueue blockingQueue =new ArrayBlockingQueue(1000);
        new Thread(new BlockingQueueTest().new Consumer(blockingQueue)).start();
        new Thread(new BlockingQueueTest().new Producer(blockingQueue)).start();
    }

    class Consumer implements Runnable {
        private BlockingQueue<String> blockingQueue;

    public Consumer(BlockingQueue<String> blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            try {
                String take = blockingQueue.take();
                System.out.println(take);
                String take2 = blockingQueue.take();
                System.out.println(take2);
                String take4 = blockingQueue.take();
                System.out.println(take4);
                String take3 = blockingQueue.poll(10, TimeUnit.SECONDS);
                System.out.println(take3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class Producer implements Runnable{

        private BlockingQueue<String> blockingQueue;

        public Producer(BlockingQueue blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            try {
                this.blockingQueue.put("1111");
                Thread.sleep(1000);
                this.blockingQueue.put("2222");
                Thread.sleep(1000);
                this.blockingQueue.put("3333");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果

1111
2222
3333
null

CountDownLatch

  • CountDownLatch 以一个给定的数量初始化。countDown() 每被调用一次,这一数量就减一。通过调用 await() 方法之一,线程可以阻塞等待这一数量到达零。以下是一个简单示例。
  • Decrementer 三次调用 countDown() 之后,等待中的 Waiter 才会从 await() 调用中释放出来。
public class CountDownLatchTest {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);
        new Thread(new CountDownLatchTest().new General(countDownLatch)).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new CountDownLatchTest().new Soldier(countDownLatch),"3号").start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new CountDownLatchTest().new Soldier(countDownLatch),"1号").start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new CountDownLatchTest().new Soldier(countDownLatch),"2号").start();

    }
    class General implements Runnable {
        private CountDownLatch countDownLatch;

        public General(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                countDownLatch.await();
                System.out.println("将领发布命令!!!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class Soldier implements Runnable{
        private CountDownLatch countDownLatch;

        public Soldier(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {

            System.out.println(Thread.currentThread().getName()+"士兵准备完毕。 count:"+countDownLatch.getCount());
            countDownLatch.countDown();
        }
    }
}

输出结果

3号士兵准备完毕。 count:3
1号士兵准备完毕。 count:2
2号士兵准备完毕。 count:1
将领发布命令!!!

CyclicBarrier

  • java.util.concurrent.CyclicBarrier 类是一种同步机制,它能够对处理一些算法的线程实现同步。换句话讲,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情。
public class TestCyclicBarrier {
    public static void main(String[] args) {
        Runnable surfInternet = new Runnable() {
            @Override
            public void run() {
                System.out.println("满足条件,可以上网了!!!");
            }
        };
        Runnable codding = new Runnable() {
            @Override
            public void run() {
                System.out.println("满足条件,可以编程了!!!");
            }
        };

        CyclicBarrier surfInternetCyclicBarrier = new CyclicBarrier(2, surfInternet);
        CyclicBarrier coddingCyclicBarrier = new CyclicBarrier(2, codding);
//        CyclicBarrier surfInternetCyclicBarrier = new CyclicBarrier(2);
//        CyclicBarrier coddingCyclicBarrier = new CyclicBarrier(2);


        new Thread(new TestCyclicBarrier.SuerfInternetCondition(surfInternetCyclicBarrier, coddingCyclicBarrier)).start();
        new Thread(new TestCyclicBarrier.coddingCyclicCondition(surfInternetCyclicBarrier, coddingCyclicBarrier)).start();

    }

    static class SuerfInternetCondition implements Runnable {
        private CyclicBarrier surfInternetCyclicBarrier;
        private CyclicBarrier coddingCyclicBarrier;

        public SuerfInternetCondition(CyclicBarrier surfInternetCyclicBarrier, CyclicBarrier coddingCyclicBarrier) {
            this.surfInternetCyclicBarrier = surfInternetCyclicBarrier;
            this.coddingCyclicBarrier = coddingCyclicBarrier;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("买了电脑");
                surfInternetCyclicBarrier.await();
                Thread.sleep(2000);
                coddingCyclicBarrier.await();
                System.out.println("学了编程");


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

    static class coddingCyclicCondition implements Runnable {
        private CyclicBarrier surfInternetCyclicBarrier;
        private CyclicBarrier coddingCyclicBarrier;

        public coddingCyclicCondition(CyclicBarrier surfInternetCyclicBarrier, CyclicBarrier coddingCyclicBarrier) {
            this.surfInternetCyclicBarrier = surfInternetCyclicBarrier;
            this.coddingCyclicBarrier = coddingCyclicBarrier;
        }

        @Override
        public void run() {
            try {
            	System.out.println("连了网线");
                surfInternetCyclicBarrier.await();
                System.out.println("装了IDE");
                coddingCyclicBarrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果

连了网线
买了电脑
满足条件,可以上网了!!!
装了IDE
学了编程
满足条件,可以编程了!!!

Semaphore

  • java.util.concurrent.Semaphore 类是一个计数信号量。这就意味着它具备两个主要方法:
    • acquire()
    • release()
  • 计数信号量由一个指定数量的 “许可” 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N 是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。这里没啥奇特的地方。
public class TestSemaphore {

    public static void main(String[] args) {
        // 公平
        Semaphore semaphore = new Semaphore(3,true);
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"1").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"2").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"3").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"4").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"5").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"6").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"7").start();
        new Thread(new TestSemaphore().new SemaphoreRunable(semaphore),"8").start();
    }

    class SemaphoreRunable implements Runnable{
        private Semaphore semaphore;

        public SemaphoreRunable(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" 通过了");
                semaphore.release();
                semaphore.acquire();
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" 返回了");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

输出结果

2 通过了
1 通过了
3 通过了
6 通过了
5 通过了
4 通过了
7 通过了
2 返回了
8 通过了
1 返回了
5 返回了
3 返回了
6 返回了
4 返回了
7 返回了
8 返回了

Exchange

  • java.util.concurrent.Exchanger 类表示一种两个线程可以进行互相交换对象的会和点。这种机制图示如下:
public class TestExchanger {
    public static void main(String[] args) throws InterruptedException {
        Exchanger exchanger = new Exchanger();

        new Thread(new TestExchanger().new ExchangerRunable("A",exchanger),"A").start();
        Thread.sleep(5000);
        new Thread(new TestExchanger().new ExchangerRunable("B",exchanger),"B").start();
    }

    class ExchangerRunable implements Runnable {
        private String exchangerObj;
        private Exchanger exchanger;

        public ExchangerRunable(String exchangerObj, Exchanger exchanger) {
            this.exchangerObj = exchangerObj;
            this.exchanger = exchanger;
        }

        @Override
        public void run() {
            String previous = exchangerObj;
            try {
                exchangerObj = (String) exchanger.exchange(exchangerObj);
                System.out.println(Thread.currentThread().getName() + " previous:" + previous + " exchange end: " + exchangerObj);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

输出结果

B previous:B exchange end: A
A previous:A exchange end: B

CountDownLatch 控制多线程执行完前,父线程阻塞
CyclicBarrier 控制多线程在某点同步
Semaphore 控制同时执行线程数
Exchanger 两个线程在某点交换数据

Java并发包基石-AQS详解

相关锁

  • synchronized和Reentrantlock

    • 区别:
    1. 使用方式不同,synchronized可在方法上和代码块中标示,而Reentrantlock只能在代码块中使用
    2. synchronized是jvm实现的而Reentrantlock是在jdk中的juc包下实现的
    3. Reentrantlock可实现公平锁和非公平锁,而synchronized只能是非公平的
    4. Reentrantlock可中断,而synchronized不行
    5. Reentrantlock可以同时绑定多个Condition对象(Condition对象通过Reentrantlock的newCondition方法创建,可以指定线程唤醒,而Objecet只能唤醒所有线程)
    • 选择:建议在不需要使用Reentrantlock的特殊功能时,使用synchronized,原因是1.synchronized是jvm自带的,可兼容各个版本,而Reentrantlock不行;2.效率上synchronized在1.6之后,进行了优化,加入了CAS自选,效率与Reentrantlock相似;3.synchronized会自动释放锁,不用担心死锁
  • 独占锁VS共享锁

    • 独占锁:只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁
    • 共享锁:多个线程可同时执行,如Semaphore、CountdownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。
  • 公平锁VS非公平锁

    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁。
    • 非公平锁: 当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。
  • 乐观锁VS悲观锁(概念)=自旋锁VS互斥锁(实现)

    • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
    • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
    • 两种锁的使用场景:从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
    • CAS的缺点:1.ABA问题;2.循环时间长开销大;3.只能保证一个共享变量的原子操作

线程池

线程池优势和参数

使用线程池的好处

  1. 频繁创建线程会消耗资源,而从池中获取线程则节省开销,就好比吃大锅饭,每个人重新造一把勺子很耗费时间,用完了放进锅中,别人再来拿就能够节约时间
  2. 将线程的创建和使用分开,方便维护

线程池执行任务顺序

  1. 首先核心线程数量未满,则使用核心线程来执行任务,核心线程一般不会超时,除非设置
  2. 当核心任务满后,判断是否超出队列大小,队列大小未满,则加入队列,等待核心线程执行完后,去除队列任务执行
  3. 当队列满时,若未超出最大线程数量,则创建线程执行
  4. 若超出最大线程数量,则根据用户或默认的策略抛出异常拒绝任务

线程池参数设置

  1. 核心线程数设置:根据每秒需要和可执行的任务数量设置,总共100~1000个任务,其中80%的时间为200个任务,一个任务消耗0.1秒,则每秒 20个任务需要执行,则核心线程数设置为20(此外,其上限也受制于硬件环境,若服务器为CPU密集型(CPU经常100%,IO很短时间就能完成),则核心线程数最大值 = CPU核数 + 1,若服务器是IO密集型,则核心线程数最大值 = CPU核数 * 2)
  2. 队列大小设置:是核心线程执行1秒的任务数量,由于一个任务为0.1s,核心线程为20,所以20/0.1为200
  3. 最大核心线程数设置:(最大的任务 - 队列大小)* 每个任务的时间,所以以上为(1000 - 200)* 0.1 = 80

线程池中各个参数如何合理设置

线程池参数可动态变化:

  1. 核心线程数和最大线程数可进行动态变化,但需注意核心线程数不能小于最大线程数量
  2. 队列大小无法动态变化,但可重写队列实现完成

如何设置线程池参数?

线程池异常问题

  • 问题:线程池对抛出的异常不会处理,导致用户无法感知和查看日志
  • 解决:
    1. 对代码进行try catch
    2. 自定义线程池,并重写afterExecute方法
    3. 使用submit,对返回的Future对象进行获取

线程池类型

1.newCachedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
  • 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
  • 适用:执行很多短期异步的小程序或者负载较轻的服务器

2.newFixedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无界阻塞队列
  • 通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程- 了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列),但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
  • 适用:执行长期的任务,性能好很多

3.newSingleThreadExecutor:

  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无界阻塞队列
  • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:一个任务一个任务执行的场景

4.NewScheduledThreadPool:

  • 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
  • 适用:周期性执行任务的场景

ThreadLocal变量

  • 作用:用作线程间的数据隔离,每个线程可填充自己的数据
  • 使用场景:1.横跨多个方法的上下文信息,如Session、用户信息;2.Spring的事务隔离,每个线程拥有自己的数据库连接
  • 原理:每个线程都会存储自己的ThreadLocal变量,从而保证数据隔离,存储类似于Map的方式,key为ThreadLocal的对象(从而可以设置不同的ThreadLocal变量),value为该对象设置的值。虽然类似Map,但并没有继承Map,数据结构为数组,若发生hash冲突,则表中的位置往后移动
//  Thread的变量

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  • 存在的问题:会出现内存泄漏的问题,原因是key为弱引用,垃圾回收会处理,处理后会设置为null,但是value无法被回收,由于使用线程池的方式线程实例一直存在,但时value一直累积增多,无法回收
  • 问题解决:在使用完ThreadLocal后,调用remove方法
  • 线程能否共享:可以,子线程可以通过上述提到的inheritableThreadLocals进行共享父线程的变量,原因是线程初始化时,会将父线程的该变量引用到子线程中,实现如下
private void test() {    
final ThreadLocal threadLocal = new InheritableThreadLocal();       
threadLocal.set("13");    
Thread t = new Thread() {        
    @Override        
    public void run() {            
      super.run();            
      Log.i( "年龄" + threadLocal.get());        
    }    
  };          
  t.start(); 
} 
public class Thread implements Runnable {
  ……
   if (inheritThreadLocals && parent.inheritableThreadLocals != null)
      this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  ……
}

Java面试必问:ThreadLocal终极篇 淦!
【纯干货】Java 并发进阶常见面试题总结

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值