本章目录

  • 1、基础知识准备
  • 2、Java 多线程概述
  • 3、Java 实现多线程
    • 3.1、继承 Thread 类
      • 如何开启新线程
      • Thread 类常用方法
      • 多线程中的同步
      • Thread 类同步方法
      • 多线程中的死锁
    • 3.2、实现 Runnable 接口
      • Runnbale 接口概述
      • Runnbale 开启新线程
      • 常用方法
      • 龟兔赛跑案例
    • 3.3、实现 Callable 接口
      • Callable 接口概述
      • Callable 开启新线程
      • Callable 开启新线程案例

1、基础知识准备

进程:进程就是处于运行中的程序,当一个程序进入内存运行,就变为一个进程。进程是系统进行资源分配和调度的基本单位,各个线程之间相互独立。同时,每个进程都拥有自己的内存空间和系统资源。

线程:线程是进程中执行任务的单元,是 CPU 调度和分配的基本单位。一个进程至少有一个线程,可以有多个线程,多个线程共享一块内存空间和一组系统资源。

程序:程序是存储在磁盘上的含有指令和数据的静态代码。

并行:在同一时间点,多个程序同时运行。

并发:在某一时间段,多个程序交替运行。

注意:并发不是真正意义上的同时进行,只是 CPU 将一段时间划分为多个时间片,每个时间片用来执行不同的程序,然后在这几个时间片之间来回切换,让用户感到多个程序在同时运行。

多线程:一个进程中多个线程并发运行。

多线程的意义:在程序运行的过程中,都在争抢 CPU 的时间片,如果是多线程程序,抢到 CPU 时间片的概率比单线程大的多。也就是说,CUP 在多线程程序中执行的时间要比单线程程序多,所以提高了程序的使用率。但哪个线程能抢到时间片是不确定的,所以多线程具有随机性。

2、Java 多线程概述

执行某个 Java 程序时,要先启动 JVM。启动 JVM 就意味着启动了一个程序,也就是启动了一个进程。该进程会自动创建一个主线程,主线程再去执行某个类的 main 方法,然后在主线程的某个位置启动新的线程。

Java 实现多线程的方式:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。

3、Java 实现多线程

3.1、继承 Thread 类

Thread 类称为线程类,创建此类实例相当于创建了一个新线程。

如何开启新线程

1、让某类继承 Thread 类,重写 Thread 类中的 run() 方法,接下来在测试类中创建此类对象,该对象调用 start() 方法,开启一个新线程。

新线程在创建后,会自动执行它的 run() 方法,所以 run() 方法中编写的是新线程完成某功能的逻辑,注意创建线程后一定要调用 start() 方法,调用 start() 方法才意味着开启一个线程。

public class ThreadDemo01 {public static void main(String[] args) {for (int i = 0; i < 10; i++) {System.out.println("我是 主线程 中的" + i);}//创建继承了 Thread 类的实例,相当于创建了一个线程MyThread myThread = new MyThread();//开启线程myThread.start();}
}class MyThread extends Thread{@Overridepublic void run() {//这里写想要新线程做什么for (int i = 0; i < 10; i++) {System.out.println("我是新线程中的" + i);}}
}

2、采用匿名对象的方法开启新线程

public class ThreadDemo02 {public static void main(String[] args) {new Thread(){@Overridepublic void run() {System.out.println("新线程1的 run() 方法");}}.start();new Thread(){@Overridepublic void run() {System.out.println("新线程2的 run() 方法");}}.start();}
}
Thread 类常用方法

1、setName(String name)、getName()

设置,获取线程的名字。

public class ThreadMethod01 {public static void main(String[] args) {//使用 Thread 类的构造器给线程命名new Thread("线程1"){@Overridepublic void run() {System.out.println(this.getName() + "的 run() 方法");}}.start();Thread thread = new Thread(){@Overridepublic void run() {System.out.println(this.getName() + "的 run() 方法");}};thread.setName("线程2");thread.start();}
}

2、static Thread currentThread()

获取当前正在执行的线程。

public class ThreadMethod02 {public static void main(String[] args) {//获取主线程Thread thread = Thread.currentThread();//给主线程设置名称并打印thread.setName("主线程");System.out.println(thread.getName());new Thread("新线程1"){@Overridepublic void run() {//获取新线程Thread thread1 = Thread.currentThread();//打印新线程的名称System.out.println(thread1.getName());}}.start();}
}

3、static void sleep(long millis)

使线程休眠指定毫秒。

public class ThreadMethod03 {public static void main(String[] args) {String[] strings = new String[]{"十三学得琵琶成","名属教坊第一部","曲罢曽教善才服","妆成每被秋娘妒"};new Thread(){@Overridepublic void run() {for (String string : strings) {System.out.println(string);try {//输出一个字符串休眠 3000 毫秒Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}}.start();}
}

4、void setDaemon(boolean on)

将指定线程设置为守护线程,当被守护线程执行完毕后,守护线程后面会自动停止。

public class ThreadMethod04 {public static void main(String[] args) {Thread thread1 = new Thread("新线程1"){@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("新线程中的:" + i);}}};Thread thread2 = new Thread(){@Overridepublic void run() {for (int i = 0; i < 50; i++) {//当新线程1中5次循环输出完毕后,守护线程后面就会停止,不会完整循环50次System.out.println("守护线程中的:" + i);}}};//将线程2设置为守护线程thread2.setDaemon(true);thread1.start();thread2.start();}
}

5、void join()

插队执行完指定线程。该方法还有重载 join(long millis) 可以指定插队毫秒,插队时间过后,线程按原有方式执行。

public class ThreadMethod05 {public static void main(String[] args) {//创建插队线程Thread thread1 = new Thread(){@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println("插队线程的:" + i);}}};//创建新线程Thread thread2 = new Thread(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("新线程的:" + i);if (i == 2){try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}}}}};thread1.start();thread2.start();}
}

6、static void yield()

暂停当前正在进行的进程,执行其他进程。

public class ThreadMethod06 {public static void main(String[] args) {Demo02 d1 = new Demo02();d1.setName("线程 a");d1.start();Demo02 d2 = new Demo02();d2.setName("线程b");d2.start();}
}class Demo02 extends Thread{@Overridepublic void run() {for (int i = 0; i < 10; i++) {if (i == 2){//当某一线程的 i 等于 2 时,暂停执行它,让其他线程去执行Thread.yield();}System.out.println(Thread.currentThread().getName() + "中的:" + i);}}
}

7、void setPriority(int newPriority)

设置线程的优先级,由 1 ~ 10 表示优先级从小到大,除此以外还有三个字段:

MAX_PRIORITY:最高优先级

MIN_PRIORITY:最低优先级

NORM_PRIORITY:默认优先级

public class ThreadMethod07 {public static void main(String[] args) {Thread thread1 = new Thread("线程1"){@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "的:" + i);}}};Thread thread2 = new Thread("线程2"){@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "的:" + i);}}};thread1.setPriority(10);thread2.setPriority(1);
//        thread1.setPriority(Thread.MAX_PRIORITY);
//        thread2.setPriority(Thread.MIN_PRIORITY);thread1.start();thread2.start();}
}
多线程中的同步

同步:多线程并发执行代码时,在某一线程执行的过程中,CPU 不切换执行其他线程,就是同步。

在多线程中,每一个线程都有自己的工作内存,当线程需要操作共享变量时,要从主存中读取共享变量至自己的工作内存,操作完毕后,将修改后的共享变量的值再写回主存。

在多线程并发中,当多个线程同时操作共享对象时,就会引发安全问题。例如:当线程1修改了共享变量的值但还未写入主存中时,线程2又修改了共享变量的值,这就导致了共享变量的异常。

为了解决这个安全问题,Java 多线程提供了同步机制,同步保证了某一线程可以顺利执行完毕而不用担心共享变量异常这一问题。

实现同步的方法:使用 Synchronized 关键字添加同步锁,在执行被上锁的代码时,CPU 不会切换其他线程。

Thread 类同步方法

1、同步代码块

格式:

synchronized (锁对象){//代码块
}

锁对象和代码块中的代码不需要有任何关联,当在一处使用这个锁对象时,只能等这处用完了锁对象后,其他地方再使用这个锁对象,这也就体现了同步这一概念。

锁对象可以是任意对象,例如:一个字符串:“abc”,一个整型数:1 等等,但我们常用一个类的 .class文件作为一个锁对象。

public class ThreadSynchronized {public static void main(String[] args) {new Thread(){@Overridepublic void run() {new Demo03().print1();}}.start();new Thread(){@Overridepublic void run() {new Demo03().print2();}}.start();}
}class Demo03{public void print1(){//同步代码块,执行此处代码时,不会切换 CPU,直至代码执行完毕synchronized (ThreadSynchronized.class){System.out.println("蜀道之难");System.out.println("难于上青天");}}public void print2(){synchronized (ThreadSynchronized.class){System.out.println("蚕丛及鱼凫");System.out.println("开国何茫然");}}
}

2、同步方法

使用 synchronized 修饰的方法称为同步方法,当一个线程执行同步方法时,其他线程若是想调用该方法,就必须等待,直到该同步方法被执行完毕,其他线程才能调用。

public class ThreadSynchronized02 {public static void main(String[] args) {Demo04 demo04 = new Demo04();//两个线程都去调用 print1() 方法new Thread("线程1"){@Overridepublic void run() {demo04.print1(Thread.currentThread().getName()+ ":");}}.start();new Thread("线程2"){@Overridepublic void run() {demo04.print1(Thread.currentThread().getName() + ":");}}.start();}
}class Demo04{//非静态同步方法的锁对象为:this//同步方法,当一个线程调用该方法时,另一个线程无法调用public synchronized void print1(String name){for (int i = 0; i < 50; i++) {System.out.println(name + "的同步方法测试" + i);}}//静态同步方法的锁对象为该类的字节码文件public static synchronized void print2(){System.out.println("静态同步方法测试1");System.out.println("静态同步方法测试2");System.out.println("静态同步方法测试3");}
}

售票案例:

public class ThreadSynchronized03 {public static void main(String[] args) {//三个线程售票new Ticket().start();new Ticket().start();new Ticket().start();}
}class Ticket extends Thread{private static int ticket = 1;@Overridepublic void run() {while (true){synchronized (Ticket.class){//当卖出100张票后,结束循环if (ticket > 100){break;}System.out.println(Thread.currentThread().getName() + "售出了第:" + (ticket++) + "张票");}}}
}
多线程中的死锁

死锁:多个线程在执行过程中,因为争夺资源而造成的一种相互等待的现象,如果没有外界原因,所有线程都无法继续执行。

死锁形成的原因:系统资源不足,线程推进顺序不当,资源分配不当等等。

public class DeadLock {private static String s1 = "筷子左";private static String s2 = "筷子右";public static void main(String[] args) {new Thread(){@Overridepublic void run() {while (true){synchronized (s1){System.out.println(getName() + " 拿到 " + s1 + " 等待 " + s2);synchronized (s2){System.out.println(getName() + " 拿到 " + s2 + " 开吃");}}}}}.start();new Thread(){@Overridepublic void run() {while (true){synchronized (s2){System.out.println(getName() + " 拿到 " + s2 + " 等待 " + s1);synchronized (s1){System.out.println(getName() + " 拿到 " + s1 + " 开吃");}}}}}.start();}
}

3.2、实现 Runnable 接口

Runnbale 接口概述

在 Java 中,规定了类只能单继承,所以如果为了使用多线程而继承了 Thread 类,那么就无法继承其他类。面对这一情况,Java 提供了 Runnable 接口,通过实现 Runnable 接口,也可以实现多线程。

实现 Runnable 接口后,必须要重写接口中的 run() 方法,run() 方法中的逻辑就是创建的新线程所要执行的逻辑。

Runnbale 开启新线程
  • 定义类实现 Runnable 接口。
  • 重写 Runnable 接口中的 run() 方法,将想要新线程执行的逻辑写入 run() 方法。
  • 将 Runnbale 接口的实例作为参数传递给 Thread 构造器。
  • 创建 Thread 对象。
  • Thread 对象调用 start() 方法开启线程。
常用方法

Runnbale 接口的实例作为参数传递给了 Thread 的构造器,最终还是使用 Thread 类创建了线程对象,所以,使用 Runnable 实现多线程,能够使用的方法还是线程类 Thread 中的方法。

方法 作用
setName(String name) 设置线程的名字
getName() 获取线程的名字
static Thread currentThread() 获取当前的线程
static void sleep(long millis) 使指定线程休眠执行毫秒数
void setDaemon(boolean on) 设置指定线程为守护线程
void join() 使指定线程插队执行
static void yield() 使指定线程让出 CPU,进入等待
void setPriority(int newPriority) 设置线程的优先级
public class RunnableDemo{public static void main(String[] args) {Demo05 demo05 = new Demo05();Thread thread1 = new Thread(demo05);//设置线程名称thread1.setName("新线程1");//获取线程名称System.out.println(thread1.getName());//设置线程优先级thread1.setPriority(10);thread1.start();Thread thread2 = new Thread(new Demo05());thread2.setName("新线程2");thread2.start();}
}class Demo05 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {//获取当前线程名称System.out.println(Thread.currentThread().getName() + "中的:" + i);if (i == 2){try {System.out.println(Thread.currentThread().getName() + "正在休眠");//线程休眠Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}if (i == 5){System.out.println(Thread.currentThread().getName() + "让出了 CPU");//线程让出 CPUThread.yield();}}}
}
龟兔赛跑案例
public class RunnableDemo02 {public static void main(String[] args) {//创建龟兔两个线程Race race = new Race();new Thread(race,"乌龟").start();new Thread(race,"兔子").start();}
}class Race implements Runnable{private static String winner = null;@Overridepublic void run() {for (int i = 1; i < 150; i++) {//判断是否产生胜利者isGameOver(i);//设置每跑1米的休眠时间,目的是为了龟兔都能获取到 cputry {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//当兔子跑的米数为10的倍数时,让它休眠1秒if ("兔子".equals(Thread.currentThread().getName()) && i % 10 == 0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}//跑了多少米System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");}}private void isGameOver(int i){if (i == 100){winner = Thread.currentThread().getName();//跑到100米时,刚进循环就调用了isGameOver() 方法,所以要在这里打印跑了100米的信息System.out.println(winner + "跑了100米");//产生胜利者,结束虚拟机停止程序System.out.println("游戏结束,胜利者是:" + winner);System.exit(0);}}
}

3.3、实现 Callable 接口

Callable 接口概述

多线程的第三种实现方法是实现 Callable 接口,Callable 接口类似于 Runnbale 接口,两者都是为其实例能被一个线程执行而设计的。

实现 Callable 接口后,必须重写接口中的 call() 方法,同理 call() 方法中的逻辑,就是开启的新线程所要执行的逻辑。

与 run() 方法不同的是,call() 方法是有返回值,并且可以抛出异常的。

Callable 开启新线程
  • 定义类实现 Callable 接口。
  • 重写 Callable 接口中的 call() 方法,将想要新线程执行的逻辑写入 call() 方法。
  • 创建 FutureTask 对象,将线程类实例传入。
  • 创建 Thread 对象,传入 FutureTask 对象。
  • Thread 对象调用 start() 方法开启线程。

最后可用 FutureTask 对象调用 get() 方法获取线程的执行结果。

Callable 开启新线程案例

Callable 在开发中的使用并不是很多,仅作了解。

public class CallableDemo01 {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable = new MyCallable(100);//执行 Callable 需要 FutureTask 实现类的支持FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);//创建线程并开启new Thread(futureTask).start();//接收线程运算后的结果Integer result = futureTask.get();System.out.println(result);}
}class MyCallable implements Callable<Integer> {private int num;public MyCallable(int num) {this.num = num;}@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= num; i++) {sum += i;}return sum;}
}

HAPPY TOGETHER.

本篇结束,各位看官我们下篇见 (∩_∩)~~

-Czx.

2021.1.27

JavaSE基础二十:Java 多线程(线程基础知识、Java 多线程、Java 实现多线程(继承 Thread 类、实现 Runnable 接口、实现 Callable 接口))相关推荐

  1. java多线程(一)-Thread类和Runnable接口

    public class Thread extends Object implements Runnable Thread通过实现Runnable实现多态关系. Java中实现多线程,最基本2种方式: ...

  2. 继承Thread类使用多线程

    java实现多线程有两种方式,一种是继承Thread类,另外一种就是实现Runnable接口. 两种实现方法的优缺点: 使用Thread类实现多线程局限性就是不支持多继承,因为java是不支持类多继承 ...

  3. html解析图片url,并用继承Thread类的多线程下载

    html解析图片url,并用继承Thread类的多线程下载 pom依赖 Java代码 把对应网页的img元素,通过继承Thread类的多线程下载下来. pom依赖 <dependencies&g ...

  4. java 文件路径表达式_Java基础(二十二) Lambda表达式和File类

    函数式编程思想 强调的是做什么,而不是以什么样的方式来做,它忽略了面向对象的复杂语法,只要能够获取到结果,谁去做的,怎么做的,都不重要,重要的是结果,不重视过程. 冗余的Runnable代码 传统的写 ...

  5. shell基础二十篇 一些笔记

    shell基础二十篇 转自 http://bbs.chinaunix.net/thread-452942-1-1.html 研讨:Bash 内建命令 read (read命令更具体的说明见博客收藏的一 ...

  6. 二十五岁零基础转行做软件测试怎么样?

    俗话说得好:男怕入错行,女怕嫁错郎,那么你的入行方向决定着你的整个职业发展!! 所以在考虑要进入什么行业之前,必须要了解清楚这个行业的发展前景怎么样? 我们都知道,随着社会的发展,互联网行业涉及也越来 ...

  7. mysql循环查询一个表中的数据并进行修改_JavaScript学习笔记(二十四)-- MYSQL基础操作...

    MYSQL mysql 是一个数据库的名字 和 php 合作的比较好的数据库 之前我们说过一个问题,前端向后端索要数据,后端就是去数据库中查询数据,返回给前端 接下来就聊聊使用 php 操作数据库 M ...

  8. Kubernetes生产实践系列之二十九:Kubernetes基础技术之容器关键技术实践

    一.前言 在文章<Kubernetes生产实践系列之二十八:Kubernetes基础技术之容器关键技术介绍>中,对于Docker容器技术依赖的namespace.cgroup和UnionF ...

  9. java基础 通过继承Thread类和实现Runnable接口创建线程

    java 创建线程 Java中,线程也是一种对象,但不是任何对象都可以成为线程. 只有实现了Runnable接口或继承了Thread类的对象才能成为线程. 继承Thread类 //格式: class ...

最新文章

  1. Udacity机器人软件工程师课程笔记(八)-ROS Turtlesim 包的相关命令
  2. 第7集 构造函数中抛出的异常
  3. Qt QDialog将窗体变为顶层窗体(activateWindow(); 和 raise() )
  4. LINQ的基本语法中八个关键字用法说明
  5. 关于合成的拷贝控制成员的一点问题
  6. django_form校验
  7. python逻辑量有什么_Python中的逻辑运算符有什么?
  8. Android Notification 手机系统横幅弹出提示框调用,横幅通知,RemoteViews使用实例
  9. PhpSpreadsheet 电子表格(excel) PHP处理笔记
  10. win11虚拟内存怎么修改 Windows11修改虚拟内存的步骤方法
  11. linux6.4网络yum 源,CentOS 6.4使用本地yum源
  12. 通证指数:ChaiNext系列指数基金上线
  13. 阿里巴巴矢量图标库 iconfont 的使用方法
  14. 蓝牙模块配置串口通讯
  15. 金士顿u盘分区工具_U盘PE安装ESD格式系统 图文教程
  16. CTF PWN之heap入门 unlink
  17. ios 直播间点赞动画
  18. 100个经典C语言程序(益智类)
  19. 全国降雨量数据、气温数据、风速数据
  20. C语言:上机编程题集

热门文章

  1. 奇迹是否会发生?乙肝从大三阳到小三阳到自愈!
  2. 大学,男生因为孤独而优秀,女生因为优秀而孤独……
  3. 基于51单片机的智能温度检测仪
  4. 几个有趣的bat脚本
  5. 联发科营收复苏,但重回辉煌不容易
  6. 魔兽世界最新服务器推荐,《魔兽世界》全新第七大区服务器推荐
  7. python个人信息查询代码_80行python代码查询你想要的英雄信息
  8. 和时间赛跑之优化高斯金字塔建立的计算过程
  9. blender常见问题
  10. ajax获取接口数据