1.关键术语和多线程基础介绍
关于多线程的基础知识请卡如下的文章:
2.基础
关键句子:Java当中线程的概念和操作系统级别线程的概念是类似的。事实上,Jvm将会把Java中的线程映射到操作系统的线程区
线程状态图:
当new出一个线程时,其实线程并没有工作。它只是生成了一个实体,当你调用这个实例的start方法时,线程才真正地被启动。启动后到Runnable状态,Runnable表示该线程的资源等等已经被准备好,已经可以执行了,但是并不表示一定在执行状态,由于时间片轮转,该线程也可能此时并没有在执行。对于我们来说,该线程可以认为已经被执行了,但是是否真实执行,还得看物理cpu的调度。当线程任务执行结束后,线程就到了Terminated状态。
有时候在线程的执行当中,不可避免的会申请某些锁或某个对象的监视器,当无法获取时,这个线程会被阻塞住,会被挂起,到了Blocked状态。如果这个线程调用了wait方法,它就处于一个Waiting状态。进入Waiting状态的线程会等待其他线程给它notify,通知到之后由Waiting状态又切换到Runnable状态继续执行。当然等待状态有两种,一种是无限期等待,直到被notify。一直则是有限期等待,比如等待10秒还是没有被notify,则自动切换到Runnable状态。
关键方法介绍:
stop():会释放所有monitor,立即停止掉线程 (过于暴力,不推荐)
intterrupt():线程中断 (并不是一定中断,它只是要求线程自己在合适的时机中断自己。)
suspend():挂起,但不会挂起
resume():继续执行,(如果加锁发生在resume()之前 ,则死锁发生)
yeild():把自己占有的cpu时间释放掉,然后和其他线程一起竞争(注意yeild的线程还是有可能争夺到cpu,注意与sleep区别)
join():等待其他线程结束,(让主线程等待t1,t2结束以后再结束。)
Daemon():守护线程(如:在后台默默地完成一些系统性的服务)
setPriority:线程优先级(并不一定是高优先级一定先完成。再多次运行后发现,高优先级完成的概率比较大,但是低优先级还是有可能先完成的。)
synchronized有三种加锁方式:
指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
3Java内存模型和线程安全
Java内存模型见下图:
详细看:线程安全
4.无锁篇
详细看:高并发Java(4):无锁
5.JDK并发包
synchronized有三种加锁方式:
指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
作用于实例方法,则不要new两个不同的实例
作用于静态方法,只要类一样就可以了,因为加的锁是类.class,可以new两个不同实例。
lock分别有:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁
5.1ReentrantLock
相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。
5.1.1可重入
lock.lock();
lock.lock();
ock.unlock();
lock.unlock();
5.1.2可中断(与synchronized不同的是,ReentrantLock对中断是有响应的)
场景:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情
lock.lockInterruptibly()能够响应中断
5.1.3可限时
ock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。
5.1.4公平锁
场景:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待
这个锁能保证线程是先来的先得到锁
5.1.5锁可以绑定多个条件
场景:ReentrantLock 对象可以同时绑定多个 Condition 对象;需要多次调用 newCondition()方法即可。而且我们还可以通过绑定 Condition 对象来判断当前线程通知的是哪些线程(即与 Condition 对象绑定在一起的其他线程)。
6.JDK并发包
6.1线程池
Executers分类:
new FixedThreadPool 固定数量的线程池,线程池中的线程数量是固定的,不会改变。
1.newFixedThreadPool 与 cacheThreadPool 差不多,也是能 reuse 就用,但不能随时建新的线程。
2.其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外
的队列中等待,直到当前的线程中某个线程终止直接被移出池子。
new SingleThreadExecutor 单一线程池,线程池中只有一个线程。
1.单例线程,任意时间池中只能有一个线程
new CachedThreadPool 缓存线程池,线程池中的线程数量不固定,会根据需求的大小进行改变。
1.缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse 如果没有,就建一个新的线程加入池中
2.缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的 daemon 型 SERVER 中用
得不多。但对于生存期短的异步任务,它是 Executor 的首选。
3.能 reuse 的线程,必须是 timeout IDLE 内的池中线程,缺省 timeout 是 60s,超过这个 IDLE 时长,线程
实例将被终止及移出池。
new ScheduledThreadPool 计划任务调度的线程池,用于执行计划任务,比如每隔5分钟怎么样,
1.这个池子里的线程可以按 schedule 依次 delay 执行,或周期执行
6.2callback和future
Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值
Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行
取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果
7.Volatile关键字
使用建议:在两个或者更多的线程需要访问的成员变量上使用 volatile。当要访问的变量已在 synchronized 代码块中,或者为常量时,没必要使用 volatile。
8.阻塞队列和阻塞栈
8.1阻塞队列
阻塞队列的接口是 java.util.concurrent.BlockingQueue,它有多个实现类:ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue 等
实现原理:实现了一个有界队列,当队列满时,便会阻塞等待,直到有元素出队,后续的元素才可以被加入队列。
8.2阻塞栈
阻塞栈与阻塞队列相似,只是它是 Java 6 中加入的新特性,阻塞栈的接口java.util.concurrent.BlockingDeque 也有很多实现类,使用方法也比较相似
9障碍器
使用时需要导入java.util.concurrent.CylicBarrier。它适用于这样一种情况:你希望创建一组任务,它们并发地执行工作,另外的一个任务在这一组任务并发执行结束前一直阻塞等待,直到该组任务全部执行结束,这个任务才得以执行。这非常像 CountDownLatch,只
是 CountDownLatch 是只触发一次的事件,而 CyclicBarrier 可以多次重用。
10并发新特性—信号量 Semaphore
以控制某个资源被同时访问的任务数,它通过acquire()获取一个许可,release()释放一个许可。如果被同时访问的任务数已满,则其他 acquire 的任务进入等待状态,直到有一个任务被 release 掉,它才能得到许可。
参考文档:java并发编程
网友评论