这是一个不完整的详细Java多线程,但对于初学者足够了,相信我,你会爱上她的

云惠网小编 2022年1月14日03:17:42
评论
6881字阅读22分56秒
摘要

本文介绍了Java多线程的创建、使用、生命周期以及线程安全,并进行了举例说明!欢迎交流讨论!

广告也精彩

Thread类常用的方法

/*
解决线程安全:同步方法
*/
public class Demo8 {
public static void main(String[] args) {
Tacket2 tacket2 = new Tacket2();
//通过匿名内部类创建线程
new Thread(new Runnable() {
//重写run方法实现线程执行逻辑
@Override
public void run() {
for (int i = 0; i < 40 ; i++) {
//售票员1卖票(线程2)
tacket2.sellTacket();
}
}
},"售票员1").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40 ; i++) {
//售票员2卖票(线程2)
tacket2.sellTacket();
}
}
},"售票员2").start();
}
}
class  Tacket2{
private int number =30; //票有30张
public synchronized void sellTacket(){ //同步方法
if (number > 0){//票数大于0才能卖
try {
Thread.sleep(10); //等10毫秒,效果好点
} catch (InterruptedException e) {
e.printStackTrace();
}
//票数-1
System.out.println(Thread.currentThread().getName()+"卖票,剩余票:"+number--);
}
}
}

死亡(Dead)

想对谁互斥访问,同步锁里面就写谁,锁对象可以是任意类型

创建线程的步骤

死锁

Java中同步代码块是通过添加 synchronized 关键字实现的,将synchronized 关键字用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问(同时只能由一个线程访问该该区块中的资源)。

那这里的Java多线程指的就是Java开发中,程序启动多个线程实现并发执行,提高CPU的利用率。

注:线程不用手动结束,执行完线程其自动结束

通过上述方法创建线程,不调用任何方法即为新建一个线程,此时该线程只有JVM为其分配的内存,并初始化了实例变量的值,没有执行线程任何操作。

  • 静态方法:当前类的Class对象
  • 非静态方法:this
代码示例

代码示例

  • 创建线程
  • 通过线程对象调用start()方法。

下面就对这三种方法进行分别介绍

就像不是所有的线程都能完成一样,有些锁可能就不能释放了,变成了死锁。当不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

实现Runnable接口

本篇文章主要介绍通过继承Thread类和实现Runnable接口这两种方式来创建线程。 具体如下:

锁机制

使用匿名内部类创建线程

创建线程的步骤

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该该线程挂起,

阻塞(Blocked)

那出现上述的肯定是不行的,下面介绍三种方法来解决线程安全问题

  • 创建线程
  • 通过线程对象调用start()方法。

同步代码块

举个例子解释一下安全问题:

线程的就绪状态是在其调用start()方法之后,此时线程启动,即变成了就绪状态,JVM此时会为其创建方法调用栈和程序计数器。(注意这个时候只是启动线程,没有线程的执行行为)。

运行(Running)

并发:指两个或多个事件可以在同一个时间段内发生。也就是说在同一个时刻只能执行一条指令,但多个指令可以被快速轮换执行,使得在宏观上具有多个进程同时执行的效果

继承Thread类

同步方法的锁对象:

比如在我们在线上购票的过程中,是很多人可以同时购买的(多线程),我们首先要看看有没有票(涉及到读操作),然后如果有票的话,就可以买票了(涉及到写操作)。理论上,每次有人购票的时候,系统就会让票的数量减少一个,但是如果多人同时购票的话(多个线程访问同一个资源),会出现多人同时购票,而系统只让票的数量减一,这些人就会是重票。

线程的使用步骤

系统中的线程由于实际情况并不总是能够顺利执行完毕,但如果稍微有情况不能执行就杀死线程会造成很大的浪费,这时候就会出现线程阻塞的概念,阻塞的线程还可以通过一定的条件转化为就绪状态,继续等待下次的运行。

释放锁的操作

代码示例

线程的使用步骤

零基础学习之Java多线程

    • 概述
    • 线程的创建
      • 继承Thread类
        • 创建线程的步骤
        • 线程的使用步骤
        • 代码示例
      • 实现Runnable接口
        • 创建线程的步骤
        • 线程的使用步骤
        • 代码示例
        • 使用匿名内部类创建线程
          • 代码示例
      • Thread类常用的方法
    • 线程生命周期
    • 线程安全
      • 同步代码块
        • 语法格式
        • 代码示例
      • 同步方法
        • 语法格式
        • 代码示例
      • 锁机制

可以锁定,但又不能一直锁定,所以什么时候释放锁也是一个关键点。下面介绍释放锁的操作:

  • 线程调用了sleep()方法
  • 线程试图获取一个同步监视器,但该同步监视器正被其他线程持有;
  • 线程执行过程中,同步监视器调用了wait(),让它等待某个通知(notify / notifyAll);
  • 线程执行过程中,同步监视器调用了wait(time)
  • 线程执行过程中,遇到了其他线程对象的加塞(join);
  • 线程被调用suspend方法被挂起(已过时,因为容易发生死锁);

阻塞状态的线程,可以通过下面的形式重新转换到就绪状态,等待下次的运行:

要深入学习多线程,首先我们需要找到什么是线程,根据百度百科的定义:线程(thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。。我们了解到线程是进程的实际运行单位,那问题来了什么是进程呢?根据百度百科定义:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,进程是程序的实体。这个就比较好理解了,进程不就是我们运行的程序吗?那这样来看,线程就是某个程序的一次实际的运行(一个程序可以打开多次,一个进程可以有多个线程组成)。

  • 定义一个类,让其实现Runnable接口
  • 在该类中重写Runnable接口中的run()方法
  • 创建该类的对象(线程的任务对象)
  • 通过Thread类的有参构造方法创建线程对象(真正的线程对象)
public synchronized void methodname(){
//可能会产生线程安全问题的代码
}

同步方法

语法格式

其中,各个状态说明如下:

其中:run()方法的方法体就是线程的执行逻辑,也称为线程执行体。

线程生命周期


/*
实现Runnable类创建线程
*/
public class Demo2 {
public static void main(String[] args) {
//创建自定义类对象  线程任务对象
MyThread1 runnable = new MyThread1();
//创建线程对象
Thread thread = new Thread(runnable,"xiaocheng");
//调用start方法,开启线程
thread.start();
}
}
//实现Runable类,创建线程
class MyThread1 implements Runnable{
/*
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
System.out.println("实现Runable类,创建线程");
}
}

这里说的线程安全指的是多线程才会有的安全性问题。这是因为当系统同时运行多个线程的时候,会出现多个线程同时访问同一个资源的情况,如果仅仅是涉及到读操作的话,不会出现问题。但是涉及到写(即修改)就会出安全问题。

就绪(Runnable)

上面说到通过继承Thread类的方式实现线程有Java单继承的缺点,针对单继承这样一个问题,Java类是通过实现接口来解决的。那么创建线程是不是也可以通过接口呢?答案是肯定的。在Java中,可以通过实现Runnable接口的方式来创建线程。

注:在Thread类的源码中可以看出,其也是实现了Runnable接口

线程遇到如下情况,会进入阻塞状态:

创建线程是使用多线程的第一步。首先先介绍了Java创建线程的方式,创建了线程才会有多个线程的情况。Java创建多线程有四种方式:

线程总归要结束的,不管是被动的还是主动的(执行完了),

在Java中,使用Thread类来表示线程,所有的线程都是Thread类的对象或者其子类的实例。(Thread类Java.lang包下的类)

处于就绪状态的线程获得CPU资源,那该线程就会调用run()方法,开启线程的执行逻辑,此时线程处于运行状态。(所谓多线程,指的是系统中有多个CPU资源,多个线程获得CPU资源后处于运行状态)。

代码示例

新建(New)

概述

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。
  • 当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。

通过继承方式实现的线程,会有Java单继承的缺点

任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,这种锁定保证了线程安全。

语法格式

  • 线程的sleep()时间到;
  • 线程成功获得了同步监视器;
  • 线程等到了通知(notify / notifyAll);
  • 线程wait的时间到了
  • 加塞的线程结束了;
  • 被挂起的线程又被调用了resume恢复方法(已过时,因为容易发生死锁);
  • public void run():实现多线程的执行逻辑(必不可少的方法)
  • public void start() :线程开始执行的方法 (线程启动方法)
  • public static void sleep(long millis) :暂停当前线程的执行(参数为毫秒数)
  • public String getName() :获取当前线程名字
  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用(可以和其他的一起用)
  • public final boolean isAlive():判断线程的状态,是否为活动状态(非终止)
  • public final int getPriority() :获取线程的优先级
  • public final void setPriority(int newPriority) :改变线程的优先级
    • public final void stop():使线程停止执行
  • public final void setDaemon(boolean on):指定线程设置为守护线程。(必须在线程启动之前设置,否则会报IllegalThreadStateException异常)
  • public final boolean isDaemon():判断当前线程是否为守护线程
synchronized(同步锁){
//需要互斥的代码
}

线程安全

  • run()方法执行完成;
  • 线程执行过程中抛出了一个未捕获的异常(Exception)或错误(Error)
  • 直接调用该线程的stop()来结束该线程(已过时,因为容易发生死锁)
  • 定义一个类,让其继承Thread类
  • 在该类中重写Thread类的run()方法、
  • 创建该类的实例,即创建了线程对象

/*
匿名内部类创建线程
*/
public class Demo3 {
public static void main(String[] args) {
//匿名内部类,一个参数
new Thread(new Runnable() {
//依然需要重写run方法,实现线程的执行逻辑
@Override
public void run() {
System.out.println("匿名内部类创建线程1");
}
}){
}.start();
//匿名内部类,两个参数
new Thread(new Runnable() {
//依然需要重写run方法,实现线程的执行逻辑
@Override
public void run() {
//调用currentThread().getName()方法,获取当前线程的名字
System.out.println(Thread.currentThread().getName());
}
},"匿名内部类创建线程2 ").start();
}
}
/*
继承Thread创建线程
*/
public class Demo1 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread myThread = new MyThread();
//调用start方法,开启线程
myThread.start();
}
}
//继承Thread方法,自定义线程
class MyThread extends Thread{
/*
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
System.out.println("继承Thread方式创建线程");
}
}

这里的同步锁只是一个概念,指的是为你想实现互斥访问资源的对象上标记了一个锁。

从上面的例子我们可以看出来,线程对象实际都是Thread实例。那这样来说,如果我们使用线程的话,除了我们自定以类中的方法外,Thread类中的方法也是必须要知道的。那下面就介绍下Thread类中常用的方法:

不会释放锁的操作

代码示例

注意:程序只能对新建状态的线程调用start(),并且只能调用一次,如果对非新建状态的线程,如已启动的线程或已死亡的线程调用start()都会报错IllegalThreadStateException异常。

在这里插入图片描述

匿名内部类不熟的话,可以点击上面的链接看下我写过的博客

有的方法执行了,当前线程会暂停,但此时该线程并不会释放锁,仍然保持锁定状态。具体方法如下:

  • 同步代码块
  • 同步方法
  • 锁机制
  • 继承Thread类
  • 实现Runnable接口
  • 实现实现Callable(本文未涉及)
  • 使用线程池方式(本文未涉及)
/*
解决线程安全:同步代码块
*/
public class Demo7 {
public static void main(String[] args) {
Tacket1 tacket1 = new Tacket1();
//通过匿名内部类创建线程
new Thread(new Runnable() {
//重写run方法实现线程执行逻辑
@Override
public void run() {
//售票员1卖票(线程1)
for (int i = 0; i < 40 ; i++) {
tacket1.sellTacket();
}
}
},"售票员1").start();
new Thread(new Runnable() {
//重写run方法实现线程执行逻辑
@Override
public void run() {
for (int i = 0; i < 40 ; i++) {
//售票员2卖票(线程2)
tacket1.sellTacket();
}
}
},"售票员2").start();
}
}
class  Tacket1{
private int number =30; //票有30张
public void sellTacket(){ //票数大于0才能卖
synchronized (this){ //this作为锁,是因为对于这几个线程,Ticket的this是同一个
if (number > 0){
try {
Thread.sleep(10); //等10毫秒,效果好点
} catch (InterruptedException e) {
e.printStackTrace();
}
//票数-1
System.out.println(Thread.currentThread().getName()+"卖票,剩余票:"+number--);
}
}
}
}

在Java中,同步方法使用的也是synchronized 关键字,不同于同步代码块的是,同步方法直接将synchronized 关键字修饰方法。这使得当线程执行该方法时候,其他线程就不能执行该方法,也实现了互斥访问资源(方法里有访问资源的操作)。

线程的创建

前面说到创建线程和启动线程,还有常用方法里说到的线程的停止,这些都是线程在整个执行过程中可能出现的状态。总的来说,线程在其生命周期内(执行过程中)一共有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。
线程生命周期中的状态转换图如下:

既然创建多线程是通过继承Thread类,然后创建类的实例的方式实现的。那如果是本身这个线程只用一次,在其他地方没有调用的情况,是不是可以通过匿名内部类来创建线程呢? Why not?
下面用一个例子来说明使用匿名内部类如何来创建线程:

本文转自 https://blog.csdn.net/weixin_44480968/article/details/122398584

腾讯云618
未分类
云惠网小编
SpringCloud -- Config、Bus解析

SpringCloud — Config、Bus解析

1、Config1.1、概述简介1. 分布式面临的问题微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要...
Java数据结构-了解复杂度

Java数据结构-了解复杂度

2.实例分析与计算  四.写在最后  // 计算斐波那契递归fibonacci的时间复杂度 int fibonacci(int N) { return N < 2 ? N : fibonacci...
[深度解剖C语言] --关键字 static

[深度解剖C语言] –关键字 static

static ---最名不副实的关键字目录1.static修饰全局变量2.static修饰函数3.static修饰局部变量static的作用:1.static修饰全局变量我们创建两...
Java数据结构-认识顺序表

Java数据结构-认识顺序表

目录二.顺序表1.概念及结构2.顺序表的实现打印顺序表获取顺序表的有效长度在pos位置新增元素判断是否包含某个元素查找某个元素对应的位置获取/查找pos位置的元素给pos位置的元素...
腾讯云618

发表评论