005多线程Thread

2020-01-14 21:29发布

多线程Thread
多进程概述
进程
多线程
线程调度
线程调度概述
线程优先级
设置对象优先级
线程控制:其他方法
线程睡眠sleep
线程加入 join():
线程礼让,暂停当前线程,执行其他线程
后台线程
中断线程
线程的生命周期
实现多线程
1.继承Thread类
线程名称
获取线程名称
设置线程名称:2种
2.实现Runnable接口(常用)
概述
实现
线程安全问题
实现卖电影票案例(不安全)
方式1:继承Thread类
方式2:实现Runnable接口
问题分析:同票和负数票
同步(synchronized)
同步概述
同步方法
同步代码块
买票同步代码块
同步方法
买票同步方法
方法1
方法2
方法3:静态方法锁
银行存钱案例
Lock锁(JDK5之后)
Lock锁卖票案例
线程死锁
死锁问题及其代码
死锁案例
方法1
方法2:
线程间通信
线程间通信概述
等待/唤醒机制
生产消费:加入等待唤醒机制,加入判断**
方法1(更好):
方法2:
优化生产消费问题
多生产者,多消费者的问题。烤鸭生产一只消费一只
Condition等待/唤醒机制
优化生产消费问题
线程组ThreadGroup
概述
获取线程组,名字
修改线程组
线程池
概述
Callable接口:创建线程3
1.求和案例
匿名内部类方式使用多线程
定时器
循环一次
循环调用
案例:在指定的时间删除的指定目录
多线程的单例
停止线程
习题
TOC

多线程Thread

多进程概述

进程

  • 1.定义:正在进行的程序(任务管理器中可以看到)(一个CPU同一时间点只运行一个进程,只运行其中的一个线程)。是系统进行资源分配和调用的独立单位。每一个进程都有他自己的内存空间和系统资源。
  • 2.多进程有什么意义呢?
    • 可以提高CPU的使用率

        单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
        也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。

  • 3.问题:一边玩游戏,一边听音乐是同时进行的吗?
    • 单cpu:
      不是。因为单CPU在某一个时间点上只能做一件事情。
      而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
    • 多CPU:可能是。

多线程

线程是依赖于进程而存在。

  • 线程:在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
    (1)线程定义:是程序的执行单元,执行路径。是程序使用CPU的最基本单位
    (2) 单线程:如果程序只有一条执行路径。
    (3)多线程:如果程序有多条执行路径。

    (360是一个进程,同时运行360的不同功能是多线程);

    一个进程中至少有一个线程。
    (4)目的:开启多个线程是为了同时运行多部分代码。

  • 程序:每一个线程都有自己运行的内容,这个内容可以成为线程要执行的程序。
  • 多线程意义:为了提高应用程序的使用率

    程序的执行其实都是在抢CPU的资源,CPU的执行权。

    多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多(线程多),就会有更高的几率抢到CPU的执行权。

    我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。

  • 注意并行和并发:
    并行:是逻辑上同时发生,指在某一个时间段内同时运行多个程序。
    并发:是物理上同时发生,指在某一个时间点同时运行多个程序。
  • Java程序运行原理
    由java命令启动JVM,JVM启动就相当于启动了一个进程。接着由该进程创建了一个主线程去调用main方法。
  • jvm虚拟机的启动是单线程的还是多线程的?
    多线程的。
    原因是垃圾回收线程(finalize())也要先启动,否则很容易会出现内存溢出。
    垃圾回收线程+主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
  • 多线程的好处:解决了多部分同时运行的问题

    如果多线程中的某一条线程发生错误,会显示异常,并停止这条线程,但其他线程不受影响.

  • 创建线程的目的
    是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
  • 多线程实现
    C/C++去调用系统功能创建进程,然后由Java去调用进程实现多线程

1.多线程优点

  • 资源利用率更好
  • 程序设计在某些情况下更简单
  • 程序响应更快

2.多线程缺点:

  • 设计更复杂
  • 上下文切换的开销:上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。
  • 增加资源消耗。

3.多线程有几种实现方案,分别是哪几种?
两种。
1.继承Thread类
2.实现Runnable接口

扩展一种:实现Callable接口。这个得和线程池结合。(一般可以不答)

线程调度

线程调度概述

假如我们的计算机只有一个CPU,那么CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。

线程有两种调度模型

  • 分时调度模型:所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间片
  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU 时间片相对多一些。

Java使用的是抢占式调度模型

线程优先级

  • 线程默认优先级是:5
  • 线程优先级的范围是:1-10
  • 线程优先级:最大为10,最小为1,默认为5
方法 定义
public final int getPriority() 获取线程对象的优先级
public final void setPriority(int newPriority) 设置线程的优先级

设置对象优先级

  1. public class ThreadPriorityDemo {
  2. public static void main(String[] args) {
  3. ThreadPriority tp1 = new ThreadPriority();
  4. ThreadPriority tp2 = new ThreadPriority();
  5. //设置线程名
  6. tp1.setName("东方不败");
  7. tp2.setName("岳不群");
  8. // 获取默认优先级
  9. System.out.println(tp1.getPriority());
  10. //设置线程优先级
  11. tp1.setPriority(10);
  12. tp2.setPriority(1);
  13. System.out.println(tp1.getPriority());
  14. System.out.println(tp2.getPriority());
  15. tp1.start();
  16. tp2.start();
  17. }
  18. }
  19. ///////////////
  20. 5-----默认是5
  21. 10---设置后
  22. 1
  23. 5
  24. 东方不败:0
  25. 东方不败:1

注意:
线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。

线程控制:其他方法

sleep(long millis) 线程休眠,单位是毫秒(ms)。(时间到了继续运行)
join() 线程加入,只有这个线程完毕,其他线程才可以继续
yield() 线程礼让,暂停当前线程,执行其他线程
setDaemon(boolean on) 后台线程将该线程标记为守护线程或用户线程必须在启动线程前调用(一旦只剩下守护线程,后台线程立即结束
stop() 中断线程
interrupt() 中断线程,把线程的状态终止,并抛出一个异常,不会影响后续代码运行
toString() Thread.*currentThread*().toString();
Thread[Thread-0,5,main]

线程睡眠sleep

  1. public class SleepThread extends Thread{
  2. public void run(){
  3. for(int i = 0 ; i < 5 ;i++){
  4. try{
  5. Thread.sleep(2000);//此线程sleep时,会运行其他线程
  6. //只能用try catch,不能throws,因为父类没抛这个异常,子类也不能抛
  7. //Thread.sleep(1000);//可以直接写sleep(1000);而省略Thread
  8. }catch(Exception ex){}
  9. System.out.println(getName()+" "+i);
  10. }
  11. }
  12. }
  13. SleepThread s1 = new SleepThread();
  14. SleepThread s2 = new SleepThread();
  15. s1.setName("意");
  16. s2.setName("而");
  17. s1.start();
  18. s2.start();

线程加入 join():

等待该线程终止,只有这个线程完毕,其他线程才可以继续

  1. public class SleepThread extends Thread{
  2. public void run(){
  3. for(int i = 0 ; i < 50 ;i++){
  4. System.out.println(getName()+" "+i);
  5. }
  6. }
  7. }
  8. //__________________
  9. SleepThread s1 = new SleepThread();
  10. SleepThread s2 = new SleepThread();
  11. SleepThread s3 = new SleepThread();
  12. s1.setName("111");s2.setName("222");s3.setName("333");
  13. s1.start();
  14. s1.join();//将1线程加入
  15. s2.start();
  16. s3.start();

线程礼让,暂停当前线程,执行其他线程

注意:
理论上是一个线程执行一次后,等待下一个线程执行,然后第一个线程再执行第二次,……实际上可能有"误差"

  1. public void run() {
  2. for (int x = 0; x < 100; x++) {
  3. System.out.println(getName() + ":" + x);
  4. Thread.yield();
  5. }
  6. }
  7. //____________________________________________________
  8. ThreadYield ty1 = new ThreadYield();
  9. ThreadYield ty2 = new ThreadYield();
  10. ty1.setName("林青霞");
  11. ty2.setName("刘意");
  12. ty1.start();
  13. ty2.start();

后台线程

方法 定义
setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
当正在运行的线程只剩下守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。

关羽张飞守护刘备,刘备完成走了,关羽张飞就也走了

  1. ThreadDaemon td1 = new ThreadDaemon();
  2. ThreadDaemon td2 = new ThreadDaemon();
  3. td1.setName("关羽");
  4. td2.setName("张飞");
  5. // 设置守护线程(注意:必须在start()方法之前设置,否则会有异常!!)
  6. td1.setDaemon(true);
  7. td2.setDaemon(true);
  8. td1.start();
  9. td2.start();
  10. Thread.currentThread().setName("刘备");//改一改main线程的名字
  11. for (intx = 0; x < 5; x++) {
  12. System.out.println(Thread.currentThread().getName() + ":" + x);
  13. }
  14. 但是不会立马结束,还会运行几次

中断线程

方法 定义
stop(): 让线程停止,这一个线程停止
interrupt() 停止程序,运行catch部分代码
  1. public class ThreadStopDemo {
  2. public static void main(String[] args) {
  3. ThreadStop ts = new ThreadStop();
  4. ts.start();
  5. try {
  6. Thread.sleep(3000);
  7. ts.stop();//3s后会停止程序
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }}}

--3s后程序停止

  1. public static void main(String[] args) {
  2. ThreadStop ts = new ThreadStop();
  3. ts.start();
  4. try {
  5. Thread.sleep(3000);
  6. ts.interrupt();//结束线程,运行catch内容
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }


好处:不影响后续代码执行(继续运行catch之后的)

线程的生命周期

  • 新建new:创建线程对象
  • 就绪:有执行资格,没有执行权
  • 运行runable:有执行资格,有执行权
  • 阻塞:由于一些操作(sleep(),wait())让线程处于了该状态。没有执行资格,没有执行权;而另一种操作(sleep()时间到,notify())却可以把它激活,激活后处于就绪状态
  • 死亡:线程对象变成垃圾,等待回收。

CPU同一时刻只能处理一个线程,多线程是多个线程轮流运行,允许运行但在等待的线程处于阻塞状态。

方法 定义
sleep 需要指定睡眠时间,单位是毫秒(ms)。(时间到了继续运行);
wait() 等待,自己无法醒来,用notify(),可以唤醒;
  • CPU的执行资格:可以被CPU处理,在处理队列中排队;
  • CPU的执行权:正在被CPU处理;

实现多线程

1.继承Thread类

  • 实现步骤
  1. 1.继承Thread类
  2. 2.重写run方法
  3. 3.直接创建Thread的子类对象创建线程。
  4. 4.调用start方法开启线程并调用线程的任务run方法执行。
  • Run方法中定义的是线程中要运行的任务代码

    不是类中的所有代码都需要被线程执行的,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。(代码若想被多线程执行,必须写(封装)在run里面

    一般来说,被线程执行的代码肯定是比较耗时的。

  • 方法
方法 定义
run() 定义类时需要覆写的方法,定义的是线程中要运行的代码块
start() 开启线程,调用run方法
getName() 获取线程的名称(Thread-编号(从0开始))run和start都是Thread-0
super(name) 带参构造时,设置线程名称
Thread.currentThread() 返回正在运行的线程对象;Thread thread = Thread.currentThread();
  • run()和start()区别
    run():仅仅是封装被线程执行的代码,直接调用是普通方法,是按顺序运行
    start():首先启动了线程,然后再由jvm去调用该线程的run()方法,是随机运行

注意:
同一个线程只能调用一次!!(如my.start();只能用一次,第二次会显示异常IllegalThreadStateException)

  • 简单示例
  1. //创建一个类,继承Thread,实现run方法
  2. public class MyThread extends Thread {
  3. public void run() {
  4. for (int x = 0; x < 10000; x++) {
  5. System.out.println(x);
  6. }
  7. }
  8. }
  9. //测试,调用线程直接new一个,start即可
  10. public class MyThreadDemo {
  11. public static void main(String[] args) {
  12. // 创建两个线程对象
  13. MyThread my1 = new MyThread();//开启一个新的线程
  14. MyThread my2 = new MyThread();
  15. my1.start();//执行线程
  16. my2.start();
  17. }
  18. }
  19. //-----
  20. 这是三个线程,my1,my2main

线程名称

名字 方法 获取的线程
获取main线程名称
获取当前线程名称
Thread.currentThread().getName() main
当前的线程(main无法使用) 方法1:Thread.currentThread().getName()
方法2:getName()
Thread-0
  • 无参构造
  1. public class MyThread extends Thread {
  2. public void run() {
  3. for (int x = 0; x < 100; x++) {
  4. System.out.println(getName() + ":" + x);
  5. }
  6. }}
  • 带参构造
  1. public class MyThread extends Thread {
  2. public MyThread() {}
  3. ////在这里可以设置线程名比如super("林");
  4. public MyThread(String name){
  5. super(name);
  6. }
  7. @Override
  8. public void run() {
  9. for (int x = 0; x < 100; x++) {
  10. System.out.println(getName() + ":" + x);
  11. }
  12. }
  13. }
获取线程名称
  1. //在main方法中调用
  2. MyThread my1 = new MyThread();
  3. MyThread my2 = new MyThread();
  4. System.out.println("main线程:"+Thread.currentThread().getName());//获取主线程名称
  5. my1.start();
  6. my2.start();
  7. //*************************************************************************
  8. main线程:main
  9. Thread-1:0
  10. Thread-1:1
设置线程名称:2种
方法 定义
setName(String name) 设置线程的名称(main线程无法改名)

无参构造设置线程名

  1. MyThread my1 = new MyThread();
  2. MyThread my2 = new MyThread();
  3. my1.setName("林青霞");
  4. my2.setName("刘意");
  5. my1.start();
  6. my2.start();
  7. ////////
  8. 林青霞:0
  9. 刘意:0...

带参构造

  1. MyThread my1 = new MyThread("林青霞");
  2. MyThread my2 = new MyThread("刘意");
  3. my1.start();
  4. my2.start();
  5. //****************
  6. 林青霞:13
  7. 刘意:0

2.实现Runnable接口(常用)

概述

1.为什么要给Thread类的构造函数传递Runnable的子类对象?

因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。

2.为什么可以避免由于Java单继承带来的局限性

比如说,某个类已经有父类了,而这个类想实现多线程,但是这个时候它已经不能直接继承Thread类了(接口可以多实现implements,但是继承extends只能单继承),它的父类也不想继承Thread因为不需要实现多线程。

3.实现Runnable接口的好处:

  • 可以避免由王Java单继承带来的 限性。
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面问对象的设计思想。

实现

  • 实现步骤
  1. 1,实现Runnable接口。
  2. 2,重写run方法,将线程的任务代码封装到run方法中。
  3. 3,创建本类的对象
  4. 4,创建Thread类的对象,并把3步骤的对象作为构造参数传递。
  • 编写实现类
  1. class Demo implements Runnable{ //通过接口的形式完成。
  2. public void run(){ //覆盖接口中的run方法
  3. for(int x=0; x<100; x++)
  4. System.out.println(Thread.currentThread().getName()+"....."+x);//获取名称,只能间接用
  5. }
  6. }
  • 不初始化线程名:
  1. class ThreadDemo{
  2. public static void main(String[] args) {
  3. Demo d = new Demo();
  4. //注意:MyRunnable对象只需要创建一个即可,多个Thread对象可以接收同一个MyRunnable对象
  5. Thread t1 = new Thread(d);//通过Thread类创建线程对象,并传递Runnable。
  6. Thread t2 = new Thread(d);
  7. t1.start();
  8. t2.start();
标签: