课程包括:核心基础、内存模型、死锁。
本章概览图
1·、实现多线程的方法到底有1种还是2种还是4种?
2、怎样才是正确的线程启动方式?
3、上山容易下山难---如何正确停止线程?(难点)
4、线程的一生---6个状态(生命周期)
5、Thread类和Object类中的重要方法详解
6、线程的各个属性
7、未捕获异常如何处理?
8、双刃剑:多线程会导致的问题
方法1:实现Runnable接口
方法2:继承Thread类
两种方法的对比:
方法1(实现Runnable接口)更好
两种方法的本质对比:
应该会执行子类的方法。(从面向对象的角度去考虑这个问题)
package threadcoreknowledge.createThreads;
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("我来自Runnable");
}
}) {
@Override
public void run() {
System.out.println("我来自Thread类");
}
}.start();
}
}
执行结果:
我来自Thread类
最精准的描述。
错误观点1:“线程池创建线程也算是一种新建线程的方式”
错误观点2:“通过Callable和FutureTask创建线程,也算是一种新建线程的方式”
错误观点3:“无返回值是实现Runnable接口,有返回值是实现Callable接口,所以Callable接口是新的实现线程的方式”
错误观点4:使用定时器创建线程
package threadcoreknowledge.createThreads.wrongways;
import java.util.Timer;
import java.util.TimerTask;
/**
* 定时器创建线程
*/
public class DemoTimmerTask {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1000, 1000);
}
}
错误观点5:匿名内部类
错误观点6:Lambda表达式
观点5、观点6其实只是利用了新的语法特性而已。
多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。
1、看经典书籍(比如:Java并发编程实战、自顶向下计算机网络)
2、看官方文档
3、英文搜google和stackoverflow
4、自己动手写,实践写demo,尝试用到项目里
5、对于不理解的知识点,参考该领域的多个书本,综合判断
6、学习开源项目,分析源码(学习synchronized原理,反编译,看cpp代码)
偏业务方向:比如天猫淘宝电商领域、百度搜索领域、滴滴的出行领域,这时候可以了解业务的核心模型。把复杂的业务进行合理的抽象。
偏技术方向:比如中间件、存储系统、消息中间件、RPC等
题目1:有多少种实现线程的方法?思路有5点。
题目2:实现Runnable接口和继承Thread类哪种方式更好?
线程池
就是基于此想法。package threadcoreknowledge.startthread;
public class StartAndRunMethod {
public static void main(String[] args) {
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName());
};
runnable.run();
new Thread(runnable).start();
}
}
启动新线程:请求JVM启动一个线程,至于何时被启动,由JVM决定。
准备工作:新线程处于就绪状态,即已获得除CPU外的其他资源。
start方法的实际过程:
线程组
start0()
方法,start0
是一个native
方法源码解析
public void run() {
if (target != null) {
target.run();
}
}
两种情况:
继承Thread类,重写run()方法,将执行子类的run()方法。
将runnable实例传递给Thread类,作为Thread类的target属性,然后调用runnable.run()
1、一个线程两次调用start()方法会出现什么情况?为什么 ?
会抛出一个异常,java.lang.IllegalThreadStateException
在start()
代码实现的开头,会对线程状态
做检查。
2、既然start()方法会调用run()方法,为什么我们选择调用start()方法,而不是直接调用run()方法呢?
因为调用start()
方法才会真正意义上启动一个线程,经历线程的各个生命周期。如果直接调用run()方法,那它就是一个普通的方法调用而已。
原理介绍:
使用interrupt来通知,而不是强制。
通常线程会在什么情况下停止?
线程被通知停止时,线程可能处于的状态?
通常线程会在什么情况下停止?普通情况(没有阻塞的情况)。
package threadcoreknowledge.stopthread;
/**
* run 方法内没有调用sleep 或者 wait方法时,停止线程
*/
public class RightWayStopThreadWithoutSleep implements Runnable {
@Override
public void run() {
int num = 0;
while (!Thread.currentThread().isInterrupted()
&& num <= Integer.MAX_VALUE / 2) {
if (num % 10000 == 0) {
System.out.println(num + "是10000的倍数");
}
num++;
}
System.out.println("任务运行结束了");
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new RightWayStopThreadWithoutSleep());
t.start();
Thread.sleep(800);
t.interrupt();
}
}
线程可能被阻塞
package threadcoreknowledge.stopthread;
/**
* 带有sleep的中断线程的写法
*/
public class RightWayStopThreadWithSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 300 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("我被interrupt了");
e.printStackTrace();
}
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("interrupte后...还会执行吗" + i);
}
};
Thread t = new Thread(runnable);
t.start();
Thread.sleep(500);
t.interrupt();
}
}
如果线程在每次迭代后都阻塞
package threadcoreknowledge.stopthread;
/**
* 如果在执行过程中,每次循环都会调用sleep或者wait方法,那么不需要每次迭代都检查是否已经中断
*/
public class RightWayStopThreadWithSleepEveryLoop {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
try {
while (num <= 10000) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
// 每次循环都会sleep
Thread.sleep(10);
}
} catch (InterruptedException e) {
System.out.println("我被interrupt了");
e.printStackTrace();
}
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("interrupte后...还会执行吗" + i);
}
};
Thread t = new Thread(runnable);
t.start();
Thread.sleep(5000);
t.interrupt();
}
}
为什么线程感知到被interrupt信号,仍然执行下面的业务逻辑呢?这点现在还不明白。可能原因:异常被catch处理掉了,所以继续执行呢。如果把异常往上层调用去抛出,下面的代码就不会被执行了。
此处为什么可以去掉while (num <= 300 && !Thread.currentThread().isInterrupted())
中的!Thread.currentThread().isInterrupted()
?也不太明白。
中断不了的情况
package threadcoreknowledge.stopthread;
/**
* 如果while里面有try/catch,会导致中断失效
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
int num = 0;
while (num <= 10000) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
try {
// 每次循环都会sleep
Thread.sleep(10);
} catch (InterruptedException e) {
System.out.println("我被interrupt了");
e.printStackTrace();
}
}
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("interrupte后...还会执行吗" + i);
}
};
Thread t = new Thread(runnable);
t.start();
Thread.sleep(5000);
t.interrupt();
}
}
Thread-0线程在某一次循环中接收到主线程的中断请求,使用catch进行处理,不影响下一次循环,所以不会停止,会继续执行循环体。
即使把while (num <= 10000) {
改成while (num <= 10000 && !Thread.currentThread().isInterrupted())
也不会停止while (num <= 10000)
循环的执行。
原因是sleep会把interrupt标记位清除。
不应该屏蔽中断
先看一个错误的处理方式,把异常处理在底层。
package threadcoreknowledge.stopthread;
/**
* 最佳实践:catch了InterruptedException后优先选择:在方法签名中抛出异常
* 那么在run()中就会强制try/catch
*/
public class RightWayStopThreadInProduction implements Runnable {
@Override
public void run() {
while (true && !Thread.currentThread().isInterrupted()) {
System.out.println("go");
throwInMethod();
}
}
private void throwInMethod() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new RightWayStopThreadInProduction());
t.start();
Thread.sleep(500);
t.interrupt();
}
}
run 方法会一直执行。需要手动结束此java
进程。
run方法无法抛出checked Exception, 只能使用try/catch去处理。
package threadcoreknowledge.stopthread;
/**
* run方法无法抛出checked Exception, 只能使用try/catch去处理
*/
public class RunThrowException {
// 普通函数可以往上层抛出异常
public void aVoid () throws Exception{
throw new Exception();
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
// @Override
// public void run() throws Exception {
// // 因此Runnable接口的run方法签名是 public abstract void run();
// // 没有提及异常
// throw new Exception();
// }
@Override
public void run() {
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(runnable);
}
}
向上抛出异常。
package threadcoreknowledge.stopthread;
/**
* 最佳实践:catch了InterruptedException后优先选择:在方法签名中抛出异常
* 那么在run()中就会强制try/catch
*/
public class RightWayStopThreadInProduction implements Runnable {
@Override
public void run() {
while (true && !Thread.currentThread().isInterrupted()) {
System.out.println("go");
try {
throwInMethod();
} catch (InterruptedException e) {
// 此处可以做一些保存日志 停止程序的操作
System.out.println("保存日志");
e.printStackTrace();
// 跳出while循环
break;
}
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(1000);
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new RightWayStopThreadInProduction());
t.start();
Thread.sleep(500);
t.interrupt();
}
}
package threadcoreknowledge.stopthread;
/**
* 最佳实践2:在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态
* 以便于在后续的执行中,依然能够检查到刚才发生了中断
*/
public class RightWayStopThreadInProduction2 implements Runnable {
@Override
public void run() {
while (true) {
if(Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted, 程序运行结束");
break;
}
System.out.println("go");
reInterrupt();
}
}
private void reInterrupt() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new RightWayStopThreadInProduction2());
t.start();
Thread.sleep(500);
t.interrupt();
}
}
为什么使用interrupt来停止线程?有什么好处?
被中断的线程
拥有如何响应中断的权利,因为有些线程的某些代码是比较重要的,我们必须等待这些线程处理完之后再由他们主动终止,或者他们决定忽略中断请求。不应该鲁莽地使用stop
方法停止线程,而是使用interrupt
发出一个中断信号,让线程自己去处理,这样使得线程代码更加安全,也能完成清理工作。
volatile
设置boolean
标志位package threadcoreknowledge.stopthread;
/**
* 描述:错误的停止方法:使用stop()来停止线程,可能会导致线程运行一半突然停止,没有办法完成一个基本单位的操作
* (一个连队),会造成脏数据(有的连队多领取少领取装备)
*/
public class StopThread implements Runnable {
@Override
public void run() {
// 模拟指挥军队:一共有5个连队,每个连队有10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取
for (int i = 0; i < 5; i++) {
System.out.println("连队" + i+ "开始领取武器");
for (int j = 0; j < 10; j++) {
System.out.println(j);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("连队" + i+ "领取完毕");
}
}
public static void main(String[] args) {
Thread t = new Thread(new StopThread());
t.start();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.stop();
}
}
resume 、suspend在挂起的时候不会释放锁,可能导致死锁。
stop()方法会释放锁,但是由于它的停止操作比较粗鲁,因此被弃用。
volatile 方法能正常使用的场景,线程不会一直阻塞才可以正常停止。
package threadcoreknowledge.stopthread.volatiledemo;
/**
* 演示使用volatile的局限:part1
* 这种方式看似可行
*/
public class WrongWayVolatile implements Runnable {
// 可见性粗糙的理解:多个线程都可以看到它的真实值
// 在JMM那块再深入了解volatile
private volatile boolean canceled = false;
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
System.out.println(num + "是100的倍数");
}
num++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
WrongWayVolatile r = new WrongWayVolatile();
Thread t = new Thread(r);
t.start();
Thread.sleep(5000);
r.canceled = true;
}
}
线程不能正常停止的场景
package threadcoreknowledge.stopthread.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 演示用volatile的局限 part2
* 陷入阻塞时,volatile是无法停止线程的
* 此例中,生产者的生产速度很快,消费者消费速度很慢,
* 所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
*/
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
// 留出时间,让阻塞队列塞满
Thread.sleep(1000);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()){
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了");
// 消费者不需要消费了,因此生产者不要继续生产了
producer.canceled = true;
System.out.println(producer.canceled);
}
}
class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue storage;
public Producer (BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
// 生产者阻塞到这里了,所以程序一直运行,不结束
// 无法走到下一次while判断
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓库中了");
}
num++;
// 此处不能sleep,否则生产者不能生产足够的数据
// 生产者不阻塞,volatile boolean类型的变量可以被改变值
// 然后循环结束
// Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者停止运行");
}
}
}
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
对上面的生产者消费者问题的改进,使用interrupt停止线程。
package threadcoreknowledge.stopthread.volatiledemo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 使用中断来修复刚才的无尽等待问题
*/
public class WrongWayVolatileFixed {
public static void main(String[] args) throws InterruptedException {
WrongWayVolatileFixed body = new WrongWayVolatileFixed();
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = body.new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
// 留出时间,让阻塞队列塞满
Thread.sleep(1000);
Consumer consumer = body.new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消费了");
Thread.sleep(100);
}
System.out.println("消费者不需要更多数据了");
// 消费者不需要消费了,因此生产者不要继续生产了
producerThread.interrupt();
}
class Producer implements Runnable {
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
if (num % 100 == 0) {
// BlockingQueue take() put()方法能够响应中断
storage.put(num);
System.out.println(num + "是100的倍数,被放到仓库中了");
}
num++;
// 此处不能sleep,否则生产者不能生产足够的数据
// 生产者不阻塞,volatile boolean类型的变量可以被改变值
// 然后循环结束
// Thread.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生产者停止运行");
}
}
}
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
}
能够修正的原因在于:即使线程处于阻塞状态,interrupt依旧能够唤醒线程。
interrupt
方法调用interrupt0
方法,interrupt0
方法是一个native
方法,由c++
实现。可以在GitHub上找到其实现。
下面两个方法做一下对比:
// java.lang.Thread
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
// java.lang.Thread
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
区别在于是否清除标志位。
java.lang.Thread#interrupted
的目标对象是什么?
Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自哪个对象。
package threadcoreknowledge.stopthread;
/**
* 注意:Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自哪个对象。
*/
public class RightWayInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
}
}
});
// 启动线程
threadOne.start();
// 设置中断标志
threadOne.interrupt();
//
System.out.println(threadOne.isInterrupted());
System.out.println(threadOne.interrupted());
System.out.println(Thread.interrupted());
System.out.println(threadOne.isInterrupted());
threadOne.join();
System.out.println("Main thread is over.");
}
}
对于第1点,可以保证被停止线程的数据安全,让被停止线程自己决定是否停止以及什么时候停止
对于第2点,请求方发出请求信号,调用interrupt()方法;被停止方必须要循环控制条件或者适当的时侯检查停止信号;子方法要传递中断,向上抛出异常,或者在try/catch后恢复中断,这都是最佳实践。
不可中断的阻塞有socket io等。
很遗憾,没有通用的解决方案。
举个例子,特例
java.util.concurrent.locks.ReentrantLock#lock
无法响应中断,但是java.util.concurrent.locks.ReentrantLock#lockInterruptibly
可以响应中断。
有些I/O操作也可以响应中断,但是我目前还不知道。
线程的一生,6个状态,生命周期
new 状态:执行了Thread thread = new Thread();
, 但是还没有执行thread.start();
Runnable 状态:调用了thread.start()
后。Java的runnable对应操作系统中的ready和running状态。
Blocked 状态:当一个线程进入到synchronized修饰的代码块的时候,并且该锁已经被其他线程拿走了,此时线程是blocked状态。只有synchronized才和Blocked关联,其他的锁不会和Blocked关联。
package threadcoreknowledge.sixstates;
/**
* 展示线程的 NEW RUNNABLE TERMINATED 状态
* 即使是正在运行,也是Runnable状态,而不是Running.
*/
public class NewRunableTerminated implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new NewRunableTerminated());
// 打印出NEW的状态
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印出Runnable的状态,即使是正在运行的线程,也是Runnable, 而不是Running
System.out.println(thread.getState());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将会打印出TERMINATED状态
System.out.println(thread.getState());
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
}
package threadcoreknowledge.sixstates;
/**
* 展示 Blocked, Waiting, Timed Waiting
*/
public class BlockedWaitingTimedWaiting implements Runnable {
public static void main(String[] args) throws InterruptedException {
BlockedWaitingTimedWaiting runnable = new BlockedWaitingTimedWaiting();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
Thread.sleep(10);
// 打印出Timed Waiting状态,因为正在执行Thread.sleep(1000);
System.out.println(thread1.getState());
// 打印出Blocked状态,因为thread2想拿到syn()的锁却拿不到
System.out.println(thread2.getState());
Thread.sleep(1300);
// 打印出Waiting状态,因为执行了wait()
System.out.println(thread1.getState());
}
@Override
public void run() {
syn();
}
public synchronized void syn() {
try {
Thread.sleep(1000);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
回答状态转换图。
调用wait()进入阻塞阶段
在synchronized
里面使用wait()
, 然后线程进入阻塞状态。
直到以下4种情况之一发生时,才会被唤醒。
调用notify() 、notifyAll()
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 展示 wait 和 notify 的基本用法
* 1. 研究代码执行顺序
* 2. 证明wait释放锁
*/
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "开始执行了");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "获取到了锁,继续执行");
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + "调用了notify()");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
}
可能的执行结果:
Thread-0开始执行了
Thread-1调用了notify()
Thread-0获取到了锁,继续执行
此段代码说明了两点:
演示 notifyAll() 用法。
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。有两种选择,notify, notifyAll
*
* start先执行,不代表线程先启动
*/
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
WaitNotifyAll r = new WaitNotifyAll();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
System.out.println("Thread C notified.");
}
}
});
thread1.start();
thread2.start();
Thread.sleep(200);
thread3.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName() + " waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + " is waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
可能的执行结果:
Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
Thread C notified.
Thread-1 is waiting to end.
Thread-0 is waiting to end.
证明wait()
只释放当前对象的那把锁
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 证明wait()只释放当前的那把锁
*/
public class WaitNotifyReleaseOwnMonitor {
public static volatile Object resourceA = new Object();
public static volatile Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA gets resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA gets resourceB lock.");
try {
System.out.println("ThreadA releases resourceA lock.");
resourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadB gets resourceA lock.");
System.out.println("ThreadB tries to get resourceB lock.");
synchronized (resourceB) {
System.out.println("ThreadB gets resourceB lock.");
}
}
}
});
thread1.start();
thread2.start();
}
}
可能的执行结果:
ThreadA gets resourceA lock.
ThreadA gets resourceB lock.
ThreadA releases resourceA lock.
ThreadB gets resourceA lock.
ThreadB tries to get resourceB lock.
JVM
决定。final native
修饰的。package threadcoreknowledge.threadobjectclasscommonmethods;
import java.util.Date;
import java.util.LinkedList;
/**
* 用wait/notify实现生产者消费者模式
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage storage = new EventStorage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
this.maxSize = 10;
this.storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == 10) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("仓库里有了" + storage.size() + "个产品");
// 通知消费者消费
notify();
}
public synchronized void take() {
// 如果队列里取空了,则等待
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + ", 现在仓库还剩下" + storage.size());
// 通知生产者继续生产
notify();
}
}
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 使用synchronized控制两个线程交替打印0-100的奇偶数
*/
public class WaitNotifyPrintOddEvenSync {
private static int count;
public static final Object lock = new Object();
private static int maxCount = 100;
// 新建两个线程
// 一个只处理偶数,一个只处理奇数
// 使用synchronized处理
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (count <= maxCount) {
synchronized (lock) {
if (count <= maxCount && (count & 1) == 0) {
System.out.println(Thread.currentThread().getName()
+ " : "
+ count);
count++;
}
}
}
}
}, "偶数").start();
new Thread(new Runnable() {
@Override
public void run() {
while (count <= maxCount) {
synchronized (lock) {
if (count <= maxCount && (count & 1) == 1) {
System.out.println(Thread.currentThread().getName()
+ " : "
+ count);
count++;
}
}
}
}
}, "奇数").start();
}
}
这种方法存在线程的空转等待,这是一种缺陷。
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 使用wait/notify控制两个线程交替打印0-100的奇偶数
* 1. 拿到锁就打印
* 2. 打印完就,唤醒其他线程,自己就休眠
*/
public class WaitNotifyPrintOddEvenWait {
private static int count;
public static final Object lock = new Object();
private static int maxCount = 100;
public static void main(String[] args) throws InterruptedException {
new Thread(new TurningRunner(), "偶数").start();
Thread.sleep(10);
new Thread(new TurningRunner(), "奇数").start();
}
static class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= maxCount) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " : " + count);
count++;
// 唤醒其他线程
lock.notify();
// 如果还要继续打印,则休眠等待
if (count <= maxCount) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
用程序实现两个线程交替打印0-100的奇偶数
上面已经作答。
手写生产者消费者设计模式
为什么
wait()
需要在同步代码块中使用,而sleep()
不需要
可以避免死锁,防止notify执行完了, wait没有与之相对应的notify来唤醒自己。
而sleep是针对自己这个线程的,不与其他线程对话,所以不需要放在同步代码块中。
为什么线程通信的方法wait() notify() notifyAll()被定义在Object类里?而sleep()定义在Thread类里?
在Java中,wait notify notifyAll是锁级别的操作,而锁是属于某一个对象的,每个对象的对象头
中都有几位用于保存锁的状态的。所以,锁是绑定在某一个对象中,而不是某一个线程中。
每个线程可以持有多把锁,如果把锁放到Thread类中,就无法实现灵活的控制逻辑了。
wait方法是属于Object对象的,那么调用Thread.wait()会怎么样?
线程退出的时候,会自动执行notify()代码,这会给预想的流程带来干扰,不建议使用Thread实例作为锁对象。
如何选择用notify还是notifyAll ?
需要唤醒一个线程就用notify, 需要唤醒多个线程就用notifyAll
notifyAll之后,所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?
线程回到ready状态,等待获得锁,然后运行。
用suspend() 和 resume() 可以来阻塞线程吗?为什么?
它们都是由于安全问题被弃用的API了。推荐使用wait notify去实现同样的休眠、恢复功能。
作用:我只想让线程在预期的时间执行,其他时候不要占用CPU资源。
sleep方法不释放锁,包括synchronized和Lock锁。这一点和wait()
不一样。
代码演示:
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁。
*/
public class SleepDontReleaseMonitor implements Runnable {
public static void main(String[] args) {
SleepDontReleaseMonitor r = new SleepDontReleaseMonitor();
new Thread(r).start();
new Thread(r).start();
}
@Override
public void run() {
sync();
}
public synchronized void sync() {
System.out.println("线程" + Thread.currentThread().getName() + "获得了monitor");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "释放了monitor");
}
}
代码演示2:sleep 不释放 Lock (Lock需要手动释放)
package threadcoreknowledge.threadobjectclasscommonmethods;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 演示 sleep 不释放 Lock (Lock需要手动释放)
*/
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
try {
Thread.sleep(3000);
System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseLock runnable = new SleepDontReleaseLock();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
两种sleep API
package threadcoreknowledge.threadobjectclasscommonmethods;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 每一秒钟输出当前时间,被中断,然后观察
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
*/
public class SleepInterrupted implements Runnable {
public static void main(String[] args) throws InterruptedException {
SleepInterrupted runnable = new SleepInterrupted();
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(6500);
thread.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中断了");
e.printStackTrace();
}
}
}
}
join的作用:
因为新的线程加入了我们,所以我们要等它执行完再出发。
用法:main等待thread1执行完毕,注意谁等谁。主
等待子
,主线程等待子线程。
join的普通用法
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 演示join,注意语句的输出顺序,会变化
*/
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
});
thread1.start();
thread2.start();
System.out.println("开始等待子线程运行完毕");
thread1.join();
thread2.join();
System.out.println("所有子线程执行完毕");
}
}
join遇到中断代码演示
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 演示join期间被中断的效果
*/
public class JoinInterrupt {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished.");
} catch (InterruptedException e) {
System.out.println("Thread1 interrupted exception.");
}
}
});
thread1.start();
System.out.println("主线程等待子线程执行完毕");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主线程中断了");
// e.printStackTrace();
// 传递中断
thread1.interrupt();
}
System.out.println("子线程已经运行完毕");
}
}
在join期间,线程到底是什么状态?:Waiting
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 先join再mainThread.getState()
* <p>
* 通过debugger看线程join前后状态的对比
*/
public class JoinThreadState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(mainThread.getState());
System.out.println("Thread-0 运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("等待子线程运行完毕");
thread.join();
System.out.println("子线程运行完毕");
}
}
join的源码
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join的源码中没有notify的操作,那么子线程结束之后,是怎么通知/唤醒主线程的呢?
线程执行完毕退出的时候,执行了notifyall操作。
等价的Java代码,实现join这个功能。
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 通过讲解join的原理,讲解join的代替写法
*/
public class JoinPrinciple {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
});
thread.start();
System.out.println("等待子线程运行完毕");
// thread.join();
synchronized (thread) {
thread.wait();
}
System.out.println("子线程运行完毕");
}
}
但是
synchronized (thread) {
thread.wait();
}
换成
Object lock = new Object();
synchronized (lock) {
lock.wait();
}
并不等价。不使用thread变量作为锁,会导致主线程一直等待。
join面试题:
WAITING状态。
作用:释放我的CPU时间片,释放了时间片,线程仍处于runnable状态。
定位:JVM不保证遵循。
yield和sleep的区别:是否随时可能再次被调度。yield是可以随时可能再次被调度的。
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* 演示打印main, Thread-0, Thread-1
*/
public class CurrentThread implements Runnable {
public static void main(String[] args) {
new CurrentThread().run();
new Thread(new CurrentThread()).start();
new Thread(new CurrentThread()).start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Thread.currentThread()
获取当前执行线程的引用。
什么时候我们需要设置守护线程?
通常情况下不需要设置。JVM提供的守护线程足够我们使用。
我们应该如何应用线程优先级来帮助程序运行?有哪些禁忌?
因为不同操作系统对于优先级的映射和调度都不一样,所以不应该使用优先级来帮助程序运行。
不同操作系统如何处理优先级问题?
不同操作系统对于优先级的映射和调度都不一样
package threadcoreknowledge.threadobjectclasscommonmethods;
/**
* ID是从1开始的,JVM运行起来后,我们自己创建的线程的ID早已不是2
*/
public class Id {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println("主线程的ID: " + Thread.currentThread().getId());
System.out.println("子线程的ID: " + thread.getId());
}
}
可能的执行结果:
主线程的ID: 1
子线程的ID: 11
JVM
默认创建、启动了一些守护线程,完成特定的功能。
作用:给用户线程提供服务。
3个特性:
守护线程和普通线程的区别:
整体上无太大差别,都是执行代码的。用户线程执行我们的业务逻辑,守护线程服务于用户线程。
唯一区别在于JVM的离开。如果线程是一个用户线程,那么它会影响到JVM的退出,而守护线程并不会影响。
线程优先级:
10个级别,默认为5.
程序设计不应该依赖于优先级
自定义UncaughtExceptionHandler
package threadcoreknowledge.uncaughtexception;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* 自己编写的 UncaughtExceptionHandler
*/
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private String name;
public MyUncaughtExceptionHandler(String name) {
this.name = name;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.WARNING, "线程异常,终止啦" + t.getName(), e);
System.out.println(name + "捕获了异常" + t.getName() + "异常" + e);
}
}
使用UncaughtExceptionHandler
package threadcoreknowledge.uncaughtexception;
/**
* 使用自己定义的 UncaughtExceptionHandler
*/
public class UseOwnUncaughtExceptionHandler implements Runnable {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("自定义捕获器"));
new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-1").start();
new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-2").start();
new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-3").start();
new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-4").start();
}
@Override
public void run() {
throw new RuntimeException();
}
}
考考你:
一共有哪几类线程安全问题?3种。
a++
哪些场景需要额外注意线程安全问题?
什么是多线程带来的上下文切换?
什么是线程安全?
《Java Concurrency In Practice》的作者Brian Goetz对“线程安全”有一个比较恰当的定义:“当多个线程访问同一个对象时,如果不需要考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的。”
白话翻译:
不管业务中遇到怎样多个线程访问某对象或者某方法的情况,而在编写这个业务逻辑的时候,都不需要做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。
什么情况下会出现线程安全问题?
package background;
/**
* 第一种:运行结果出错
* 演示计数不准确(减少),找出具体出错的位置
*/
public class MultiThreadsError implements Runnable {
int index = 0;
static MultiThreadsError instance = new MultiThreadsError();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(instance.index);
}
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
for (int i = 0; i < 10000; i++) {
index++;
}
}
}
原因是index++
不是原子操作。
a++
具体消失在哪里?请看代码。来自悟空老师。我目前还看不懂。
package background;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 第一种:运行结果出错
* 演示计数不准确(减少),找出具体出错的位置
*/
public class MultiThreadsError implements Runnable {
int index = 0;
static MultiThreadsError instance = new MultiThreadsError();
final boolean[] marked = new boolean[1000000];
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongIndex = new AtomicInteger();
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("表面上结果是:" + instance.index);
System.out.println("真正运行的次数:" + realIndex.get());
System.out.println("错误次数:" + wrongIndex.get());
}
@Override
public void run() {
// while (index < 10000) {
// index++;
// }
marked[0] = true;
for (int i = 0; i < 10000; i++) {
try {
cyclicBarrier2.reset();
cyclicBarrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
index++;
try {
cyclicBarrier1.reset();
cyclicBarrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
realIndex.incrementAndGet();
synchronized (instance) {
if (marked[index] && marked[index - 1]) {
System.out.println("发生错误" + index);
wrongIndex.incrementAndGet();
}
marked[index] = true;
}
}
}
}
package background;
/**
* 第二种线程安全问题,演示死锁
*/
public class MultiThreadError2 implements Runnable {
int flag = 1;
// 锁对象 o1 o2 是两个线程共享的
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MultiThreadError2 r1 = new MultiThreadError2();
MultiThreadError2 r2 = new MultiThreadError2();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch(InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
}
对象逸出情况1:方法返回一个private对象
package background;
import java.util.HashMap;
import java.util.Map;
public class MultiThreadsError3 {
private Map<String, String> states;
public MultiThreadsError3() {
states = new HashMap<>();
states.put("1", "周一");
states.put("2", "周二");
states.put("3", "周三");
states.put("4", "周四");
}
public Map<String, String> getStates() {
return states;
}
public static void main(String[] args) {
MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
Map<String ,String> map = multiThreadsError3.getStates();
System.out.println(map.get("1"));
map.remove("1");
System.out.println(map.get("1"));
}
}
对象逸出情况2:构造函数初始化未完毕,就this赋值。
package background;
/**
* 初始化未完毕,就this赋值
*/
public class MultiThreadsError4 {
static Point point;
public static void main(String[] args) throws InterruptedException {
new PointMaker().start();
Thread.sleep(10); // 休眠10ms, 打印1,0
// Thread.sleep(100); // 休眠100ms, 打印1,1
if (point != null) {
System.out.println(point);
}
}
}
class Point {
private final int x, y;
public Point(int x, int y) throws InterruptedException {
this.x = x;
MultiThreadsError4.point = this;
Thread.sleep(100);
this.y = y;
}
@Override
public String toString() {
return x + "," + y;
}
}
class PointMaker extends Thread {
@Override
public void run() {
try {
new Point(1,1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
对象逸出情况3:
package background;
import java.util.HashMap;
import java.util.Map;
/**
* 构造函数中运行线程
*/
public class MultiThreadsError6 {
private Map<String, String> states;
public MultiThreadsError6() {
new Thread(new Runnable() {
@Override
public void run() {
states = new HashMap<>();
states.put("1", "周一");
states.put("2", "周二");
states.put("3", "周三");
states.put("4", "周四");
}
}).start();
}
public Map<String, String> getStates() {
return states;
}
public static void main(String[] args) throws InterruptedException {
MultiThreadsError6 multiThreadsError6 = new MultiThreadsError6();
// Thread.sleep(10);
System.out.println(multiThreadsError6.getStates().get("1"));
}
}
返回”副本“。
package background;
import java.util.HashMap;
import java.util.Map;
public class MultiThreadsError3 {
private Map<String, String> states;
public MultiThreadsError3() {
states = new HashMap<>();
states.put("1", "周一");
states.put("2", "周二");
states.put("3", "周三");
states.put("4", "周四");
}
public Map<String, String> getStates() {
return states;
}
public Map<String, String> getStatesImproved() {
return new HashMap<>(states);
}
public static void main(String[] args) {
MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
// Map<String ,String> map = multiThreadsError3.getStates();
// System.out.println(map.get("1"));
// map.remove("1");
// System.out.println(map.get("1"));
System.out.println(multiThreadsError3.getStatesImproved().get("1"));
multiThreadsError3.getStatesImproved().remove("1");
System.out.println(multiThreadsError3.getStatesImproved().get("1"));
}
}
执行结果:
周一
周一
用工厂模式修复监听初始化问题:
package background;
/**
* 观察者模式 对象隐式逸出:注册监听事件
*
* 用工厂模式修复初始化问题
*/
public class MultiThreadsError7 {
int count;
private EventListener listener;
private MultiThreadsError7(MySource source) {
listener = new EventListener() {
@Override
public void onEvent(Event e) {
System.out.println("\n我得到的数字是" + count);
}
};
for (int i = 0; i < 1000; i++) {
System.out.print(i + " ");
}
System.out.println();
count = 100;
}
public static MultiThreadsError7 getInstance(MySource source) {
MultiThreadsError7 safeListener = new MultiThreadsError7(source);
// 完成所有准备工作再注册
source.registerListener(safeListener.listener);
return safeListener;
}
public static void main(String[] args) {
MySource mySource = new MySource();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySource.eventCome(new Event() {});
}
}).start();
MultiThreadsError7 multiThreadsError5 = MultiThreadsError7.getInstance(mySource);
}
static class MySource {
private EventListener listener;
void registerListener(EventListener listener) {
this.listener = listener;
}
void eventCome(Event e) {
if (listener != null) {
listener.onEvent(e);
} else {
System.out.println("还未初始化完毕");
}
}
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
性能问题有哪些体现?什么是性能问题?
接口响应慢。
为什么多线程会带来性能问题?
调度:上下文切换
协作:内存同步
上下文主要是和一些寄存器相关的,一次上下文切换主要包含以下活动:挂起一个线程,保存现场,把当前线程的状态存储在内存的某处,保存程序计数器等。
缓存开销:缓存失效。局部性原理。
何时会导致密集的上下文切换?抢锁、I/O
协作:内存同步。
关键词区分:JVM内存结构、Java内存模型、Java对象模型
过程:Java源代码->JVM ByteCode->具体CPU指令
需要JVM对内存模型定制一个规范,使得在不同的硬件平台字节码的运行结果是一致的。
JVM内存结构、Java内存模型、Java对象模型
JVM内存结构:和Java虚拟机的运行时区域有关。
Java内存模型:和Java的并发编程有关。
Java对象模型:和Java对象在虚拟机中的表现形式有关。
为什么需要JMM?
是规范。
是工具类和关键字的原理。
最重要的3点内容:重排序、可见性、原子性。
是规范:
C语言不存在内存模型的概念。这就导致了C语言依赖处理器,不同处理器结果不一样。可能一段代码在一种处理器上运行正常,而在另一种处理上运行不正常,无法保证并发安全。
因此需要一个标准,让多线程运行的结果可预期。
因此,JMM是规范。
是一组规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序。
如果没有这样的一个JMM内存模型来规范,那么很可能经过了不同JVM的不同规则的重排序之后,导致不同的虚拟机上运行的结果不一样,那是很大的问题。
是工具类和关键字的原理:
volatile synchronized Lock等的原理都是JMM。
如果没有JMM,那么就需要我们自己指定什么时候用内存栅栏等,那是相当麻烦的,幸好有了JMM,让我们只需要使用同步工具和关键字就可以开发多线程并发程序。
最重要的3点内容:重排序、可见性、原子性。
演示重排序的现象:
package jmm;
/**
* 演示重排序的现象
* “直到达到某个条件才停止”,测试小概率事件
*/
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
Thread one = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
one.start();
two.start();
one.join();
two.join();
System.out.println("x = " + x + ", y = " + y);
}
}
期待出现x=1,y=1的结果
package jmm;
import java.util.concurrent.CountDownLatch;
/**
* 演示重排序的现象
* “直到达到某个条件才停止”,测试小概率事件
*/
public class OutOfOrderExecution {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (; ; ) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
CountDownLatch countDownLatch = new CountDownLatch(1);
Thread one = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
one.start();
two.start();
countDownLatch.countDown();
one.join();
two.join();
String result = "第" + i + "次" + " (x = " + x + ", y = " + y + ")";
if (x == 1 && y == 1) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}
可能出现x=0,y=0的情况,产生了重排序。
编译器优化:包括JVM, JIT编译器等
CPU指令重排:就算编译器不发生重排,CPU也可能对指令进行重排。
内存的”重排序“:线程A的修改线程B却看不到,引出可见性问题。
package jmm;
/**
* 演示可见性带来的问题
*/
public class FieldVisibility {
int a = 1;
int b = 2;
private void change() {
a = 3;
b = a;
}
private void print() {
System.out.println("b=" + b + ",a=" + a);
}
public static void main(String[] args) {
while (true) {
FieldVisibility test = new FieldVisibility();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
这段代码可能的执行结果:
b=2,a=1
b=3,a=3
b=2,a=3
还有一种不常见的结果是:
b=3,a=1
package jmm;
/**
* 演示可见性带来的问题
*/
public class FieldVisibility {
volatile int a = 1;
volatile int b = 2;
private void change() {
a = 3;
b = a;
}
private void print() {
System.out.println("b=" + b + ",a=" + a);
}
public static void main(String[] args) {
while (true) {
FieldVisibility test = new FieldVisibility();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
使用volatile保证可见性。
不适用的场景:a++
package jmm;
import java.util.concurrent.atomic.AtomicInteger;
public class NoVolatile implements Runnable {
volatile int a;
AtomicInteger realA = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
realA.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
NoVolatile r = new NoVolatile();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(r.a);
System.out.println(r.realA.get());
}
}
package jmm;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 适用volatile的情况: 赋值操作
*/
public class UseVolatile1 implements Runnable {
volatile boolean done = false;
AtomicInteger realA = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
setDone();
realA.incrementAndGet();
}
}
private void setDone() {
done = true;
}
public static void main(String[] args) throws InterruptedException {
UseVolatile1 r = new UseVolatile1();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(r.done);
System.out.println(r.realA.get());
}
}
done=true;
语句不受之前状态的影响。
运行结果一定是
true
20000
下面的例子与之形成对比。
package jmm;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 不适用volatile的情况
*/
public class NoVolatile1 implements Runnable {
volatile boolean done = false;
AtomicInteger realA = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
flipDone();
realA.incrementAndGet();
}
}
private void flipDone() {
done = !done;
}
public static void main(String[] args) throws InterruptedException {
NoVolatile1 r = new NoVolatile1();
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(r.done);
System.out.println(r.realA.get());
}
}
运行结果可能为:
false
20000
也可能为:
true
20000
package jmm;
/**
* 演示可见性带来的问题
*/
public class FieldVisibility {
int a = 1;
int abc = 1;
int abcd = 1;
volatile int b = 2;
private void change() {
a = 3;
abc = 8;
abcd = 80;
b = 0;
}
private void print() {
if (b == 0) {
System.out.println("b=" + b + ",a=" + a + ",abc="+ abc + ",abcd=" + abcd);
}
}
public static void main(String[] args) {
while (true) {
FieldVisibility test = new FieldVisibility();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
此例中,变量b
作为了触发器。
package jmm;
import java.util.concurrent.CountDownLatch;
/**
* 演示重排序的现象
* “直到达到某个条件才停止”,测试小概率事件
*/
public class OutOfOrderExecution {
private static volatile int x = 0, y = 0;
private static volatile int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
for (; ; ) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
CountDownLatch countDownLatch = new CountDownLatch(1);
Thread one = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 1;
x = b;
}
});
Thread two = new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
b = 1;
y = a;
}
});
one.start();
two.start();
countDownLatch.countDown();
one.join();
two.join();
String result = "第" + i + "次" + " (x = " + x + ", y = " + y + ")";
if (x == 0 && y == 0) {
System.out.println(result);
break;
} else {
System.out.println(result);
}
}
}
}
加了volatile就会禁止指令重排序。执行结果就不会出现x = 0, y = 0
的情况。
使用synchronized保证可见性。
什么是原子性?
Java中的原子操作有哪些?
long double的原子性问题?
原子操作 + 原子操作 != 原子操作
java.util.concurrent.Atomic.*
包中所有类的原子操作package singleton;
/**
* 饿汉式(静态常量)(可用)
* 类装载的时候就完成了实例化
*/
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
package singleton;
/**
* 饿汉式(静态代码块)(可用)
* 类装载的时候就完成了实例化
*/
public class Singleton2 {
private static final Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public static Singleton2 getInstance() {
return INSTANCE;
}
}
package singleton;
/**
* 懒汉式(线程不安全)(不可用)
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
package singleton;
/**
* 懒汉式(线程安全,使用同步方法)(不推荐使用)
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {
}
public synchronized static Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
package singleton;
/**
* 懒汉式(线程不安全,使用同步代码块)(不可用)
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (instance == null) {
synchronized (Singleton5.class) {
instance = new Singleton5();
}
}
return instance;
}
}
package singleton;
/**
* 懒汉式(双重检查)(推荐面试使用)
*/
public class Singleton6 {
private volatile static Singleton6 instance;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (instance == null) {
synchronized (Singleton6.class) {
// 此处可以用happens-before解释,
// 第二个执行到此的线程一定可以看到第一个执行到此的线程所做的改变
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
双重检测锁方式的优点:线程安全;延迟加载;效率较高。
为什么要double-check?
为什么要用volatile?
package singleton;
/**
* 静态内部类(可用),属于懒汉的行为方式
*/
public class Singleton7 {
private Singleton7() {
}
private static class SingletonInstance {
// 既线程安全,又懒加载
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
生产实践中最佳的写法
package singleton;
import org.omg.PortableInterceptor.INACTIVE;
/**
* 枚举方式的单例
*/
public enum Singleton8 {
INSTANCE;
public void whatever() {
}
}
class Test {
public static void main(String[] args) {
Singleton8.INSTANCE.whatever();
}
}
枚举是一种特殊的类。
枚举是一种final Class,继承自java.lang.Enum
类。
枚举也能做到懒加载。
为什么双重检查模式要用volatile?
讲一讲什么是Java内存模型?
什么是内存可见性?
讲一下操作系统的内存结构图以及JMM
缺一不可,逐个分析之前的例子。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。