17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)

简介: 17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)

前置Thread线程基础-并行和并发

并行就是同时执行,并发就是在交替执行

在操作系统中,安装了很多程序,并发指的是在一段时间内宏观上多个程序同时执行,这个在单个CPU系统中,每一个时刻只有一个程序执行,即微观上这些程序是分时交替的执行,只不过给人感觉是在同时运行,因为分时交替运行时间非常短暂

现在而言都是多核CPU,则这些并发执行程序可以分配到不同的处理器上(CPU),实现多个任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序就可以同时执行了,你的电脑CPU核心越多你电脑的性能就相对更加强悍

PS:单核处理的计算机肯定是不能并行处理多个任务,只能是多个任务在单个CPU上并发需要运行,同理线程也是一样的从宏观的角度而言线程并行运行从微观角度看就是串行运行,即线程是一个一个去执行的,当系统只有一个CPU是,线程会以某种顺序执行多个线程,这个情况称之线程调度【线程提供CPU时间争抢】

17.1 进程和线程


17.1.1 进程

进程:程序是静止的,只有真正运行时的程序,才被称为进程。

进程是程序的一次执行过程,是系统运行程序的基本单元,系统运行一个程序即在运行一个进程【从创建、运行、消亡的一个过程】,每一个进程都有自己一个独立的空间【内存空间】,一个应用程序(进程)可以同时运行多个线程

特点:

  • 单核CPU在任何时间点上。
  • 只能运行一个进程。
  • 宏观并行、微观串行。
  • 进程是一个独立程序单元并且拥有自己独立空间
  • 一个进程中可以同时运行多个线程
  • 进程是可以完成多个任务交替执行的,这样做开发成本高,进行是独立的进程与进程之间是无法进行“通信”的

17.1.2 线程

线程:又称轻量级进程(Light Weight Process)。

  • 线程是在进程的内部执行,并且可以存在多个,彼此之间共享进程的内存区域
  • 程序中的一个顺序控制流程,同时也是CPU的基本调度单位。
  • 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。

比如:

  • 迅雷是一个进程,当中的多个下载任务即是多个线程。
  • Java虚拟机是一个进程,默认包含主线程(main),通过代码创建多个独立线程,与main并发执行。
  • 可以利用这个轻量级的开发完成一个进程内部程序交替执行效果(线
    程并发执行)

17.1.3 进程和线程区别

  • 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
  • 一个程序运行后至少有一个进程。
  • 一个进程可以包含多个线程,但是至少需要有一个线程。
  • 进程间不能共享数据段地址,但同进程的线程之间可以。

进程: 有独立的内存空间,进程中数据存放的空间是独立的并且至少有一个线程

线程:堆空间是共享的,栈空间是独立的,线程消耗的资源要比进程小,并且可以多个线程存在同一个进程中

PS:需要知道了解的知识点:

  1. 因为一个程序中有多个线程并发运行,那么从微观的角度而言是有先后顺序的,那么线程执行的顺序是取决于CPU的调度【线程争抢CPU时间片】,程序猿只能进行干涉,在不加干涉前提下线程执行就会出现很多随机性
  2. Java程序进程中最少包含两个线程,一个是主线程就是main()方法【它在执行方法是或者执行程序时它的优先级永远最高】,另外一个垃圾回收机制线程(GC)【守护线程】,每当Java执行一个类的时候,实际上都会启动一个JVM,

每一个JVM实际上就是在操作系统中启动了一个线程,Java本身就被垃圾回收机制所守护,所以Java运行时至少启动了两个线程

  1. 由于创建一个线程开销远比创建一个进程开销要小很多,那么我们在开发多任务运行时,通常会优先考虑创建多线程,而不是多进程
  2. 所有的线程轮流使用CPU,每一个线程都会得到CPU分配的时间片,这个分配“尽量”平均分配CPU资源,但是这个效果不一定能达到,所以同优先级的线程对CPU时间的争抢是存在随机性的,可以对线程进行优先级设置来概念获取CPU时间片的几率,优先级越高几率越大,优先级越低几率越小

17.1.4 线程组成

任何一个线程都具有基本的组成部分:

  • CPU时间片:操作系统(OS)会为每个线程分配执行时间。
  • 运行数据:
    堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
    栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
  • 线程的逻辑代码。

17.2 创建线程


Java中创建线程主要有两种方式:

  1. 继承Thread类。此时子类就是线程类
  2. 实现Runnable接口。此时实现类不是线程类,使用Thread类才可
    成为线程
    无论是上述两种方式中那种方式都需必须重写run方法实现线程逻辑
  1. 实现Callable接口, 此实现方方式并不是线程类,主要针对的是线程池提供
  2. 线程池可以帮组我们创建线程并进行管理操作

如果实现线程需要继承Thread或实现Runnable接口,但是无论是那种都需要实现一个必要核心方法

这个run方法是提供线程实现的核心逻辑,需要线程什么样需求代码,就将将代码写入到这个线程中

17.2.1 继承Thread类

步骤:

  • 编写类、继承Thread。
  • 重写run方法。
  • 创建线程对象。
  • 调用start方法启动线程。

案例演示:

MyThread类:

public class MyThread extends Thread {
  
  public MyThread() {
    // TODO Auto-generated constructor stub
  }
  public MyThread(String name) {
    super(name);
  }
   
  @Override
  public void run() {
    for(int i=0;i<100;i++) {
      System.out.println("子线程:"+i);
    }
  }
}

TestMyThread类:

public class TestThread {
  public static void main(String[] args) {
    //1创建线程对象
    MyThread myThread=new MyThread();
    myThread.start();//myThread.run()
    //创建第二个线程对象
    MyThread myThread2=new MyThread();
    myThread2.start();
    //主线程执行
    for(int i=0;i<50;i++) {
      System.out.println("主线程======================"+i);
    }
  }
}

获取线程名称:

  • getName()。
  • Thread.currentThread().getName()。
  public void run() {
    for(int i=0;i<100;i++) {
      //this.getId获取线程Id 
      //this.getName获取线程名称
      //第一种方式 this.getId和this.getName(); 
      //System.out.println("线程id:"+this.getId()+" 线程名称:"+this.getName()+" 子线程............."+i);
      //第二种方式 Thread.currentThread() 获取当前线程
      System.out.println("线程id:"+Thread.currentThread().getId()+" 线程名称:"+Thread.currentThread().getName()+" 子线程。。。。。。。"+i);
    }
  }
public static void main(String[] args) {
    //1创建线程对象
    MyThread myThread=new MyThread("我的子线程1");
    //2启动线程,不能使用run方法
    //修改线程名称
    //myThread.setName("我的子线程1");
    
    myThread.start();//myThread.run()
    
    //创建第二个线程对象
    MyThread myThread2=new MyThread("我的子线程2");
    
    //myThread2.setName("我的子线程2");
    myThread2.start();

    //主线程执行
    for(int i=0;i<50;i++) {
      System.out.println("主线程======================"+i);
    }
  }

17.2.2 应用案例

实现四个窗口各卖100张票。

TicketWin类

public class TicketWin extends Thread{
  
  public TicketWin() {
    // TODO Auto-generated constructor stub
  }
  public TicketWin(String name) {
    super(name);
  }
  private int ticket=100;//票
  @Override
  public void run() {
    //卖票功能
    while(true) {
      if(ticket<=0) {
        break;
      }
      System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
      ticket--;
    }
  }
}

TestWin类:

public class TestWin {
  public static void main(String[] args) {
    //创建四个窗口
    TicketWin w1=new TicketWin("窗口1");
    TicketWin w2=new TicketWin("窗口2");
    TicketWin w3=new TicketWin("窗口3");
    TicketWin w4=new TicketWin("窗口4");
    //启动线程
    w1.start();
    w2.start();
    w3.start();
    w4.start();
  }
}

17.2.3 实现Runnable接口

步骤:

  • 编写类实现Runnable接口、并实现run方法。
  • 创建Runnable实现类对象。
  • 创建线程对象,传递实现类对象。
  • 启动线程。

案例演示:

MyRunnable类:

public class MyRunnable implements Runnable{

  @Override
  public void run() {
    for(int i=0;i<100;i++) {
      System.out.println(Thread.currentThread().getName()+" ........."+i);
    }
  }

}

TestMyRunnable类:

public class TestRunnable {
  public static void main(String[] args) {
    //1创建MyRunnable对象,表示线程要执行的功能
    MyRunnable runnable=new MyRunnable();
    //2创建线程对象
    Thread thread=new Thread(runnable, "我的线程1");
    //3启动
    thread.start();
    
    for(int i=0;i<50;i++) {
      System.out.println("main............"+i);
    }
  }
}

17.2.4 课堂案例

实现四个窗口共卖100张票。

Ticket类:

public class Ticket implements Runnable {
  private int ticket=100;//100张票

  
  @Override
  public void run() {
    while(true) {
      if(ticket<=0) {
        break;
      }
      System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
      ticket--;
    }
    
  }
}

TestTicket类:

public class TestTicket {
  public static void main(String[] args) {
    //1创建票对象
    Ticket ticket=new Ticket();
    //2创建线程对象
    Thread w1=new Thread(ticket, "窗口1");
    Thread w2=new Thread(ticket, "窗口2");
    Thread w3=new Thread(ticket, "窗口3");
    Thread w4=new Thread(ticket, "窗口4");
    //3启动线程
    w1.start();
    w2.start();
    w3.start();
    w4.start();
  }
}


17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中):https://developer.aliyun.com/article/1580255

目录
相关文章
|
9天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
120 60
【Java并发】【线程池】带你从0-1入门线程池
|
5天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
51 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
21天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
87 14
|
24天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
53 13
|
25天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
45 17
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
57 26
|
3月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
269 2
|
4月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
4月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
65 10

热门文章

最新文章