Java线程基础知识(状态、共享与协作)
1.基础概念
-
CPU核心数和线程数的关系
核心数:线程数=1:1 ;使用了超线程技术后---> 1:2 -
CPU时间片轮转机制
又称RR调度,会导致上下文切换 -
什么是进程和线程
进程:程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源
线程:CPU调度的最小单位,必须依赖进程而存在。 -
澄清并行和并发
并行:同一时刻,可以同时处理事情的能力
并发:与单位时间相关,在单位时间内可以处理事情的能力 -
高并发编程的意义、好处和注意事项
好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化
问题:
线程共享资源,存在冲突;
容易导致死锁;
启用太多的线程,就有搞垮机器的可能 -
查看 JVM自启动线程
Attach Listener :线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。
signal dispather: 前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
Finalizer: 用来执行所有用户Finalizer 方法的线程
Reference Handler :它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "]" + " " + threadInfo.getThreadName());
}
}
- Java management包
management包中提供了比较全面的监控和管理工具,包括JVM的监管API、监管API日志等等。 -
管理接口
PlatformManagementObject接口:所有的管理接口都要继承该接口,这个接口是从1.7才出现的,从其文档的注释中可以看到其存在的价值是为以后平台的扩展而设计的,而不是为了应用程序。
BufferPoolMXBean接口:缓冲池管理接口包括direct和mapped类型的缓冲池。
ClassLoadingMXBean接口:类加载管理接口,可以监控管理虚拟机类加载系统。
CompilationMXBean接口:虚拟机的编译系统的管理与监控。
GarbageCollectorMXBean接口:虚拟机垃圾收集的管理接口,通过该接口可以查看垃圾收集的时间和次数。
MemoryManagerMXBean接口:该接口用于内存管理,其中,垃圾收集器属于该类型的内存管理器。
MemoryMXBean接口:用于虚拟机的内存管理,执行GC、获取堆内存和非堆内存相关数据。
MemoryPoolMXBean接口:用于内存池的管理,所谓的内存池表示的是虚拟机使用和内存管理者管理的内存资源。
OperatingSystemMXBean接口:操作系统管理接口,可以查看系统的平均负载、系统参数、可用的进程数、系统版本和名称等等。
PlatformLoggingMXBean接口:日志管理接口,可以设置日志级别、获取日志名称等等。
RuntimeMXBean接口:虚拟机运行时管理接口,获取虚拟机的名称、虚拟机版本、获取java的classpath、获取系统参数等。
ThreadMXBean接口:虚拟机线程管理。可以获取线程数、获取线程Id、线程信息、当前线程CPU时间、当前线程用户时间、查看死锁线程等等。 -
信息类实体
LockInfo:任何的java锁(简单的java锁和Concurrent包中所使用的锁,AbstractOwnableSynchronizer和Condition的实现类/子类)。
MemoryUsage:内存使用快照,用于获取每个虚拟机或者堆或者虚拟机非堆内存池作为整体的使用信息。
MemoryNotificationInfo:内存通知的信息。
MonitorInfo:继承自LockInfo,同步代码块或者是同步方法上的锁。
ThreadInfo:线程信息,包括线程名称,线程id,阻塞时间,阻塞次数,等待时间,等待次数,锁信息,锁名称,锁拥有者id等。
ManagementPermission:权限管理类。 -
工厂类
ManagementFactory:MXBean通过该工厂类进行获取,使用了工厂模式管理,经过该类获取到相应的MXBean类之后再调用其中的方法得到需要管理和监控的信息。
2.Java的三种线程创建方式
-
类Thread
-
接口Runnable
-
接口Callable
-
接口Runnable与接口Callable区别
(1)Callable规定的方法是call(),Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)Callable方法可以抛出异常,Runnable方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。
3.Java线程的状态
![](http://upload-images.jianshu.io/upload_images/4222138-15541ed567bcd51f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/968/format/webp)
-
进入Blocked状态,是在竞争锁的过程中,比如synchronized导致的竞争。从而进入blocked状态。
Waiting/Timed_Waiting是在调用了wait()之后,进入了waiting状态。待notify()/notifyAll()之后,就进入Blocked状态。然后竞争锁,从而可以获取处理机,进行执行操作。 -
Notify() vs NotifyAll()
notify: 从waiting等待队列中,随机选取一个线程进行唤醒。
notifyAll: 将改对象的waiting等待队列中的所有线程唤醒,并入blocked状态。然后由其自由竞争锁,依次执行,最终直到所有的线程都依次获取锁,并执行完毕。队列中就没有等待的线程。 -
线程只有6种状态。整个生命周期就是这几种状态的切换。
-
run()和start() :run方法就是普通对象的普通方法,只有调用了start()后,Java才会将线程对象和操作系统中实际的线程进行映射,再来执行run方法。
run 和 start区别代码StartAndRun.java -
yield() :让出cpu的执行权,将线程从运行转到可运行状态,但是下个时间片,该线程依然有可能被再次选中运行。
-
线程的优先级
取值为1~10,缺省为5,但线程的优先级不可靠,不建议作为线程开发时候的手段 -
守护线程
和主线程共死,finally不能保证一定执行
DaemonThread.java -
线程自然终止:自然执行完或抛出未处理异常
stop(),resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()容易导致死锁。 -
问题:interrupt是怎样避免suspend之类的死锁的?是使用wait notify实现的?
-
java线程是协作式,而非抢占式
调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
isInterrupted() 判定当前线程是否处于中断状态。
static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false。
方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。
抛出异常HasInterruptException.java
参考EndRunnable.java
参考EndThread.java
-----------------------------------补充--------------------------------------- -
当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法。
抛InterruptedException的代表方法有:
- java.lang.Object 类的 wait 方法
- java.lang.Thread 类的 sleep 方法
- java.lang.Thread 类的 join 方法
执行wait方法的线程,会进入等待区等待被notify/notify All。在等待期间,线程不会活动。
执行sleep方法的线程,会暂停执行参数内所设置的时间。
执行join方法的线程,会等待到指定的线程结束为止。
因此,上面的方法都是需要花点时间的方法。
- sleep方法与interrupt方法
interrupt方法是Thread类的实例方法,在执行的时候并不需要获取Thread实例的锁定,任何线程在任何时刻,都可以通过线程实例来调用其他线程的interrupt方法。
当在sleep中的线程被调用interrupt方法时,就会放弃暂停的状态,并抛出InterruptedException异常,这样一来,线程的控制权就交给了捕捉这个异常的catch块了。 - wait方法和interrupt方法
当线程调用wait方法后,线程在进入等待区时,会把锁定接触。当对wait中的线程调用interrupt方法时,会先重新获取锁定,再抛出InterruptedException异常,获取锁定之前,无法抛出InterruptedException异常。 - join方法和interrupt方法
当线程以join方法等待其他线程结束时,一样可以使用interrupt方法取消。因为join方法不需要获取锁定,故而与sleep一样,会马上跳到catch程序块
4.线程的共享
- synchronized内置锁
对象锁,锁的是类的对象实例。
类锁 ,锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。
对象锁和类锁SynClzAndInst.java - volatile关键字
适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性。
volatile不能保证线程安全VolatileUnsafe.java - ThreadLocal
线程局部变量。可以理解为是个map,类型 Map<Thread,Integer>
ThreadLocal保证互不干扰UseThreadLocal.java
5.线程的协作
-
等待和通知的标准范式
等待方:
1.获取对象的锁
2.循环里判断条件是否满足,不满足调用wait方法
3.条件满足执行业务逻辑
通知方:
1.获取对象锁
2.改变条件
3.通知所有等待在对象的线程 -
notify和notifyAll应该用谁?
尽量使用notifyAll,使用notify有可能发生信号丢失的情况。
notify和notifyAll实例-快递类Express.java
notify和notifyAll实例-测试类TestWN.java -
等待超时模式实现一个连接池
假设等待时长T,当前时间now + T以后超时
等待超时模式——数据库连接池框架
核心类wait-notifyAll DBPool.java
测试类DBPoolTest.java
long overtime = now + T
long remain = T;
while (result 不满足条件 && remain > 0) {
wait(remain);
remain = overtime – now;
}
return result;
- join()方法
保证线程A在线程B执行后再继续执行:join和CountDownLatch
join嵌套插队 UseJoin.java - 调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?
yield后,所持有的锁是不释放的
sleep,持有的锁是不释放
在调用wait()前,必须持有锁,调用wait之后,锁自动释放,当wait返回时,线程会重新持有锁
notifyAll调用前持有锁,调用notifyall本身不会释放锁,所以一般放到方法最后
sleep持有锁时不会释放 SleepLock.java
转自:https://www.jianshu.com/p/492f45ea7e64