一、什么是线程、进程、多线程
进程:系统中正在运行的程序
线程:进程中负责控制执行的一个控制单元
多线程:一个进程中有多个线程执行称之为多线程
!!
一个进程中最少要有一个线程
一个进程中可以有多个线程
开启多个线程是为了同时执行多段代码,每个线程都有自己的执行的内容,这个内容称之为线程要执行的 任务
在java语言中:
线程A和线程B,堆内存
和 方法区
内存共享。但是 栈内存
独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发
对于java程序来说,代码在开始执行时,会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法(main方法就是主线程
)。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
也就是说现在的java程序中至少有两个线程并发,一个是 垃圾回收线程
,一个是 执行main方法的主线程
。
二. 如何创建线程
线程的三种创建方式
1.Thread:Thread class ————>继承Thread类(重点)
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法,启动线程
注意:线程不一定立即执行,由CUP安排调度(主线程和副线程交替执行)
package com.example.demo;
public class TestThread extends Thread {
// 重写继承Thread的run方法
@Override
public void run() {
super.run();
for (int i = 0;i<=20;i++){
System.out.println("这是使用重写run方法创建线程--------"+i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread testThread = new TestThread();
//调用start()方法开启线程
testThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("这是主线程main方法---" + i);
}
}
}
注意!!!!!!如果 直接调用run方法,则线程不会并发执行而是先根据代码顺序进行执行
public static void main(String[] args) {
//创建一个线程对象
TestThread testThread = new TestThread();
//调用start()方法开启线程
testThread.run();
for (int i = 0; i < 200; i++) {
System.out.println("这是主线程main方法---" + i);
}
}
2.Runnable:Runnable接口 ————>实现Runnable接口(重点)
- 定义MyRunnable类实现Runnable接口。
- 实现run()方法,编写线程执行体。
- 创建线程对象,调用start()方法启动线程
因为是实现的Runnable接口,接口中没有start的方法,所以需要创建一个Thread对象来调用start方法,Thread源码中可以创建时携带一个Ruannable对象,
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
实现创建线程
package com.example.demo;
public class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0;i<=20;i++){
System.out.println("这是实现Runnable接口实现run方法创建线程--------"+i);
}
}
public static void main(String[] args) {
//实现Runnable接口
TestRunnable testRunnable = new TestRunnable();
//创建一个线程对象,并将Runnable接口对象传入
Thread thread = new Thread(testRunnable);
//调用start()方法开启线程
thread.start();
//启动线程 可以简写为 new Thread(testRunnable).start();
for (int i = 0; i < 200; i++) {
System.out.println("这是主线程main方法---" + i);
}
}
}
3.Callable:Callable接口 ————>实现Callable接口(了解)
可以定义返回值
可以抛出异常
实现Callable接口,需要返回值类型。
重写call方法,需要抛出异常。
创建目标
创建执行服务:ExecutorService xx = Executors.newFixedThreadPool(2);
提交执行:Future r1 = ser.submit(t1);
获取结果:boolean rs1 = r1.get();
关闭服务:ser.shutdownNow();
/**
* @author yxf
*/
//线程创建方式三:实现callable接口,重写call()方法。
public class TestCallable implements Callable<Boolean> {
private String url; //网路图片地址
private String name; //保存的文件名
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException,InterruptedException {
TestCallable t1 = new TestCallable("https://wx2.sinaimg.cn/mw2000/b7a7a24fgy1h82cus8xxlj22qj3pqkjo.jpg","shaking.jpg");
TestCallable t2 = new TestCallable("https://wx2.sinaimg.cn/orj360/b7a7a24fly1h6edp8a91jj22ph3ttkjq.jpg","想我了没.jpg");
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(2);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
//关闭服务
ser.shutdownNow();
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
}
总结:
继承Thread类
1. 子类继承Thread类具备多线程能力。
2. 启动线程:子类对象.start();
3. 不建议使用:避免OOP单继承局限性。
实现Runnable接口
1. 实现接口Runnable具有多线程能力。
2. 启动线程:传入目标对象+Thread对象.start();
3. 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
实现Callable接口-------了解即可
三、线程的状态
线程有五种状态
1.创建状态
当线程被创建时,该线程就进入了创建状态
2.就绪状态
线程对象调用start方法时进入该就绪状态,但是并不以为着该线程马上就进行了调度执行
3.运行状态
当线程处于就绪状态时CPU为其分配内存资源后线程就进入了运行状态,这时线程才会执行线程体中的代码块,处于运行状态的线程也可以进行线程调度,将cpu资源释放,从而转为就绪状态
4.阻塞状态
当调用sleep,wait 或同步锁定synchronized时,线程进入阻塞状态就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。
5.死亡状态
线程中断或者结束,线程就进入了死亡状态。一旦进入死亡状态,就不能再次启动
下面我会用龟兔赛跑的案例来介绍线程的 线程休眠sleep(),线程停止标志,线程礼让yield(),线程强制执行join()
1.线程休眠sleep()
当前线程进入休眠,进入“阻塞状态
”,放弃占有CPU时间片,如果有多个线程则将CPU内存让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
package com.example.demo;
public class GuiTuRace implements Runnable{
// 龟兔赛跑案例,理解线程休眠sleep,线程停止标志,线程礼让yield(),线程强制执行join()
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//线程停止标志符,----可以是一个方法,也可以是一个常量
if (gameOver(i)){
// 如果 gameOver为true 打破for循环 比赛结束,结束线程
break;
}
// 使用线程休眠sleep,使乌龟的线程每隔50步停止1毫秒
if (Thread.currentThread().getName()=="乌龟" && i % 50 == 0){
try {
System.out.println("乌龟线程停止了---------100ms");
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"--->跑了第"+i+"步");
}
}
public boolean gameOver(int steps){
if (winner!=null){//判断是否有获胜者,有则返回true,使线程结束
return true;
}else{//判断线程的结束条件,即两个线程中哪一个先达到线程结束的条件,达成条件后返回true
if (steps>=200){
winner=Thread.currentThread().getName();
System.out.println("获胜者为----->"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
GuiTuRace guiTuRace = new GuiTuRace();
Thread TuZi = new Thread(guiTuRace,"兔子");
Thread WuGui = new Thread(guiTuRace,"乌龟");
WuGui.start();
TuZi.start();
}
}
每当乌龟跑了50步,就停下休息100ms
2.线程结束/中断方法
2.1自定义结束标记
在这个例子中我定义的一个gameover方法作为停止标志
public boolean gameOver(int steps){
if (winner!=null){//判断是否有获胜者,有则返回true,使线程结束
return true;
}else{//判断线程的结束条件,即两个线程中哪一个先达到线程结束的条件,达成条件后返回true
if (steps>=200){
winner=Thread.currentThread().getName();
System.out.println("获胜者为----->"+winner);
return true;
}
}
return false;
}
若达到了线程停止的条件(即兔子或者乌龟到达了200步终点),则将标识符设置为true
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//线程停止标志符,----可以是一个方法,也可以是一个常量
if (gameOver(i)){
// 如果 gameOver为true 打破for循环 比赛结束,结束线程
break;
}
// 使用线程休眠sleep,使乌龟的线程每隔50步停止1毫秒
if (Thread.currentThread().getName()=="乌龟" && i % 50 == 0){
try {
System.out.println("乌龟线程停止了---------100ms");
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName()+"--->跑了第"+i+"步");
}
}
线程中run方法中的代码块在接收到标识符判断是否结束该线程,即打破代码块中的for循环
2.2 调用Tread的方法interrupt()
2.2.1 interrupt方法应用场景
用来打断正在阻塞的线程:sleep/wait/join
打断正常的线程
2.2.2 interrupt() 方法
Thread类的实例方法,其作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。)作用与正常线程会将中断标记设置为true,但是作用于阻塞线程会将中断标志刷新false(中断标记默认为false,刷新就是重新刷会默认)。
2.2.3 interrupted() 方法
Thread类的静态方法,作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。
2.2.4 isInterrupted() 方法
Thread类的实例方法,作用是只测试此线程是否被中断,不清除中断状态。
作用于正常线程时
package com.example.demo;
public class InterruptDemo implements Runnable{
@Override
public void run() {
// Thread.currentThread().isInterrupted()
// isInterrupted判断线程是否中断
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "----------线程运行中");
// 添加一些耗时操作,以便观察线程的打断情况
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException {
InterruptDemo interruptDemo = new InterruptDemo();
Thread thread1 = new Thread(interruptDemo);
thread1.start();
// 主线程休眠3s后打断线程1
Thread.sleep(3000);
// 设置thread1.isInterrupted()为true
System.out.println("设置thread1.isInterrupted()前为--"+thread1.isInterrupted());
thread1.interrupt();//打断正在正常运行的thread1
System.out.println("设置thread1.isInterrupted()前后--"+thread1.isInterrupted());
}
}
作用于阻塞线程时
package com.example.demo;
public class InterruptDemo implements Runnable{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws InterruptedException {
InterruptDemo interruptDemo = new InterruptDemo();
Thread thread1 = new Thread(interruptDemo);
thread1.start();
// 设置thread1.Interrupted()
System.out.println("设置thread1.isInterrupted()前为--"+thread1.isInterrupted());
thread1.interrupt();//打断正在已经进入sleep状态的阻塞线程thread1
System.out.println("设置thread1.isInterrupted()后为--"+thread1.isInterrupted());
}
}
3.线程礼让yield()
线程让位,即让当前线程暂停,回到就绪状态,将此次抢占的CPU资源分配让给其它线程。
上面的例子中我只是把sleep代码块稍作修改,如下
if (Thread.currentThread().getName() == "乌龟" && i % 50 == 0) {
Thread.yield();
System.out.println("乌龟线程进行了线程礼让---------");
}
每当乌龟跑了50步时,并进行一次线程礼让,即将cpu资源释放,让其他线程占用cpu资源
要注意的是并不是每次都让成功的,有可能它又抢到时间片了。
4.线程的join()方法
让主线程等待(进入WAITING状态),一直等到其他子线程不再活动为止。
package com.example.demo;
public class GuiTuRace implements Runnable {
// 龟兔赛跑案例,理解线程休眠sleep,线程停止标志,线程礼让yield(),线程强制执行join()
private static String winner;
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---------开始");
for (int i = 1; i <= 100; i++) {
//线程停止标志符,----可以是一个方法,也可以是一个常量
if (gameOver(i)) {
// 如果 gameOver为true 打破for循环 比赛结束,结束线程
break;
}
System.out.println(Thread.currentThread().getName() + "--->跑了第" + i + "步");
}
System.out.println(Thread.currentThread().getName()+"---------结束");
}
public boolean gameOver(int steps) {
if (winner != null) {//判断是否有获胜者,有则返回true,使线程结束
return true;
} else {//判断线程的结束条件,即两个线程中哪一个先达到线程结束的条件,达成条件后返回true
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("获胜者为----->" + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
System.out.println("main---------开始");
GuiTuRace guiTuRace = new GuiTuRace();
Thread TuZi = new Thread(guiTuRace, "兔子");
Thread WuGui = new Thread(guiTuRace, "乌龟");
TuZi.start();
WuGui.start();
for (int i = 0; i < 10; i++) {
if (i==5){
try {
System.out.println("TuZi---------强制执行");
System.out.println("wugui---------强制执行");
WuGui.join();
TuZi.join(); // 乌龟兔子合并到主线程中,当前主线程受阻塞,乌龟兔子线程执行直到结束。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程执行--------"+i);
}
System.out.println("main---------结束");
}
}
当主线程执行了一半时,使主线程进入等待,等待子线程执行结束继续执行主线程的方法