近期在学习Java线程,也是从基础开始再重新回顾一遍,顺便做个笔记整理下思路,个人也是看网上视频加上个人理解,若有错误还请广大网友指正,共同进步。
1 线程的相关概念
1.1 进程和线程
1 进程:进程就是运行中的程序,启动一个线程后,操作系统就是为该进程分配一个内存空间。如图:
2 线程:线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程。可以理解为一个进程包含一个或者多个线程。
1.2 并发与并行
1 并发:同一时刻,多个任务交替执行,造成一种貌似同时的错觉。单核cpu实现的多任务就是并发。
2 并行:同一时刻,多个任务同时执行,多核cpu可实现并行。
帮助记忆理解:
并发的“发”,可理解为发生,同一时刻有多件事情要发生,你一个人(单核CPU)要全部将其解决。
并行的“行”,可以理解为共同行走,你和你的好朋友一起散步,两个人是同时往前走的。
2 线程运行的过程(以Java为列)
线程的实现有两种方式,一种是继承Thread线程类的方式,如下:
//继承Thread线程类
class Cat extend Thread{/*******/}
public static void main(String[] args) {
Cat cat=new Cat();
cat.start(); //启动一个子线程
}
另一种是类B实现Runnable的接口,如下:
//实现Runnable接口
class Dog implements Runnable{/********/}
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);//创建了Thread对象,把 dog对象(实现Runnable),放入Thread
dog.start(); //启动一个子线程
}
下面将根据这两种方法具体介绍。
2.1 继承Thread线程实现线程
当程序执行时,之后开启main方法这个主线程,则进行一个进程的执行,若有子线程,子线程A.start()开启一个子线程,当主线程和子线程全部执行完毕,进程结束。注意:只有所有的线程结束,进程才会结束。这样说可能很难理解,上代码:
public class Thread01 {
public static void main(String[] args) throws InterruptedException{
Cat cat=new Cat(); //创建Cat对象,可以当作线程使用
cat.start(); //启动子线程
//说明:当main线程启动一个子线程 Thread-0,主线程不会阻塞,会继续执行,这时 主线程和子线程会交替执行
System.out.println("主线程继续执行"+Thread.currentThread().getName());
for (int i = 0; i <10 ; i++) {
System.out.println("主线程 i="+i);
Thread.sleep(1000);
}
}
}
/*
1 当一个类继承了Thread类,该类就可以当作线程类使用
2 我们会重写run方法,写上自己的业务代码
3 Thread类 内部也是实现了 Runnable 接口的run方法
*/
class Cat extends Thread{
int times=0;
@Override
public void run() {
while (true){
//该线程每隔1秒种,在控制台输出 “喵喵,我是小喵喵”
System.out.println("喵喵,我是小喵喵 "+(++times)+" 线程名="+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times==80){
break; //当times到80,退出while,这时线程也就退出。。。
}
}
}
}
上述的代码中,其中main()方法,即为一个主线程。cat.start(),即启动了一个子线程,在主线程和子线程同时运行时,无论时主线程先结束还是子线程先结束,进程都不会停止。全部线程结束时,整个进程才会停止。上述代码可以直接复制运行,可以实际的观察一下。
2.2 实现Runnable接口实现线程
class Dog implements Runnable { //通过实现Runnable接口,实现线程
int count = 0;
@Override
public void run() { //普通方法
while (true) {
System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog); //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
thread.start(); //启动一个子线程
}
}
代码之后,看一个流程图:
1 Thread thread =newThread(dog); 在Thread类的具体内部实现中会发现也是实现了Runnable的接口:
2 将dog对象放进Thread中,之后通过thread.start()的方式启动线程。
2.3 继承Thread VS 实现Runnable的区别
1 从Java设计来看,通过继承Thread或者实现Runnable接口创建线程本质上没有区别,从jdk帮助文档上看Threrad类本身就是实现了Runnable接口。
2 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable。
3 如何终止线程
3.1 任务执行结束
当任务执行结束,线程会终止。
3.2 线程通知方式
还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。上代码:
class T extends Thread{
int count=0;
//设置一个控制变量
private boolean loop=true;
@Override
public void run() {
while (loop){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 运行中... "+(++count));
}
}
//通过set的方法来设置线程是否执行
public void setLoop(boolean loop){
this.loop=loop;
}
}
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//如果希望main线程去t1 线程终止,必须可以修改loop
//让t1 退出run方法,从而终止 t1线程-> 通知方式
Thread.sleep(10000); //休眠10秒,在退出
t1.setLoop(false);
}
}
3.3 线程中断
1 线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能得情况下停止当前工作。
2 每个线程都有一个boolean类型的中断变量,并且在Thread类中有三个方法,分别是。
public class Thread{
public void interrupt(){...}
public boolean isInterrupted(){...}
public static boolean interrupted(){...}
...
}
1 interrupt()方法 可以被其他线程进行调用,通过将中断变量设置为true来中断线程。
2 isInterrupted() 方法返回线程的中断变量状态,但不会改变其状态值。
3 interrupted() 方法会先清除中断状态,即将中断变量设为false,并返回之前的中断状态。
3.3.1 如何理解中断机制
中断操作(即调用线程的interrupte()方法)并不是会停止一个正在运行的线程,它只是把线程里边的中断变量设置为true,向这个线程发出一个中断请求,而线程怎么反应,则是线程自己的事情。它可以完全不理会中断,照常执行。
3.3.2 如何恢复中断
并不是把线程从BLOCKED或WAITING等状态中解救出来(本来中断也不是这么个意思),而是把线程的中断状态变量重新设置为True。
4 线程常用的方法
4.1 常见方法
1 setName //设置线程名称,使之与参数name相同
2 getName //返回线程的名称
3 start //是该线程开始执行;java虚拟机底层调用该线程的start0()方法
4 run //调用该线程的run方法
5 setPriority //更改线程的优先级
6 getPriority //获取线程的优先级
7 sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8 interrupt //中断线程,不是停止,一般用于正在休眠的线程
9 线程优先级:MAX_PRIORITY MIN_PRIORITY NORM_PRIORITY
4.2 线程礼让
1 yield线程礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功。
4.2 线程插队
join 线程的插队,插队的线程一旦成功,则肯定先执行插入的线程所有任务。
class T2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程(大哥)吃了 "+i+" 个包子");
}
}
}
public static void main(String[] args) throws InterruptedException {
T2 t2=new T2();
t2.start();
for(int i=0;i<20;i++){
Thread.sleep(1000);
System.out.println("主线程(小弟)吃了 "+i+" 个包子");
if (i==5){
System.out.println("主线程(小弟)让(大哥)先吃");
// join 线程插队 一定会成功
// t2.join();
// 礼让 不一定会成功
Thread.yield();
System.out.println("子线程(老大)吃完了,我们接着吃");
}
}
}
4.3 用户线程和守护线程
1 用户线程:也叫工作线程,当线程的任务执行完或以通知的方式结束。
2 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
3 常见的守护线程:垃圾回收机制。
5 线程的生命周期
在Java中,线程的生命周期有新建状态,就绪状态,运行状态,阻塞状态和终止状态等五个状态,如下图所示:
5.1 新建状态
当一个线程处于创建状态时,系统不会为他分配资源。
Thread myThread=new Thread()
5.2 就绪状态
Java通过start()方法启动处于新建状态的线程对象,使其进入就绪状态。处于就绪状态的线程已经具备了运行的条件,将进入线程队列等待系统为其分配CPU,一旦获得了CPU,线程进入运行状态,并调用自己的run方法。
myThread.start();
5.3 运行状态
处于就绪状态的线程被调度并获得CPU的处理后进入到运行状态,每一个Thread类及其子类的对象都有一个run()方法,当线程被调度的时候,它将自动调用本对象的run()方法。
5.4 阻塞状态
处于运行状态的线程,在某些情况下会暂时终止运行,进入阻塞状态。阻塞状态下的线程不能进入就绪的队列,只有当引起阻塞的原因消除时,线程便转入就绪状态,重新到就绪队列中排队等待,当获取CPU的资源时,从原来终止位置开始继续执行。
1 调用sleep()方法 使线程进入休眠状态
2 调用suspend()方法使线程进入挂起的状态(建议不再使用)
3 调用wait()方法,进入等待状态
4 等待输入输出操作
5.5 终止状态
终止状态是线程生命周期的最后一个阶段。线程终止的原因主要有两个:
1 线程完成全部的工作,工作结束运行。
2 线程被强制终止运行。如通过 stop()或destroy()方法终止一个线程。
6 线程同步
1 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2 也可以理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
7 互斥锁
1 Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2 每个对象都应于一个可称为 "互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
4 同步的局限性:导致程序执行效率降低。
5 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)。
6 同步方法(静态的)的锁为当前类本身。
8 线程的死锁
多个线程都占用了对方的锁资源,但不肯想让,导致了死锁,在编程中一定要避免死锁的发生。
9 释放锁
9.1 会释放锁的情况
1 当前线程的同步方法、同步代码块执行结束。例如上完厕所,完事出来。
2 当前线程在同步代码块、同步方法中遇到break、return。例如没有正常的完事,经理叫他修改bug,不得已出来。
3 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。例如没有正常的完事,突然地震了,急忙跑出来。
4 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停并释放锁。例如一直蹲着,突然没了感觉,一直拉不出来,出来等会再进去。
9.2 不会释放锁的情况
1 当前线程在同步代码块、同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。上厕所,太困了,在坑位上迷了一会。
2 当前线程在同步代码块、同步方法时,其他线程调用了该线程的suspend()方法将线程挂起,线程不会释放锁。应尽量避免使用suspend