Java进阶学习第十七节——线程的实现、休眠

Thread(线程)

1. 课程介绍

  1. 什么是线程(了解)
  2. 创建启动线程的两种方式(掌握)
  3. Thread中的常用方法(了解)

2. 线程的概念

1、哪些地方听说过线程
单例-懒汉模式有线程安全问题;
StringBuffer - StringBuilder;
CPU-几核几线程;
任务管理器-进程-线程数;
多线程下载软件

2 进程是什么?
计算机中正在运行的程序;
程序: 看成就是一些代码;
进程: 可以看成是正在运行的一大段代码;

3 线程是什么?
① 线程是进程中的单元;
② 进程看成是一大段代码,那么一个线程可以看成是一小段代码;
③ 一个进程中可以有多个线程组成,最少有1个;
④ 线程也可以看成是一个轻量级的进程;

4、多线程下载软件为什么快?

在这里插入图片描述
① 一个CPU在同一个时间片上只能够执行一个线程任务;
② CPU会以线程为单位,进行频繁的切换;
③ 上面的图例在CPU眼中就是一堆线程 15个
④ 假设上面的每一个线程获得CPU的资源的机会都是一样,一共15秒的时间,假设每一个线程都可以获得1秒
⑤ 是不是线程数越多越好呢?

3. 自定义第一个线程

3.1 场景描述
开发一个游戏(LOL),实现的功能一边玩游戏,一边播放背景音乐
3.2 实现流程分析
1、玩游戏的这个功能使用一个打印语句代替;
2、播放背景音乐也用一个打印语句代替;
在这里插入图片描述

在这里插入图片描述
3.3 代码实现
1、思考:根据上面的分析如何下手写?
2、我们需要自己定义类,然后覆写run方法,然后把我们的代码写在我们覆写的run方法里面,然后启动
3、根据上面的场景我们需要创建哪些类?
① 玩游戏的线程类
② 放音乐的线程类
③ 测试类:创建① ②的对象,然后调用start方法启动

4 代码清单:

public class PlayGameThread extends Thread{
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("double kill...");
		}
	}
}

public class MusicThread extends Thread{
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("摩擦。。。摩擦...");
		}
	}
}

public class Test {
	public static void main(String[] args) {
		PlayGameThread pgt = new PlayGameThread();
		MusicThread mt = new MusicThread();
		
		pgt.start();
		mt.start();
	}
}

3.4 小结:创建启动线程的方式一(继承Thread类)

1、明确需要把什么事情封装成一个独立的线程(核心的业务代码); 2、自定义一个类 extends Thread;
3、覆写run方法:里面写上面1中的代码; 4、创建自定义类的对象 t ; 5、启动: t.start();

3.5 注意事项
1、直接调用run方法和start的区别?
可以直接调用run方法,但是没有启动一个独立的线程;
只有调用start 才回启动一个独立的线程;

2、自己启动的线程和主线程有关系吗?
① 直接写一个最简单的hello word 程序,就有一个主线程
② 一个线程一旦启动就是独立的了,和创建启动它的环境没有直接的包含关系
代码清单:

public class Test2 {

	/**
	 * 测试主线程执行完毕我们自定义的线程还是会继续执行(前提就是主线程完了,自定义的线程还没有执行完)
	 */
	public static void main(String[] args) {
		System.out.println("hello...");
		
		new ThreadTest().start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println("main"+i);
		}
	}
}

class ThreadTest extends Thread{
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("自定义线程"+i);
		}
	}
}

4. 案例分析:多线程售票示例

在这里插入图片描述

在这里插入图片描述

代码版本1:
在这里插入图片描述

在这里插入图片描述
结果:销售了150张票
希望:50张
原因:num是类中的实例成员,创建一个该类的对象就会赋值一份,其实有3个独立的num
如何解决: 使用static修饰 num 让它所在的类的对象共享一个num

版本2:

在这里插入图片描述

结果:52
期望: 50
原因:(下一章节讲)感觉每销售一张票都有判断 num>0 现在怎么会多出来呢?

5. 实现方式创建启动线程

在这里插入图片描述

代码清单:

public class TicketThread implements Runnable{
	private int num = 50;
	
	public void run() {
		// 最终的代码
		while(num>0){
			System.out.println("您的票号是:"+num);
			num--;
		}
	}
}

public class Test {
	public static void main(String[] args) {
		/*
		 * 只需要创建一个TicketThread的对象   tt
		 * 以tt为参数来创建3个Thread对象
		 */
		TicketThread tt = new TicketThread();
		
		Thread t1 = new Thread(tt);
		Thread t2 = new Thread(tt);
		Thread t3 = new Thread(tt);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

问题: 为什么上面的num没有static 结果也只有50张左右—》 创建了一个 num所在的类的对象

5.2 小结:实现方式启动线程的流程

1、明确线程主体; 要明确哪些代码放到线程体里面
2、自定义一个类实现Runnable接口
3、覆写run方法  : 写第一步中的代码
4、创建一个自定义类的对象  t   
5、以t为参数来构造一个Thread对象  tt;
6、tt.start();//启动一个线程

5.3 继承Thread 和实现Runnable的区别

1、继承有局限,Java中类只能够单继承
2、实现方式,我们的类在业务上可以继承它本应该有的类,同时可以实现接口变成一个线程类
3、关于数据共享的问题:就看所谓被共享的数据所在的类的对象被创建了几个

6. Thread类

6.1 线程休眠sleep
1、static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
在这里插入图片描述
当前正在执行的线程就是主线程
2、可以做倒计时
代码清单:

Frame f = new Frame();
		Label label = new Label("10");
		label.setBackground(Color.RED);
		// 字体对象
		Font font = new Font("宋体",Font.BOLD,666);
		label.setFont(font);
		label.setAlignment(Label.CENTER);
		f.add(label);
		
		f.setSize(780, 780);
		f.setLocationRelativeTo(null);
		f.setVisible(true);
		
		for (int i = 10; i >= 0; i--) {
			label.setText(i+"");
			Thread.sleep(1000);
		}

3、可以用来模拟网络延迟

6.2 线程的优先级
1、 概念: 每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程(简单说:如果一个线程的优先级越高,获得CPU资源的机会更大,不等于高优先级的就最先执行)

2、如何设置优先级呢? 调用方法
int getPriority() 返回线程的优先级
void setPriority(int newPriority) 更改线程的优先级

3、希望验证一下上面的理论
① 先通过主线程来测试获得和设置优先级
② 设置自己定义的线程的优先级
③ 线程的默认优先级和创建它的环境线程的当前优先级一致,主线程的默认优先级是5

4、小结:
知道调用对应的方法来获得和设置线程的优先级
想办法测试高优先级的执行机会高于低优先级?

6.3 守护线程
1、守护线程(精灵线程/后台线程)
每个线程都可以或不可以标记为一个守护程序
后台线程仅仅就是对线程的一个分类或者标记

2、特点:
① 一般来说后台线程是为前台线程服务的(例如gc线程);
② 如果所有的前台线程都死了,那么后台线程也会自动的死亡;但是前台线程死了,后台线程不一定立即死亡(可能还需要收尸…)
③ 一个线程的默认状态和创建它的环境线程状态一致

3、测试把一个线程标记为后台线程
① 相关的方法:
boolean isDaemon() 测试该线程是否为守护线程
void setDaemon(boolean on) 将该线程标记为守护线程或用户线程
如果上面从参数为true 表示是后台线程
② 先以主线程来进行测试:1获得 2 尝试修改
③ 自定义的线程: 1 获得 2 尝试修改

4、小结
① 知道什么是守护线程
② 知道如何调用方法修改它的状态
③ 主线程不能够修改状态—》活动状态的线程是不能被修改的

猜你喜欢

转载自blog.csdn.net/qq_38846837/article/details/84959967