Java多线程-1(3)

2021年9月22日 12点热度 0条评论 来源: rttrti

本份随记主要为狂神老师的Java多线程教学的学习笔记,记载了视频中一些有关基础概念以及部分代码示例。随记分为1-3共三份,知识点记录的不是很深入,以后的学习过程中随时补充。

1 有关基础概念

1.1 核心概念

  1. 线程就是独立的执行路径
  2. 程序运行时,即使没有自己创建线程,后台也会由多个线程(主线程、gc线程)
  3. main()称之为主线程,为系统的入口,用于执行整个程序
  4. 一个进程中,若开辟多个线程,线程的运行由调度器安排调度,调度器与操作系统紧密相关,先后顺序不能被人为干预。
  5. 对同一份资源操作时,会存在资源抢夺问题,需加入并发控制
  6. 线程会带来额外的开销(cpu调度时间,并发控制开销)
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致(队列和锁)。

1.2 程序、进程、线程

  1. 程序(静态概念):程序是指令和数据的有序集合,本身没有任何运行的含义
  2. 进程(动态概念):程序的一次执行过程,是系统资源分配的单位
  3. 线程:CPU调度和分派的基本单位。(进程中可包含至少一个的若干线程)
  4. 进程 vs 线程:
    同一进程中的线程使用相同的地址空间,而不同的进程则不会。这允许线程读写公共共享和数据结构和变量,也增加了线程之间的通信。然而,进程间通信(即IPC)是非常困难的,并且需要耗费大量资源。

1.3 并发、并行

  1. 并行:两个或多个事务在同一时刻发生。
    实际多线程为并行执行:多个cpu(多核)共同执行线程。
  2. 并发:两个或多个事务在同一时间间隔内发生。
    模拟多线程为并发执行:在一个cpu情况下同一时间点只能执行一个线程,但切换很快。

2 线程创建

2.1 Thread class(继承Thread类)

  1. 自定义线程类继承Thread类
  2. 子类重写父类的run方法,编写程序执行体
  3. 分配并启动子类的实例(创建线程对象,调用start方法启动线程)

程序示例:

点击查看代码

// 1.创建子类继承自Thread类
public class CreateThread extends Thread{

	//2.重写run方法
	@Override
	public void run() {
		for(int i = 0; i < 5; i++){
			System.out.println("Run.No." + i);
		}
	}
	public static void main(String[] args){
		//3.创建一个线程对象
		CreateThread createThread1 = new CreateThread();
		//3.调用start方法开启线程
		createThread1.start();
		for (int i = 0; i < 5; i++) {
			System.out.println("No." + i);
		}

	}
}

注: 线程开启不一定立即执行,由CPU调度执行,线程执行顺序不是由定义顺序决定的。

2.2 Runnable接口的实现

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run方法,编写程序执行体
  3. 创建线程对象(代理),调用start方法启动线程。
    new Thread(implementRunnable1).start();

程序示例:

点击查看代码

// 1.实现runnable接口
public class ImplementRunnable implements Runnable{

	//2.重写run方法
	@Override
	public void run() {
		for(int i = 0; i < 200; i++){
			System.out.println("Run.No." + i);
		}
	}
	public static void main(String[] args){
		//3.1 创建一个接口实现类对象
		ImplementRunnable implementRunnable1 = new ImplementRunnable();
		//3.2 创建线程对象,通过线程对象开启线程(代理)
		Thread thread = new Thread(implementRunnable1);
		// 3.3调用线程start方法
		thread.start();
			// 3-归并
		new Thread(implementRunnable1).start();
		for (int i = 0; i < 5; i++) {
			System.out.println("No." + i);
		}

	}
}

2.3 Thread与Runnable的对比

2.3.1 继承Thread类:

  1. 创建方法:子类继承Thread类具备多线程能力
  2. 启动线程方法:子类对象.start()
  3. 不建议使用:避免单继承局限性

2.3.2 实现Runnable接口:

  1. 实现接口Runnable具有多线程能力
  2. 启动线程方法:new Thread(传入目标对象).start()
  3. 推荐使用:避免了单继承局限性,灵活方便,方便同一对象被多个线程使用。

2.4 Callable接口(了解)

  1. 实现Callable接口,需返回值类型
  2. 重写call方法,需抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行:Future<Boolean> result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务:ser.shutdownNow();

优缺点分析:

  1. 可以定义返回值
  2. 可以抛出异常
  3. 实现方式较为复杂

3 静态代理

示例:婚庆公司结婚模型(你去结婚,婚庆公司帮你结婚)
内容:

  1. 结婚接口Marry
  2. 你You:参与结婚的角色
  3. 婚庆公司WeddingCompany: 代理角色,帮你完成结婚这件事
  4. 比较Thread,婚庆公司~Thread, You~要调用的线程对象(实现runnable接口的类),thread代替接口实现类去做一些事情
    new WeddingCompany(new You("Mike")).HappyMarry();
    new Thread(() -> System.out.println("I love you!")).start();

模式总结:

  1. 真实对象和代理对象都要实现同一个接口
  2. 代理对象要代理真实角色
  3. 优势
    1. 代理对象可以做很多真实对象不愿做或无法做的事
    2. 真实对象专注做自己的事

4 线程状态

线程可以处于以下状态之一:

  1. NEW: 尚未启动的线程处于此状态。
  2. RUNNABLE:在Java虚拟机中执行的线程处于此状态。
  3. BLOCKED:被阻塞等待监视器锁定的线程处于此状态。
  4. WAITING:正在等待另一个线程执行特定动作的线程处于此状态。
  5. TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  6. TERMINATED:已退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

与此有关的一些线程方法

4.1 线程停止

  1. 不推荐JDK提供的stop,destroy方法[已过时 @Deprecated]
  2. 推荐线程自行停止(利用次数,不建议死循环)
  3. 建议使用标志位进行终止变量,当flag=false则终止线程运行。

4.2 线程休眠(sleep)

  1. sleep(时间)指定当前线程阻塞的毫秒
  2. sleep存在异常InterruptedException
  3. sleep时间打倒后线程进入就绪状态
  4. sleep可以模拟网络延时,倒计时等
  5. 每个对象都有一个锁,sleep不会释放锁

4.3 线程礼让(yield)

概念: 让当前正在执行的线程暂停,但不阻塞(将线程从运行状态转为就绪状态)。

礼让为让CPU重新调度,因此礼让成功与否取决于CPU。

4.4 线程强制执行(join)

Join合并线程,待此线程执行完成后再执行其他线程,其他线程阻塞。
类似于VIP插队

4.5 线程状态观测(Thread.State)

线程状态State为Thread类中的枚举类型:
public static enum Thread.State extends Enum<Thread.State>

获取线程状态的方法:
Thread.State state = thread.getState(); System.out.println(state);

4.6 线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定该调度哪个线程来执行。

范围:线程优先级用数字表示[1, 10]
Thread.MIN_PRIORITY = 1; Thtead.MAX_PRIORITY = 10

改变/获取线程优先级:
getPriority(), setPriority(int xxx)

注:先设置优先级再启动;优先级低只是意味着获得调度的概率低。

4.7 守护线程(daemon)

  1. 线程分为用户线程和守护线程
  2. 虚拟机必须确保用户线程执行完毕(main)
  3. 虚拟机不用等待守护线程执行完毕(gc)
  4. 守护线程:后台记录操作日志,监控内存,垃圾回收等
    原文作者:rttrti
    原文地址: https://www.cnblogs.com/jingqz/p/15314106.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。