Java多线程小结

死锁:企图抢夺对方所持有的锁,谁都不先让,陷入死结。(保证统一的加锁解锁顺序可以避免)
饥饿:由于优先级低等原因,一直抢不到锁而导致无法运行。
活锁:譬如2个线程都发现对方有自己需要的锁,就主动释放锁给对方。结果双方都在谦让,以至于都在做让锁的无用功。

线程的stop、suspend、resume等方法已废弃。

线程的中断方法:
1. interrupt():设置线程中断标志位,会对sleep和wait产生一个中断异常(实例方法)
2. isInterrupted():判断线程中断标志位(实例方法)
3. interrupted():判断当前线程的中断标志位,之后清除(静态方法)
用法:Thread.currentThread().isInterrupted()。

Thread.sleep():让当前线程休眠,但不释放资源。(静态方法)

wait()和notify()方法,都是Object类的实例方法,也就是说每个对象都可以调用。好比每个对象都有一个停车场(等待队列),调用obj.wait()就是把当前对象wait在这个停车场里。而notify()方法则把该对象停车场中的一个随机一个等待线程唤醒。
wait和notify都必须在synchronized语句块中才能使用。
wait会释放资源。

join():让调用线程wait,直到当前线程运行完毕(实例方法)

ThreadGroup:线程组

Thread.setDaemon():设置守护线程,守护线程会在所有工作线程结束后退出,如果只有守护线程,JVM会退出结束运行。常见守护线程有GC线程、JIT线程。

线程优先级:分为1-10级,根据操作系统不同有所区别。



ReentrantLock:可重入锁,可冲入指的是同一个锁可以多次获得。

ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
lock.unlock();
}

lock.lockInterruptibly():可被中断的锁,会抛出一个中断异常。

lock.tryLock(5, TimeUnit.SECONDS):尝试5秒内获取锁,如果失败,返回false。如果不带参数则立即返回,获取锁成功返回true,失败返回false。

公平锁:ReentrantLock fairLock = new ReentrantLock(true);



Condition condition = lock.newCondition();
condition.await(); // 当前线程等待
condition.awaitInterruptibly(); // 可中断等待
condition.signal(); // 随机唤醒该condition上await的一个线程



Semaphore semaphore = new Semaphore(5, true); // 5个许可;第二个参数可选,是否为公平锁
semaphore.acquire(); // 获取许可
semaphore.acquireInterruptibly(); // 可中断的许可获取
semaphore.release(); //释放许可



读写锁:ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock(); // 读锁
Lock wirteLock = readWriteLock.writeLock(); // 写锁

读与读线程不互斥,其余都互斥。



CountDownLatch latch = new CountDownLatch(5); // countDown被调用5次,才会把await的线程唤醒
latch.await(); // 当前线程等待
latch.countDown(); // 每调用一次,倒计时一次



public CyclicBarrier(int parties, Runnable barrierAction); // 第一个参数是计数数量,第二个参数是计数完成后的执行动作
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {......});
barrier.await(); // 等待并且计数1次,当有5个线程调用了await方法,则计数满5次,所有线程继续运行。该等待可被中断并抛出异常



LockSupport.park(); // 阻塞当前线程,静态方法
LockSupport.unPark(); // unpark必须操作在park过的线程上,否则会阻塞

park方法能响应中断,但是不会抛出中断异常,需要通过中断标志位来做中断处理(Thread.interrupted())。



指定线程数量的线程池:
ExecutorService Executors.newFixedTreadPool(int n);
线程数不限的线程池:
ExecutorService Executors.newCachedThreadPool();
只有一个线程的线程池:
ExecutorService Executors.newSingleThreadPool();
指定线程数量,用于定时执行任务的线程池:
ScheduledExecutorService Executors.newScheduledThreadPool(int n);
单个线程数量,用于定时执行任务的线程池:
ScheduledExecutorService Executors.newSingleThreadScheduledExecutor();

ScheduledExecutorService的方法:
1. schedule 延时多久执行某个任务
2. scheduleAtFixedRate 每隔一个时间段执行任务,如果任务耗时太长超过时间间隔,下个任务会立即执行。
3. scheduleWithFixedDelay 每个任务结束后隔多久执行下一个任务



线程池的核心类ThreadPoolExecutor
构造方法:ThreadPoolExecutor(int coreSize, int maximumPoolSize, long keepAlive, TimeUnit unit, BlockingQueue<Runnable> queue, ThreadFactory factory, RejectedExecutionHandler handler)
1. coreSize核心线程数
2. maximumPoolSize 最大线程数,只有等待队列满了,才会在coreSize的基础上增加线程,但最大不会超过该参数
3. keepAlive 存活时间,coreSize内的线程不受此限制
4. unit 存活时间单位
5. queue 等待队列
6. factory 线程工程,默认是直接new Thread,一般用默认的Executors.defaultThreadFactory()即可。也可以自定义,实现ThreadFactory接口。
7. handler 任务的拒绝策略,一般在线程不足且等待队列已满的时候触发

BlockingQueue的类型:
1. SynchronousQueue 没有容量的队列,只会阻塞
2. ArrayBlockingQueue 有界队列,内部是个有限数组
3. LinkedBlockingQueue 无界队列,是一个链表
4. PriorityBlockingQueue 优先级队列

拒绝策略jdk提供了4种,都是ThreadPoolExecutor的静态内部类:
1. AbortPolicy 抛弃任务并抛出异常
2. DiscardPolicy 直接丢弃任务,并什么都不做
3. DiscardEldestPolicy 丢弃最老的任务,再讲该任务加入等待队列
4. CallerRunsPolicy 主线程直接调用任务的run方法来执行
还可以自定义任务拒绝策略,实现RejectedExecutionHandler接口即可。

ThreadPoolExecutor提供了3个空方法,用于自定义增强功能:
1. beforeExecute 线程运行前执行
2. afterExecute 线程运行后执行
3. terminated 线程池退出时执行
只要继承ThreadPoolExecutor并实现上述3个方法即可。

关闭线程池:调用shutDown()方法,该方法不会中止正在运行的任务,但线程池不再接受新任务。

线程池与CPU的关系:
一般来说CPU没有等待才能提供最快的响应,所以对响应要求高的,CPU个数等于线程数最好。



Fork/Join框架:
ForkJoinPool为fork join线程池,向其中提交的任务必须为ForkJoinTask,ForkJoinTask有两个重要的子类:RecursiveAction和RecursiveTask,前者没有返回值,后者有返回值。主要是重写compute方法,使用递归的思路对任务进行拆分。fork方法进行递归拆分,join进行合并。



JUC集合类:

1. ConcurrentHashMap 高效并发的HashMap,使用的是分段加锁的原理。默认将bucket划分到16个segment里,每个segment的锁相互不受影响。该类高并发下读写性能好,但是size方法性能并不好(比Collections同步的HashMap性能差),是因为size方法先不加锁对分段元素数量求和,如果失败则对16个segment都要加锁,总共要加16把锁。

2. CopyOnWriteArrayList 适合读多写少。写的时候,会复制一个副本进行写入操作,最后将修改同步回去。这个跟JDK的读写锁不太一样,只有写入与写入会互斥。读写不互斥。

3. ConcurrentLinkedQueue 可以看做是线程安全的LinkedList。高并发下性能最好的队列,内部使用的是CAS的方式,所以性能特别快。

4. BlockingQueue 阻塞队列,适合作为数据共享的通道。常见的实现类有有界的ArrayBlockingQueue、无界的LinkedBlockingQueue、容量为0的SynchronousBlockingQueue、任务优先级的PriorityBlockingQueue、延时队列DelayQueue、双端队列BlockingDeque等等。元素都是从尾部放入,从头部取出,有两组方法需要关注:offer()与poll()、put()与take()。前一组非阻塞,失败直接返回false与null;后者会阻塞。

5. ConcurrentSkipListMap 使用调表的数据结构进行快速查找(类似于索引)。跳表数据结构的元素是排序的,所以对内部数据输出,是按照排序顺序输出的。

Collections可以将map等转换为线程安全的类。本质上是使用装饰模式,对原有集合功能进行增强,给起加锁。用法:Collections.synchronizedMap(new HashMap());
这种方式高并发下的性能不是很好。



锁的优化策略:
1. 只在必要时进行同步,减少总的同步时间
2. 减小锁的粒度,譬如ConcurrentHashMap那样
3. 锁分离。譬如LinkedBlockingQueue对put和take方法分别用两把锁进行同步。因为put和take是分别作用于队列的尾部和头部,本身就不冲突,也就没必要用同一把锁进行同步。
4. 锁粗化。譬如在for循环中获取锁。多次的对锁的请求耗费资源,可以将同步块扩大到for循环外。



偏向锁:假定锁只会被一个线程获取,在同一个线程第二次来请求锁时,将不再进行锁的同步操作。如果有其他线程来获取锁,偏向锁将失效。

轻量级锁:加解锁都是使用CAS操作来更新对象头的相关信息。如果有2个以上的线程争夺对象锁,则轻量级锁失效,膨胀为重量级锁。

重量级锁:就是互斥锁

自旋锁:如果线程获取锁失败,不会立即挂起,而是会做几次循环重新尝试获取锁。自旋锁在虚拟机里是自适应的,具体参考《深入理解Java虚拟机》

锁消除:将锁同步操作直接消除掉,与不加锁一样处理。譬如局部变量的Vector,没有必要加锁,本身作用域就是线程安全的。逃逸分析技术可参考《深入理解Java虚拟机》



ThreadLocal:
Thread类有一个ThreadLocalMap类型的成员变量,这样每new一个Thread线程,每个线程对象都有自己的ThreadLocalMap变量,该变量为键值对类型,key为指向ThreadLocal实例的弱引用,value为具体的值。每个线程通过把具体的值放入到这个map里,来避免高并发下线程同步导致的资源共享。

如果使用线程池,那么线程很可能是复用的,会一直存在。如果一直存在,那么ThreadLocalMap会由于存放越来越多的值而膨胀,最终导致内存泄漏。所以最好通过ThreadLocal.remove()方法来主动回收变量。或者通过将ThreadLocal的实例赋值为null,这样ThreadLocalMap里的entry的key就是null的,就是无效的entry,会被系统在给ThreadLocalMap增加值等情况主动清理key为null的无效值。

ThreadLocal的缺点是是占用更大的内存,但是会比同步更快。所以如果同步情况下性能损失较大,就使用ThreadLocal来处理。



无锁
属于乐观锁,使用CAS算法。CAS(V, E, N),对V值进行判断是否与期望值E相等,如果相等,更新为N值。

AtomicInteger:使用CAS保证Integer操作的原子性,所以是线程安全的。同样的有 AtomicLong、AtomicBoolean、对象引用AtomicReference(该类也提供了compareAndSet、getAndSet方法等,每次操作都是整个对象级的)

AtomicStampedReference:比AtomicReference多了个版本号,等于是一次性要比较两个值并设置两个值。

AtomicIntegerArray:无锁Integer数组,统一使用的是CAS算法保证原子性。类似的还有AtomicLongArray、AtomicReferenceArray

Unsafe类:封装了一些类型指针的操作,如compareAndSet、堆外内存分配等。只有rt.jar包中的类能操作该类,该类会判断调用方的ClassLoader是否为null(只有C++实现的Bootstrap类加载器的Java层面的ClassLoader为null,其余ext、app等类加载器都是Java层面的实现,ClassLoader部位null)

AtomicIntegerFieldUpdater:使用反射,对非线程安全的Integer类进行包装,得到一个原子的线程安全的操作类。譬如二方库、三方库,引入的是jar,源码不能改,已经写死成非线程安全的,怎么办?调用的时候,用该类包装一下,就是线程安全的了。
类似的还有Long和Reference的。
条件要求比较苛刻,用到的时候再参考怎么做。记住就行。



怎么避免死锁:
1. 保证相同的加锁解锁顺序
2. 使用无锁CAS
3. 使用可中断或有限时的可重入锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值