Java多线程(一)线程的概念

本文目录:

进程和线程
线程创建方式
线程的状态
中断线程
守护线程

进程和线程

什么是进程和线程?

在计算机中,一个任务就是一个进程

在进程内部还需要多个子任务,每个子任务被称为线程。

一个进程可以包含一个或多个线程(至少一个)。

进程和线程的关系
  1. 一个进程可以包含一个或多个线程(至少一个线程)
  2. 线程是操作系统调度的最小任务单位
  3. 如何调度线程完全由操作系统决定
实现多任务的方法
  1. 多进程模式(每个进程只有一个线程)
  2. 多线程模式(一个进程有多个线程)
  3. 多进程+多线程(复杂度最高)
多进程 vs 多线程
  1. 创建进程比创建线程开销大
  2. 进程间通信比线程间通信慢
  3. 多进程稳定性比多线程高
Java内置多线程支持
  1. 一个Java程序实际上是一个JVM进程
  2. JVM用一个主线程来执行main()方法
  3. 在main()方法中又可以启动多个线程
多线程编程特点
  1. 多线程需要读写共享数据
  2. 多线程经常需要同步
  3. 多线程编程的复杂度高,调试更困难
Java多线程编程特点
  1. 多线程模型是Java程序最基本的并发模型
  2. 网络、数据库、Web等都依赖多线程模型
  3. 必须掌握Java多线程编程才能继续深入学习

线程创建方式

Thread类

Java为我们提供了一个Thread类来操作线程,基本使用如下

public class ThreadTest {
  	public static void main(String[] args) {
    	// 创建线程对象
    	Thread thread = new Thread();
    	// 启动线程
    	thread.start();
  	}
}

线程启动后虚拟机会自动调用run()方法(自己调用没有任何意义),直接使用Thread类对象启动线程run()方法不会执行任何操作,因此需要我们自己定义Thread的子类并重写run()方法来执行我们自己的操作

class SubThread extends Thread {
  	@Override
    public void run() {
		System.out.println("自定义线程运行中。。。")
    }
}

public class ThreadTest {
  	public static void main(String[] args) {
    	SubThread subThread = new SubThread();
    	subThread.start();
  	}
}
Runnable接口

另一种创建线程的方法是实现Runnable接口,并重写run()方法

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread running...");
    }
}

但是创建线程对象和启动线程的方法稍有不同

public class Main {
    public static void main(String[] args) {
      	// 创建Runnable的实现类对象
        Runnable runnable = new MyThread();
      	// 通过Runnable实现类对象创建Thread对象
        Thread thread = new Thread(runnable);
      	// 启动线程
        thread.start();
    }
}
总结
  1. Java用Thread对象表示一个线程,通过调用start()启动一个线程
  2. 一个线程对象只能调用一次start()
  3. 线程的执行代码是run()方法
  4. 线程调度由操作系统决定,程序本身无法决定
  5. Thread().sleep()可以把当前线程暂停一段时间

线程的状态

线程状态
  1. New(新创建)
  2. Runnable(运行中)
  3. Blocked(被阻塞)
  4. Waiting(等待)
  5. Timed Waiting(计时等待)
  6. Terminated(已终止)
线程终止原因
  1. run()方法执行到return语句返回(线程正常终止)
  2. 因为未捕获的异常导致线程终止(线程意外终止)
  3. 对某个线程的Thread实例调用stop()方法强制终止(不推荐)
线程等待

当A线程运行时调用了B线程的join()方法,则会等待B线程执行结束后继续执行A线程。

public class Main {

    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        subThread.start();
        try {
          	// 主线程会等待subthread执行完成
            subThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      System.out.println("主线程结束。。。");
    }
}

也可以指定等待的时间,超过等待时间线程仍然没有结束则不再等待,如果不指定或指定时间为0则表示一直等待。

public class Main {
    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        subThread.start();
        try {
          	// 等同于subThread.join(0);表示一直等待
          	subThread.join();
          	// 主线程最多等待subthread线程1000ms
            subThread.join(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      System.out.println("主线程结束。。。");
    }
}			

如果调用join()方法的线程已经运行结束了,则会立即返回不进行等待。

中断线程

什么是中断线程

如果线程需要执行一个长时间任务,就可能需要能中断线程。

class MyThread extends Thread {
  	@Override
  	public void run() {
    	// 长时间循环执行任务
    	while (true) {
      		// do something...
    	}
  	}
}

中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法。需要被中断的线程就是中断线程。

线程中断方法
检测isInterrupted()

中断线程需要通过检测isInterrrupted()标志来决定要不要中断,其他线程通过调用interrupt()方法中断该线程。

class MyThread extends Thread {
  	@Override
  	public void run() {
    	// 检测isInterrupted()标志决定是否继续执行
  		while (!isInterrupted()) {
        	System.out.println("running...");
      	}
  	}
}

puclic class Main {
  	public static void main(String[] args) throws Exception {
      	Thread t = new MyThread();
      	t.start();
      	Thread.sleep(1000);
      	// 中断线程
      	t.interrupt();
    }
}

如果线程处于等待状态,该线程会捕获InterruptedException,补货到该异常说明其他线程对其调用了interrupt()方法,通常情况下该线程应该立刻结束运行。

class MyThread extends Thread {
	public void run() {
	    while(!isInterrupted()) {
	      	System.out.println("running...");
	      	try {
		        // 线程处于等待状态
		        Thread.sleep(1000);
	      	} catch (InterruptedException e) {
	        	e.printStackTrace();
	        	// 捕获到中断异常,立即结束线程运行
	        	return;
			}
	    }
	}
}
自定义running标志检测

通过检测自定义running标志位来代替检测isInserrupted(),通过修改running的值来决定线程是否中断。

class MyThread extends Thread {
	// 自定义一个标识位
	public volatile boolean ruuning = true;
	public void run() {
		// 检测自定义标识位
		while (running) {
			// do something...
		}
	}
}
public class Main {
	public static void main(String[] args) {
		Thread t = new MyThread();
		t.start();
		Thread.sleep(1000);
		// 修改自定义标识位
		t.running = false;
	}
}

标识位使用volatile来标记,是因为线程间共享变量需要使用volatile标记,以确保线程能读取到更新后的变量值。

volatile简介
Java内存模型

在这里插入图片描述

在Java虚拟机中,变量的值保存在主内存中,当线程访问一个变量的时候,会先获取一个副本,并且保存到自己的工作内存中,如果线程修改了变量的值,虚拟机会在某个时刻把值回写到主内存,但这个时间是不固定的。

volatile作用

volatile关键字的目的是告诉虚拟机:

  • 每次访问变量是,总是获取主内存的最新值

  • 每次修改变量后,立刻回写到主内存

因此volatile关键字解决的是可见性问题:

  • 当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值
总结
  • 调用interrupt()方法可以中断一个线程
  • 通过检测isinterrupted()标识获取当前线程是否已中断
  • 如果线程处于等待状态,该线程会捕获InterruptedException
  • isInterrupted()为true或者捕获了InterruptedException都应该立刻结束
  • 通过标识位判断需要正确使用volatile关键字
  • volatile关键字解决了共享变量在线程间的可见性问题

守护线程

Java中所有线程结束后,JVM退出,如果有无限循环(定时任务)的线程,则虚拟机无法退出,这是就需要守护线程。

什么是守护线程
  • 守护线程是为其他线程服务的线程
  • 所有非守护线程都执行完毕后,虚拟机退出
特点
  • 守护线程不能持有任何资源(如打开文件等),因为虚拟机退出时守护线程无法自动释放资源。
创建守护线程

设置线程属性setDaemon(true)即可把该线程变为守护线程。

class TimerThread extends Thread {
	@Override
	public void run() {
    // 无限循环
		while (true) {
			System.out.println(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				break;
			}
		}
	}
}

public class Main {
	public static void main(String[] args) throws Exception {
		System.out.println("Main start");
		TimerThread t = new TimerThread();
		t.start();
		Thread.sleep(5000);
		System.out.println("Main end");
	}
}

因为子线程在无限循环地打印,所以当Main end输出后程序并不会结束,子线程还在继续打印时间,如果把子线程变为守护线程则程序就正常退出了

//TimerThread类同上
public class Main {
	public static void main(String[] args) throws Exception {
		System.out.println("Main start");
		TimerThread t = new TimerThread();
	    // 设为守护线程,必须在线程启动之前,否则会出现异常
	    t.setDaemon(true);
		t.start();
		Thread.sleep(5000);
		System.out.println("Main end");
	}
}

Java中的垃圾回收线程就是一个守护线程,它始终在低级别的状态下运行,实时监控和管理系统中的可回收资源。当所有非守护线程都退出时,守护线程也就没有存在的必要了,乖乖的自己离开。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bright1st

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值