前言
前面学了Java多线程并发(1.1)线程与线程的实现、启动,然后去看了许多资料,发现还有很多知识,不能简单的跳过,学习笔记还要继续补充
目录
- 线程知识
- 线程操作
2.1.创建线程方法 2.2.设置线程名
2.3.守护线程
2.4.yield让行
2.5.join
2.6.终止线程方法
2.7.等待与唤醒 - 总结
线程知识
- 线程在JVM中的存储
一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。
程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 。
栈:用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈祯。
堆:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的。
方法区:则用来存放 JVM 加载的类、常量及静态变量等信息,也是线程共享的 。
即JVM运行时的数据区可以分为两种:线程共享和线程独占
- 进程、线程的状态
进程的3个基本状态:执行、就绪(活动就绪-静止就绪)、阻塞(活动阻塞-静止阻塞)
线程的5个基本状态:创建、就绪、执行、阻塞、完成(结束)
关于锁定(Blocked)在多线程的知识中
随着计算机的不断发展,进程调度需要花费越来越多的时间和空间
引入线程主要是为了提高系统的执行效率,减少处理机的空转时间和调度切换的时间,以及便于系统管理。使OS具有更好的并发性
进程实现多处理非常耗费CPU的资源,而我们引入线程是作为调度和分派的基本单位(取代进程的部分基本功能【调度】)
一个Firefox是一个进程,而在Firefox上的多个页面可以看成线程
- 并行与并发
并行:是说在单位时间内多个任务同时在执行
并发:是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行
并行是针对进程的,并发是针对线程的
并发是造成诸多问题的关键
线程的操作方法
创建线程方法
常见的有两种创建线程的方法,继承Thread类,继承Runnable接口
详情见Java多线程并发(1.1)线程与线程的实现、启动
而在实现多线程的时候,继承重写了run()方法,而启动线程又是使用start()方法
run()和start()方法区别:
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
start()方法是Thread类的方法:
start会启动start0()方法
而start0()方法是native修饰的
native是什么?
native详解
总结就是native是用来通知操作系统的,告诉操作系统要实现native修饰的方法,Java只能调用
设置线程名
线程的名字:主线程叫做main,其他线程是Thread-x
可以看线程的构造方法
nextThreadNum方法:
threadInitNumber没有设置值,那我们看看线程命名是怎样的?
我们可以试试
在线程方法下的main方法查看:
System.out.println(creatThread1.getName());
System.out.println(creatThread2.getName());
也就是Thread-x,后接数字排序通过setName()设置名字
System.out.println(creatThread1.getName());
creatThread1.setName("hello");
System.out.println(creatThread1.getName());
守护线程
守护线程是为其他线程服务的, 垃圾回收线程就是守护线程
守护线程有一个特点: 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行
那就需要注意:守护线程不用访问共享资源,守护线程不知道什么时候会关闭
通过on(也就是输入值)设置守护线程
isAlive()方法:当线程已经启动再设置守护线程会报错
package com.company.Thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
public class OpenThread {
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
static class CreatThread1 implements Runnable{
public void run(){
for(int i=0;i<30;i++) {
System.out.println(i + ":运行线程1");
}
}
}
static class CreatThread2 implements Runnable{
public void run(){
for(int i=0;i<300;i++)
System.out.println(i+":运行线程2");
}
}
public static void main(String[] args){
CreatThread1 thread1=new CreatThread1();
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
//启动线程需要实例化Thread,并传入我们的实例
//启动线程1
Thread myThread1=new Thread(thread1);
myThread1.start();
//启动线程2
Thread myThread2=new Thread(thread2);
//设置线程2位守护线程
myThread2.setDaemon(true);
myThread2.start();
}
}
因为守护线程,线程2在线程1关闭后运行到62就也关闭了
yield让行
前面学习了sleep方法,让线程睡眠
接下来学习yield方法让行
yield方法会先让别的线程执行,但是不确保真正让出
package com.company.Thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
public class OpenThread {
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
static class CreatThread1 implements Runnable{
public void run(){
for(int i=0;i<30;i++) {
System.out.println(i + ":运行线程1");
}
}
}
static class CreatThread2 implements Runnable{
public void run(){
for(int i=0;i<300;i++)
System.out.println(i+":运行线程2");
}
}
public static void main(String[] args){
CreatThread1 thread1=new CreatThread1();
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
//启动线程需要实例化Thread,并传入我们的实例
//启动线程1
Thread myThread1=new Thread(thread1);
myThread1.start();
//启动线程2
Thread myThread2=new Thread(thread2);
//让行
myThread2.yield();
myThread2.start();
}
}
然而让行并不怎么样
join
线程运行完,再执行其他线程
package com.company.Thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
public class OpenThread {
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
static class CreatThread1 implements Runnable{
public void run(){
for(int i=0;i<30;i++) {
System.out.println(i + ":运行线程1");
}
}
}
static class CreatThread2 implements Runnable{
public void run(){
for(int i=0;i<300;i++)
System.out.println(i+":运行线程2");
}
}
public static void main(String[] args) throws InterruptedException {
CreatThread1 thread1=new CreatThread1();
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
//启动线程需要实例化Thread,并传入我们的实例
//启动线程1
Thread myThread1=new Thread(thread1);
myThread1.start();
//启动线程2
Thread myThread2=new Thread(thread2);
//join方法
myThread2.join();
myThread2.start();
}
}
和优先级方法类似
虽然Java方法的意愿是好的,但是操作系统不允许
操作系统内部有线程规划器
终止线程方法
最初是stop方法,stop方法可以让一个线程A终止掉另一个线程B
被终止的线程B会立即释放锁,这可能会让对象处于不一致的状态。
线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法将线程B终止,就会造成数据的不一致
stop方法不安全,已经过时了
现在使用interrupt方法请求终止线程,它并没有强制终止线程(强制的话就和stop一样的)
请求终止进程就是让线程自己去终结,具体该怎么中断要看线程自身
不能中断阻塞线程、死亡线程
源代码:
interrupt方法压根是不会对线程的状态造成影响的,它仅仅设置一个标志位罢了
interrupt线程中断还有另外两个方法(检查该线程是否被中断):
静态方法interrupted()–>会清除中断标志位
实例方法isInterrupted()–>不会清除中断标志位
package com.company;
public class Main {
public static void main(String[] args) {
Main main = new Main();
// 创建线程并启动
Thread t = new Thread(main.runnable);
System.out.println("This is main ");
t.start();
try {
// 在 main线程睡个3秒钟
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("In main");
e.printStackTrace();
}
// 设置中断
t.interrupt();
}
Runnable runnable = () -> {
int i = 0;
try {
while (i < 1000) {
// 睡个半秒钟我们再执行
Thread.sleep(500);
System.out.println(i++);
}
} catch (InterruptedException e) {
// 判断该阻塞线程是否还在
System.out.println(Thread.currentThread().isAlive());
// 判断该线程的中断标志位状态
System.out.println(Thread.currentThread().isInterrupted());
System.out.println("In Runnable");
e.printStackTrace();
}
};
}
sleep线程被中断,抛出异常
大概可以看出终结线程的步骤:
线程执行 - > checkAccess判断是否有权限置换中断标志 -> 查看是否阻塞 ->如果是,抛出异常,将中断标志改为false -> 如果一切顺利,调用interrupt0方法,interrupt0是native方法,控制操作系统
等待与唤醒
通过wait()方法可以让线程进入等待状态(阻塞),通过notify()或者notifyAll()方法就能唤醒
wait()是属于Object类的方法,同样的等待方法sleep是Thread类的方法
wait()会释放当前共享对象的锁,而sleep()不会
注意:当前线程调用共享变量对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁并不会被释放
package com.company.Thread;
public class PersonThread {
static class CreatThread1 {
public void run() {
System.out.println("运行线程");
System.out.println("开始等待");
synchronized (this){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待结束");
}
}
public static void main(String[] args) {
CreatThread1 creatThread1=new CreatThread1();
new Thread(creatThread1::run).start();
}
}
wait()方法执行后,没有唤醒的话一直在等待,也可以设置超时唤醒wait(time)
唤醒:notify() 通过另一个线程唤醒线程
package com.company.Thread;
public class PersonThread {
static class CreatThread1 {
public void run() {
System.out.println("运行线程");
System.out.println("开始等待");
synchronized (this){
try {
this.wait();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待结束");
}
public void ThreadNotify(){
synchronized (this){
this.notify();
}
}
}
public static void main(String[] args) {
CreatThread1 creatThread1=new CreatThread1();
Thread thread1=new Thread(creatThread1::run);
thread1.start();
Thread thread2=new Thread(()->creatThread1.ThreadNotify());
thread2.start();
}
}
无论是wait()方法还是notify()方法都需要首先获取目标对象上的一个监视器,也就是wait、notify都在锁synchronized 的代码块或方法内
在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的
为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁
锁住了一个对象,就是获得对象相关联的监视器
当然,要唤醒必须是同一个监视器
notify唤醒对于等待同一个对象的线程是无序唤醒的,我们可以通过嵌套方法,一个一个唤醒实现有序
即编写一个唤醒方法唤醒线程1,线程1里有一个唤醒方法唤醒线程2。。。
notifyAll()就是用来唤醒正在等待状态中的所有线程的,注意:
(1)notifyAll()只会唤醒那些等待抢占指定的object’s monitor的线程,其他线程则不会被唤醒。
(2)notifyAll()只会一个一个的唤醒,而并非统一唤醒。因为在同一时间内,只有一个线程能够持有object’s monitor
(3)notifyAll()只是随机的唤醒线程,并非有序唤醒
package com.company.Thread;
public class PersonThread {
static class CreatThread1 {
public void run() {
System.out.println("运行线程");
System.out.println("开始等待");
synchronized (this){
try {
this.wait();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等待结束");
}
public void ThreadNotify(){
synchronized (this){
this.notifyAll();
}
}
}
static class CreatThread2{
public void run(){
System.out.println("对象2开始等待");
synchronized (this){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("对象2等待结束");
}
}
public static void main(String[] args) {
CreatThread1 creatThread1=new CreatThread1();
CreatThread2 creatThread2=new CreatThread2();
Thread thread=new Thread(creatThread2::run);
thread.start();
Thread thread1=new Thread(creatThread1::run);
thread1.start();
Thread thread2=new Thread(()->creatThread1.ThreadNotify());
thread2.start();
}
}
对象creatThread1的notifyAll唤醒不了对象creatThread2的线程
总结
- 复习了线程自身:线程在JVM的存储,线程与进程的基础状态,并发与并行
- 线程的操作:创建、命名、守护线程、yield让行、join、interrupt
Thread中重要的还是那几个可以切换线程状态的方法,还有理解中断的真正含义