java并发编程基础
线程简介
为什么要使用线程
- 更多的处理器核心
- 更快的响应时间
- 更好的编程模型
线程优先级
在java中,通过整型变量priority来控制优先级,范围是1~10。默认是5级,设置优先级时,针对频繁阻塞(休眠/IO操作)需要设置高优先级,偏计算的设置低优先级。(但是操作系统可能会忽略java优先级的设置)
线程的状态
线程的运行生命周期中有6种状态:
- NEW:初始状态,线程被构建,但是没有调用start()方法
- RUNNABLE:运行状态
- BLOCKED:阻塞状态表示线程阻塞于锁
- WAITING:等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定的操作(通知或中断)
- TIME_WAITING:超时等待状态,该状态不同于WAITING,他是可以在指定的时间自动返回的。
- TERMINATED:终止状态,表示当前线程已经执行完毕。
public static void main(String[] args) {
new Thread(new TimeWaiting(), "TimeWaitingThread").start();
new Thread(new Waiting(), "WaitingThread").start();
new Thread(new Blocked(),"BlockedThread-1").start();
new Thread(new Blocked(),"BlockedThread-2").start();
}
// 该线程不断的进行睡眠
static class TimeWaiting implements Runnable {
@Override
public void run() {
while (true) {
SleepUtils.second(100);
}
}
}
// 该线程在Waiting.class实例上等待
static class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 该线程在Blocked.class 实例上加锁后,不会释放该锁
static class Blocked implements Runnable {
@Override
public void run() {
synchronized (Blocked.class) {
while ((true)) {
SleepUtils.second(100);
}
}
}
}
public static class SleepUtils {
public static final void second(long seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
通过运行这段代码,在输入jps找到pid,再执行jstack $pid,我们发现
// BlockedThread-2线程阻塞在获取Blocked.class示例的锁上
"BlockedThread-2" prio=5 tid=0x00007feacb05d000 nid=0x5d03 waiting for monitor
entry [0x000000010fd58000]
java.lang.Thread.State: BLOCKED (on object monitor)
// BlockedThread-1线程获取到了Blocked.class的锁
"BlockedThread-1" prio=5 tid=0x00007feacb05a000 nid=0x5b03 waiting on condition
[0x000000010fc55000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
// WaitingThread线程在Waiting实例上等待
"WaitingThread" prio=5 tid=0x00007feacb059800 nid=0x5903 in Object.wait()
[0x000000010fb52000]
java.lang.Thread.State: WAITING (on object monitor)
// TimeWaitingThread线程处于超时等待
"TimeWaitingThread" prio=5 tid=0x00007feacb058800 nid=0x5703 waiting on condition
[0x000000010fa4f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
启动和终止线程
构造线程对象
初始化一个Thead,内部执行的是init()方法
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
启动线程-start()方法
start()方法的含义是:当前线程同步告知java虚拟机,只要线程规划期空闲,应立即启动调用start()的线程
理解中断
其他线程通过调用该线程的interrupt()来对其进行中断操作
线程间的通信
volatile关键字
volatile用来修饰字段,就是告知程序任何对该变量的访问均需要从共享内存中获取,对他的改变必须同步刷新共享内存,他能保证所有线程对变量的访问的可见性。
synchronized关键字
synchronized可以修饰方法或者以同步块的形式来进行使用。它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,保证了线程对变量的访问的可见性于排他性。
任何对象都有自己的监视器,当这个对象被同步块或者同步方法调用时,执行方法必须先获取该对象的监视器,而没有获取到监视器的线程则会被阻塞到同步块和同步方法的入口,进入BLOCKED状态。
等待/通知机制
相关的方法
方法名称 | 描述 |
---|---|
notify | 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁 |
notifyAll | 通知所有等待在该对象的线程 |
wait | 调用该方法进入waiting状态,只有等待其他线程通知或被中断才能返回,调用wait()方法,会释放对象的锁 |
wait(long) | 超时等待一段时间,没有通知就超时返回 |
wait(long,int) | 更细粒度的控制 |
经典范式,等待/通知的经典范式,分为两部分,等待方/通知方
等待方
获取对象的锁
如果条件不满足,那么调用对象的wait()方法,被统治后仍要检查条件
条件满足则执行对应的逻辑
synchronized(对象) { while(条件不满足) { 对象.wait(); } 对应的逻辑处理 }
通知方
获取对象的锁
改变条件
通知所有的等待在该对象的线程
synchronized(对象) { 改变条件 对象.notifyAll(); }
Thread.join()的使用
thread.join()指的是当前线程等待thead线程终止后才从thread.join()线程返回。
ThreadLocal的使用
ThreadLocal线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储对象。这个结构被附带到线程上。
private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new ThreadLocal<>();
public static void begin() {
TIME_THREAD_LOCAL.set(System.currentTimeMillis());
}
public static long end() {
return System.currentTimeMillis() - TIME_THREAD_LOCAL.get();
}
public static void main(String[] args) throws InterruptedException {
Profiler.begin();
TimeUnit.SECONDS.sleep(2);
TimeUnit.SECONDS.sleep(20);
System.out.println("cost:" + Profiler.end() + "mills");
}
begin()方法将时间绑定到当前线程上,end()方法返回的是begin()方法到end()方法调用的时间差