学习JAVA并发编程之美笔记(一)
技术类书籍是拿来获取知识的,不是拿来收藏的,你得到了书籍不意味着你得到了知识,所以请不要得到书籍后就觉得沾沾自喜,要经常翻阅!!经常翻阅!
Java并发编程线程基础
写在前面的话:这是我自己学习本书记录的感悟。
Q1:什么是线程?
解释线程前需要说清什么是进程,因为线程是进程中的一个实体,线程本身不会独立存在。进程是操作系统进行资源分配和调度的基本单位,线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。操作系统分配资源是按照进程的,但是cpu分配资源是按照线程的。因为真正占用cpu运行的是线程,所以说线程是cpu分配的基本单位。
Q2:线程通知与等待
wait();
当一个线程调用一个共享变量的wait()方法时,调用线程会被阻塞挂起。
(1)其他线程调用该共享变量的notify()或notifyAll()方法。
(2)其他线程调用该线程的interupt()方法,改线程抛出interuptedException异常返回。
注意若是调用共享对象wait()方法的线程没有拿到该对象的监视器锁时会抛出IllegaMonitorStateException异常。
package com.example.thread;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 生产者、消费者经典例子
*
* @Author qiulijun
* @Date 16:33 2019/2/25
* @Param
* @return
**/
@Slf4j(topic = "生产者、消费者")
public class Queue {
public static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(16, true);
public static void production() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (queue) { // 拿到检索监视器锁
while (queue.size() == 10) {
try {
log.info("线程0 wait");
queue.wait();
} catch (InterruptedException e) {
log.error("线程等待,被中断", e);
}
}
for (int i = 0; i < 10; i++) {
log.info("添加i " + i);
try {
queue.put(i);
} catch (InterruptedException e) {
log.error("线程等待,被中断", e);
}
}
queue.notifyAll();
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (queue) { // 拿到检索监视器锁
while (queue.size() == 0) {
try {
log.info("线程2 wait");
queue.wait();
} catch (InterruptedException e) {
log.error("线程等待,被中断", e);
}
}
log.info("---------------" + queue.size());
log.info("---------------" + queue);
try {
for (int i = 0; i < 10; i++) {
log.info("移除i " + i);
queue.take();
}
} catch (InterruptedException e) {
log.error("线程等待,被中断", e);
}
log.info("---------------" + queue);
queue.notifyAll();
}
}
});
thread.start();
thread1.start();
}
public static void main(String[] args) {
production();
}
}
这是经典的生产者、消费者。在使共享变量wait时先将监视器锁拿到,所以不会抛出异常。通过判断队列大小,避免虚假唤醒。
在释放资源时当前线程只会释放调用的wait();方法的资源,假如线程持有多个共享资源其他资源不会被释放。
join();
会在调用线程中进行阻塞,等待其他线程执行完成。
package com.example.thread;
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("德玛西亚");
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("诺克萨斯");
}
});
thread.start();
thread1.start();
System.out.println("。。。。");
thread.join();
thread1.join();
System.out.println("。。。。");
}
}
主线程进行到
thread.join();
thread1.join();
会进行阻塞,子线程进行执行。
输出结果为:
。。。。
德玛西亚
诺克萨斯
。。。。
假如将
//thread.join();
//thread1.join();
注释
输出结果为
。。。。
。。。。
德玛西亚
诺克萨斯
sleep();
Thread类中的静态方法,当一个执行中线程调用了sleep方法,线程会暂时让出指定时间的执行权。不参与cpu的调度,当时线程所拥有的监视器资源不会释放。
yield();
Thread类中有个一静态yield方法,当线程调用这个方法时,实际暗示线程调度器当前线程请求让出自己的cpu使用,但是线程调度器可以无条件忽略这个暗示。假如让出成功,会处于就绪状态。线程调度器会从就绪队列中取一个优先级最高的一个线程,有可能还是这个线程获得cpu执行权。
线程中断
interrupt();终端线程,线程A运行线程B将A线程interrupt方法掉用并且设置为true并立即返回。设置标记不会使A线程中断,会继续往下执行。A线程调用wait、join、sleep方法进入阻塞状态时,线程B调用interrupt会抛出中断异常IntrruptedExption返回。
package com.example.thread;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
@Slf4j(topic = "中断")
public class TestJoin {
public static ArrayBlockingQueue a = new ArrayBlockingQueue<Integer>(10);
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("德玛西亚");
synchronized (a) {
while (true) {
try {
a.wait();
} catch (InterruptedException e) {
log.error("阻塞状态被中断" ,e);
}
}
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("诺克萨斯");
}
});
thread.start();
thread1.start();
System.out.println("。。。。");
thread.interrupt();
thread.join();
thread1.join();
System.out.println("。。。。");
}
}
输出结果
。。。。
诺克萨斯
德玛西亚
17:39:16.703 [Thread-0] ERROR 中断 - 阻塞状态被中断
java.lang.InterruptedException: null
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.example.thread.TestJoin$1.run(TestJoin.java:20)
at java.lang.Thread.run(Thread.java:745)
isInterrupted();检测当前线程是否被中断,是返回true。
interrupted();检测当前线程是否被中断,是返回true并且清除终端标识。
Q3:理解线程上下文切换
在多线程编程中,线程个数一般都大于 CPU 个数,而每个 CPU 时同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的,CPU 资源的分配采用了时间片轮转策略,也就是给每个线程分配 个时间片,线程在时间片内占用 CPU 执行任务。当线程使用完时间片后,就会处于就绪状态并让出CPU让其他线程占用 这就是上下文切换从当前线程的上下文切换到了其他线程 那么就有个问题,让出 CPU 的线程等下次轮到自己占有 CPU 时如何知道自己之前运行到哪里了?所以在切换线程上下文时需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场。
线程上下文切换时机有当前线程的CPU时间片使用完处于就绪状态时,当前线程被其他线程中断时。
Q4:ThreadLocal
我理解ThreadLocal是将一份共享变量,复制给每个线程。同时每个线程只会对这个变量进行读,所以共享变量不会改变。ThreadLocal操作变量是不需要加锁保证数据一致性。
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set("1");
threadLocal.get();
}
});
}
}
threadLocal.set("1");
内部实现
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);// 假如线程刚刚初始化map肯定为null.
if (map != null)
map.set(this, value); // 将当前线程与value存入,当前线程中threadLocals变量中
else
createMap(t, value);// 初始化当前线程中threadLocals属性
}
map.set(this, value);
内部实现
ThreadLocalMap getMap(Thread t) { // t是当前线程,t.threadLocals就能拿到当前线程中threadLocals属性
return t.threadLocals;
}
createMap(t, value);
内部实现
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
map.set(this, value);
内部实现
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
很明显ThreadLocal的作用是将共享变存入线程中的工具。维护线程和变量的map在每个线程中。
但是ThreadLocal是不支持继承的!
public class Test {
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set("1");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Object o = threadLocal.get();
System.out.println(o);
}
});
thread.start();
Object o = threadLocal.get();
System.out.println(o);
}
}
输出结果:
1
null
Process finished with exit code 0
在主线程设置属性在子线程不能取到,如果想要取到值就需要通过inheritableThreadLocals这个属性。
public class Test {
static InheritableThreadLocal threadLocal = new InheritableThreadLocal();
public static void main(String[] args) {
threadLocal.set("1");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Object o = threadLocal.get();
System.out.println(o);
}
});
thread.start();
Object o = threadLocal.get();
System.out.println(o);
}
}
输出结果:
1
1
Process finished with exit code 0
使用InheritableThreadLocal能够继承属性原因:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
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 (parent.inheritableThreadLocals != null) // 判断父线程中inheritableThreadLocals 是否为空,不为空将其中值赋予当前线程。
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
第一章基本完成,为其他技术做铺垫。