前言
什么是JMM主内存和本地内存(了解)
Java作为高级语言,屏蔽了这些底层细节,用JMM定义了一套读写内存数据的规范,虽然我们不再需要关心一级缓存和二级缓存的问题,但是,JMM抽象了主内存和本地内存的概念。这里说的本地内存并不是真的是一块给每个线程分配的内存,而是JMM的一个抽象,是JMM的一个抽象,是二级缓存等的抽象。
一、多线程的内存模型(JMM)
Java多线程内存模型(JMM)对于刚入门的跟我一样学习多线程的小白先初步了解一下三大特性。想要了解更多可到网上搜一下学习。
1.1 原子性
一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
1.2 可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够“立即”看得到修改的值。
1.3 有序性
主要是用于线程间的通讯,比如join、wait等操作。
1.4 认识多线程基础的核心知识
多线程基础的核心知识有实现多线程有几种方式、启动线程的正确方法、停着线程、线程的状态等等。
二、 实现多线程的方法有几种方式
对于网上一搜就会有许多实现多线程的方式,但对于官方的说法正确的就只有两种。
2.1 实现方式
方法一实现Runnable接口,重写run()函数
推荐:Runnable接口
(1). 从代码架构角度
(2). 新建线程的损耗
(3). Java 不支持双继承
通过源码查看,方法一最终调用target.run();
public class RunnableStyle implements Runnable{
public static void main(String args[]){
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable實現線程..");
}
}
方法二继承Thread类,重写run()函数,运行start()方法
public class ThreadStyle extends Thread{
@Override
public void run() {
System.out.println("Thread 方式創建線程");
}
public static void main(String args[]){
new ThreadStyle().start();
}
}
2.2 同时使用Runnable和Thread
执行第一个run的时候没什么问题,但最后执行到第二个run的时候并调用start()方法,重写了run的Thread线程。
public class BothRunnableAndThread {
public static void main(String args[]){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(" i come Runnable ....");
}
}){
@Override
public void run() {
System.out.println(" i come thread ....");
}
}.start();
}
}
结果: i come thread ....
2.3 对于创建线程的方式
- 通常我們可以分為兩類,Oracle官方也是這麼說
- 準確的講,創建線程只有一種方式那就是構造Thread類,而實現線程的執行單元有兩種方式
(1). 方法一:實現Runnbale接口的run方法,然後把Runnbale實例傳給Thread類
(2). 方法二: 重寫Thread的run方法(繼承Thread類)
3. 典型错误观点
(1). 線程池創建線程也算是一種新建線程的方式
(2).通過Callable和FutureTask創建方式 ,也算是一種新建線程的方式
(3). 無返回值是實現runnbale接口,有返回值是实现callable接口,所以callable是新的实现线程的方式
(4).定时器
(5).匿名内部类
(6).Lambda表达式
2.4 線程池創建線程也算是一種新建線程的方式
public class ThreadPoolDemo {
public static void main(String args[]){
ExecutorService executorService =
Executors.newCachedThreadPool();
for (int i = 0 ; i< 1000 ; i++){
executorService.submit(
new Tarsk(){}
);
}
}
}
class Tarsk implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("name:"+Thread.currentThread().getName());
}
}
2.5 定时器也是算是一种
public class DemoTimmerTask {
public static void main(String args[]){
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("name:"+Thread.currentThread().getName());
}
}, 1000,1000 );
}
}
2.6 匿名内部类
public class AnonymousInnerClassDemo {
public static void main(String args[]){
new Thread(){
@Override
public void run() {
System.out.println("name"+Thread.currentThread().getName());
}
}.start();
new Thread(new Runnable() {
public void run() {
System.out.println("name"+Thread.currentThread().getName());
}
}).start();
}
}
2.7 Lambda表达式
public class Lamba {
public static void main(String args[]){
new Thread(()-> System.out.println("name:"+Thread.currentThread().getName())).start();
}
}
三、启动线程的正确和错误的方法
3.1 start()方法和run()的比较
输出结果的:一个是main,一个Thread-0,
main是调用runnable.run(),带来的效果有偏差
Thread-0是调用 new Thread(runnable).start();实现的效果没有用偏差
代码演示:
/**
* @Desc: 对比start和run两种启动线程的方式
*/
public class StartAndRunMethod {
public static void main(String args[]){
Runnable runnable = () ->{
System.out.println("name:"+Thread.currentThread().getName());
};
runnable.run();
new Thread(runnable).start();
}
}
结果:
name:main
name:Thread-0
3.2 start()方法原理阅读
1.start()方法含义
(1).启动新线程
(2).准备工作
(3).不能重复start();方法
代码演示执行两次start方法:
public class CantStartTwice {
public static void main(String args[]){
Thread thread = new Thread();
thread.start();
thread.start();
}
}
结果:
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at threadcoreknowledge.startthread.CantStartTwice.main(CantStartTwice.java:46)
2.start()源码解析
Thread最初始是为0,步骤如下
(1).一开始 启动新线程检查线程状态,
进入Thread.start()
if (threadStatus != 0)
throw new IllegalThreadStateException();
(2). 加入线程租
group.add(this);
(3). 调用star0()
start0();
started = true;
(4). 查看Thread方法下完整的start()方法
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
3.3 run()方法原理阅读
(1).源码解析
Thread类的run()方法
public void run() {
if (target != null) {
target.run();
}
(2). 两种情况
第一种:继承Thread类重写Thread run方法
public class ThreadStyle extends Thread{
@Override
public void run() {
System.out.println("Thread 方式創建線程");
}
public static void main(String args[]){
new ThreadStyle().start();
}
}
第二种:传入target对象
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
3.4 总结:启动线程常见问题
(1). 一个线程两次调用start()方法会出现一个情况 为什么
答:抛出一个异常,在start方法最开始的状态去检查,如果不符合规定就会抛出异常
线程的六个状态: NEW 、Runnbale 、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
(2). 既然start()方法会调用run方法,为什么我们会选择调用start()方法,而不是直接调用run方法呢
调用start方法才是真正开启一个线程,run只是普通的方法
四、停止线程的正确方法(重要、难点)
4.1 如何正确停着线程
原理介绍:使用interrupt来通知,而不是强制
4.2.1. try、catch 使用:interrupt
(1).如果while里面放 try/catch,会导致中断失效,一直执行下去,只能自己手动关闭程序
public class CantInterrupt {
public static void main(String args[]) throws InterruptedException {
Runnable runnable = () ->{
int num = 0 ;
while (num <= 1000){
if (num % 100 == 0){
System.out.println(num+"是100的倍数");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
结果:
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:20)
at java.lang.Thread.run(Thread.java:748)
500是100的倍数
600是100的倍数
(2). 实际开发中的两种最佳实践
方式一:优先选择:传递中断
在catch了InterruptedExcetion之后的优先选择:在方法签名中抛出异常,那么在run()就会强知try、catch。
public class RightWayStopThreadInProd implements Runnable{
@Override
public void run() {
while (true && !Thread.currentThread().isInterrupted()){
System.out.println("start");
try {
throwInMethod();
} catch (InterruptedException e) {
// 保存日志、停止程序
System.out.println("保存日志操作");
e.printStackTrace();
}
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(2000);
}
public static void main(String args[]) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
方式二:不想或无法传递:恢复中断
catch子语句中调用Thread。currentThread().interrupt()来恢复设置中断状态,
以便于在后续的执行中,依然能够检查到刚才发生了中断。
public class RightWayStopThreadProd2 implements Runnable{
@Override
public void run() {
while (true){
if (Thread.currentThread().isInterrupted()){
System.out.println("Interrupted,程序运行结束");
break;
}
System.out.println("go");
reInterrupt();
// 保存日志、停止程序
System.out.println("保存日志");
}
}
private void reInterrupt() {
try{
Thread.sleep(2000);
}catch (InterruptedException e){
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String args[]) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadProd2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
结果:
go
保存日志
java.lang.InterruptedException: sleep interrupted
Interrupted,程序运行结束
at java.lang.Thread.sleep(Native Method)
4.2.2.响应中断的的方法总列表
下列都是响应中断方法的一些方法
* 1. ◆Object.wait(/ wait( long)/ wait( long, int)
* 2. ◆Thread. sleep( long) /sleep(long,int)
* 3. ◆Thread. join0/ join( long)/join(Jong, int)
* 4. ◆java. util.concurrent. BlockingQueue.take0/put( E)
* 5. ◆java. util.concurrent.locks.Lock.lockInterruptibly()
* 6. ◆java. util. concurrent. CountDownLatch. await(
* 7. ◆java. util. concurrent. CyclicBarrier. awaitO
* 8. ◆java. util. concurrent. Exchanger, exchangeM)
* 9. ◆java.nio.channels.InterruptibleChannel相关方法
* 10. ◆java.nio.channels.Selector的相关方法
4.2.3. 正确停止带来的好处
1.正确的停止方法:interrupt
(1).通常线程会在什么情况下停着普通情况
run方法内没有sleep或wait方法时,
使用isInterrupted、interrupt停止线程
public class RightWayStopThreadWithoutSleep implements Runnable{
@Override
public void run() {
int num = 0 ;
// 需要添加isInterrupted去标识中断
while (!Thread.currentThread().isInterrupted() &&
num <= Integer.MAX_VALUE / 2) {
if (num % 1000 == 0 ){
System.out.println(num+"是10000的倍数");
}
num++;
}
System.out.println("任务运行结束");
}
public static void main(String args[]){
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
try {
Thread.sleep(1000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
.........
(2).线程可能被阻塞
带有sleep的中断线程的写法,由于调用Thread.sleep()阻塞过程中,再用interrupt也可以解决阻塞的状态
public class RightWayStopThreadSleep {
public static void main(String args[]) throws InterruptedException {
Runnable runnable = () ->{
int num = 0 ;
while ( num <=300 && !Thread.currentThread().isInterrupted()){
if (num % 100 == 0){
System.out.println(num+"是100的倍数");
}
num ++;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
结果:
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcoreknowledge.stopthreads.RightWayStopThreadSleep.lambda$main$0(RightWayStopThreadSleep.java:20)
at java.lang.Thread.run(Thread.java:748)
(3).如果线程在每次迭代后都阻塞
如果在执行过程中,每次循环都会调用sleep或者wait等方法,那么不需要每次迭代都检查是否中断。因为在sleep类的方法中会帮我们响应中断。
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 ++;
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
结果:
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at threadcoreknowledge.stopthreads.RightWayStopThreadSleep.lambda$main$0(RightWayStopThreadSleep.java:20)
at java.lang.Thread.run(Thread.java:748)
4.3 停止线程的错误方法
4.3.1. 被弃用的stop,suspend和resume
错误的停止方法: 用stop ()来停止线程,会导致线程运行一班突然停止, 没办法完成一个基本单位的操作(一个连队),会造成脏数据(有的连队多领取少领取装备)。下拉操作会导致后面领取装备的时候停止,从而程序没有继续执行下去。
public class ErroStopThread implements Runnable{
@Override
public void run() {
//模拟指挥军队:一共有5个连队,每个连队100人,以连队为单位,发放武器弹药,
// 叫到号的士兵前去领取
for (int i = 0 ; i < 5; i++){
System.out.println("连队"+i+"开始领取武器");
for (int j = 0 ; j < 10 ; j++){
System.out.println("j:"+j);
try {
Thread.sleep(50);
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("连队"+i+"已经领取完毕");
}
};
public static void main(String args[]){
Thread thread = new Thread(new ErroStopThread());
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
}
}
结果:
连队1开始领取武器
j:0
j:1
j:2
j:3
j:4
j:5
j:6
j:7
j:8
j:9
4.3.2. 用volatile设置boolean标识位
volatile修饰的boolean它是可见性行的,对于多个线程都可共享的值。可有参考网上学习JMM,当然内存模型和内存结构是不一样。
下面的执行结果会直接等待5秒就结束了,表面看是去让程序中断是没有什么问题,但是这样的方法是有足够的健壮性,就是适应当前的程序而不适应另外的一个程序。
下面执行的是个没有问题的程序
public class WrongWayVolatile implements Runnable {
private volatile boolean cancled = false;
@Override
public void run() {
int num = 0;
try {
while(num <= 10000 && !cancled){
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 wrongWayVolatile =
new WrongWayVolatile();
Thread thread = new Thread(wrongWayVolatile);
thread.start();
Thread.sleep(5000);
wrongWayVolatile.cancled = true;
}
}
结果:
0是100的倍数。
100是100的倍数。
200是100的倍数。
300是100的倍数。
400是100的倍数。
.........
4.3.3演示用volatile的局限,陷入阻塞时,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.cancled = true;
System.out.println(producer.cancled);
}
}
class Producer implements Runnable{
public volatile boolean cancled = false;
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while(num <= 10000 && !cancled){
if (num % 100 ==0) {
storage.put(num);
System.out.println(num+"是100的倍数,被放到仓库中了。。");
}
num++;
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("生产者结束运行");
}
}
}
4.3.4 演示用中断来修复上面的程序无尽等待的问题
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();
producerThread.isInterrupted();
}
class Producer implements Runnable{
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while(num <= 10000 && !Thread.currentThread().isInterrupted()){
if (num % 100 ==0) {
storage.put(num);
System.out.println(num+"是100的倍数,被放到仓库中了。。");
}
num++;
}
}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;
}
}
}
结果:
2500被消费了
3500是100的倍数,被放到仓库中了。。
消费者不需要更多数据了。
生产者结束运行
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
4.4 停止线程相关重要函数的源码解析
4.4.1. interrupt 方法
判断是否已被中断相关方法
(1). static boolean interrupt
static boolean interrupt
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
(2). boolean isInterrupted
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
(3).Thread.interrupted的目的对象
注意: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("isInterrupted: " + threadOne.isInterrupted());
//获取中断标志并重置 静态方法
System.out.println("isInterrupted: " + threadOne.interrupted());
//获取中断标志并重直 主线程也是false
System.out.println("isInterrupted: " + Thread.interrupted());
//获取中断标志 返回上面第一个中断,依然是true
System.out.println("isInterrupted: " + threadOne.isInterrupted());
threadOne.join();
System.out.println("Main thread is over.");
}
}
结果:
isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true
(4).查看当前线程的isInterrupted方法
public boolean isInterrupted() {
return isInterrupted(false);
}
4.4.2 停止线程-总结
(1). 原理:用Interrupt来请求
(2). 想停止线程,要请求方、被停止方、子方法被调用方相互配合 try{}catch{}
(3). 最后再说错误的方法:stop/suspend已废弃,volateile的boolean无法处理长时间阻塞的情况
(4). 如何处理不可中断的阻塞
5.总结
大致上就把Java 多线程的八大核心前面的三个学习了解,这是用看某学习视频总结而来的个人学习文章。希望也能让观看此篇文章的小伙伴能够更加清晰的了解到多线程的基础来。