【并发编程】线程的基础知识篇

简介: 【并发编程】线程的基础知识篇

1.进程与线程的区别

(1)什么是进程和线程

  • **进程:**是系统进行分配和资源管理的最小单位。
  • **线程:**进程的一个执行单元,是进程内调度的实体,CPU调度和分派的最小单位,是程序执行的最小单位。

(2)线程与进程的区别

  • 进程有自己独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据来维护代码块、堆栈段和数据段。
  • 线程是共享程序中的数据,使用相同的地址空间,因此CPU切换一个线程的花费远比进程小。
  • 线程之间通信更方便,同一进程下线程共享全局变量、静态变量等数据,而进程之间通信以通信方式进行。


f0f8735350bb4d3d9fc9a1c9266b8375.jpg

2.线程的状态相互转换

(1)线程的六种状态

  • **新线程(NEW):**新创建一个线程对象,但还没有调用start()方法。
  • **运行(RUNNABLE):**处于可运行状态的线程正在JVM中执行,但它可能正在等待来自操作系统中的其他资源,例如处理器。
  • **阻塞(BLOCKED):**线程阻塞于synchronized锁,等待获取synchronized锁的状态的线程。
  • **等待(WAITINT):**obj调用wait()、join()等方法表示该线程进入等待状态,等待其他线程操作。
  • **超时等待(TIME_WAITING):**obj.wait(时间参数),Thread.join(),该状态的线程不同于WAITING,假如没有唤醒操作,它可以在指定时间内自行到RUNNABLE状态。
  • **终止(TERMINATED):**表示该线程已经执行完毕,死亡状态。

(2)线程状态验证

  • RUNNABLE状态
public static void main(String[] args) {
      new Thread(()->{
          try {
              System.in.read();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }).start();
}


826d5cf2e7994d6d9a8df71bdf6aba65.jpg


  • BLOCKED和TIME_WAITING状态
public static void main(String[] args) {
        Object obj = new Object();
        new Thread(()->{![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/c22e0fb0dac14470ac4867c266bf6a7d.jpeg#pic_center)
            synchronized (obj){
                try {
                    //当线程拿到锁,无限睡眠,不释放锁
                    System.out.println("线程1拿到锁");
                    Thread.sleep(100000000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            synchronized (obj){
                //线程2模拟拿不到锁
                System.out.println("线程2拿到锁");
            }
        }).start();
    }


f2fa79784cff4edca5195aff49dcdc9c.jpg


4298a7060e8643bcbfb1431deeb79668.jpg

  • WAITING状态
public static void main(String[] args) {
        Object obj = new Object();
        new Thread(()->{
            synchronized (obj){
                try {
                    //获取锁对象调用wait()让其进入等待状态
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


2b7bd86126d743b5aaab1e4ea37615ea.jpg

(3)线程的状态转换

d23e5fd0584a43ab8b4ad855d2615759.jpg

3.创建线程的方式

(1)继承Thread类重写run()方法

public class MyThread extends Thread {
    @Override
    public void run(){
        System.out.println("线程运行");
    }
}
public static void main(String[] args) {
     MyThread myThread = new MyThread();
     myThread.start();
}

0da72598198c43308beec8dfa9a60793.jpg

(2)实现Runnable接口重写run()方法

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("线程运行");
    }
}
public static void main(String[] args) {
     new Thread(new MyRunnable()).start();
}

f624a8cda01e4d1b9035e937861ac2aa.jpg

(3)实现Callable接口重写call()方法

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("线程运行");
        return 1;
    }
}
public static void main(String[] args) {
    MyCallable callable = new MyCallable();
    FutureTask<Integer> futureTask= new FutureTask<>(callable);
    new Thread(futureTask).start();
}

8f8fe0447db349f5881893f65b169855.jpg

4.线程的挂起和恢复

(1)什么是线程的挂起

  • 线程的挂起操作实质上就是使线程进入“非可执行”的状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行。
  • 当线程挂起后,可以通过重新唤醒线程来使之回复运行。

(2)如何挂起线程

  • 被废弃的方法
  • thread.suspend()该方法不会释放线程所占的资源。如果使用该方法将某个线程挂起,则可能会使其他等待资源的线程死锁。
  • thread.resume()方法本身并无问题,但是不能独立于suspend()方法存在。
  • JDK新的挂起唤醒方法
  • wait()暂停执行、放弃已经获得的锁、进入等待状态。
  • notify()随机唤醒一个在等待的线程。
  • notifyAll()唤醒所有在等待锁的线程,自行抢占CPU资源。

(3)suspend()、resume()案例

public class SuspendDemo implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行run()方法,调用suspend()方法之前");
        /**
         * 线程挂起(废弃的方法)
         */
        Thread.currentThread().suspend();
        System.out.println(Thread.currentThread().getName()+"执行run()方法,调用suspend()方法之后");
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new SuspendDemo());
    thread.start();
    /**
      * 对主线程进行休眠,以防目标线程先执行了唤醒操作,在执行挂起操作
      */
    Thread.sleep(3000L);
    thread.resume();
}

e7c73981932347b1b38e138ddfbc4180.jpg

  • 注意:suspend()挂起时,不会释放锁资源,会发生死锁,如果主线程不休眠很容易让线程先调用唤醒,从而让线程一直处在挂起的状态。

50004c4fff1e4f8cb9e1d20a2767aad6.jpg

(4)wait()、notify()案例

public class WaitDemo implements Runnable{
    private static Object object = new Object();
    @Override
    public void run() {
        synchronized (object){
            try {
                System.out.println(Thread.currentThread().getName()+"获取锁资源");
                object.wait();
                System.out.println(Thread.currentThread().getName()+"执行业务逻辑");
                System.out.println(Thread.currentThread().getName()+"释放锁资源");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread(new WaitDemo2(),"线程1");
    thread1.start();
    Thread.sleep(3000L);
    synchronized (object){
        object.notify();
    }
}

d3327f9aca6648f6869a286362799a83.jpg

5.线程的中断操作

(1)stop()废弃方法,开发中不要使用,因为一旦调用,线程就会立刻停止,因此有可能会引发线程安全性问题。

public class InterruptedDemo extends Thread{
    private int i = 0;
    private int j = 0;
    @Override
    public void run() {
        i++;
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        j++;
    }
    public void printf(){
        System.out.println("i:"+i);
        System.out.println("j:"+j);
    }
}
public static void main(String[] args) throws InterruptedException {
    InterruptedDemo thread1 = new InterruptedDemo();
    thread1.start();
    Thread.sleep(1000L);
    thread1.stop();
    thread1.printf();
}

1152d174e61e410aa67304b994ca9f59.jpg


6e21838b3c2c4b508ae26cca198adac3.jpg

(2)Thread.interrupt()方法,改变线程终止的标识,默认是false,线程执行,调用interrupt()方法标识改成true,线程停止执行。

public class InterruptedDemo extends Thread{
    @Override
    public void run() {
        System.out.println("线程终止标识:"+Thread.currentThread().isInterrupted());
       while (!Thread.currentThread().isInterrupted()){
           System.out.println("业务逻辑执行");
       }
        System.out.println("线程终止标识:"+Thread.currentThread().isInterrupted());
    }
}
public static void main(String[] args) throws InterruptedException {
    InterruptedDemo thread1 = new InterruptedDemo();
    thread1.start();
    Thread.sleep(1);
    thread1.interrupt();
}

36799524ac9c43439f00625c88076122.jpg


272f026bd6f64524b394f7f19bc86996.jpg

(3)自定义标识,通过判断来终止线程执行

public class InterruptedDemo extends Thread{
    //注意这里一定要加上volatile,防止指令重排
    private static volatile boolean FLAG = true;
    @Override
    public void run() {
        System.out.println("线程执行标识:"+FLAG);
       while (FLAG){
           System.out.println("业务逻辑执行");
       }
        System.out.println("线程执行标识:"+FLAG);
    }
}
public static void main(String[] args) throws InterruptedException {
    InterruptedDemo thread1 = new InterruptedDemo();
    thread1.start();
    Thread.sleep(1);
    FLAG = false;
}


e76f01d6344b41a18aa8aae460efce71.jpg


ca89410bf56644cda8c71d21f98e7e2f.jpg

6.线程的优先级

线程的优先级告诉程序该线程的重要程度有多大,如果有大量线程被堵塞,都在等待运行,程序会尽可能的先运行优先级大的那个线程。但是这并表示优先级低的线程不会先运行。若线程的优先级较低,只不过表示它被允许运行的机会小一些。

线程的优先级设置可以为1-10的任意数值,1的优先级最小,10的优先级最大,Thread类中定义了三个线程的优先级,MIN_PRIORITY(1)、NORM_PRIORITY(5)、MAX_PRIORITY(10),一般情况下是这几个常量值,不建议自行设置其他值。

不同平台,对线程的优先级的支持不同。不能过度依赖线程的优先级。

线程优先级用处:需要快速处理的任务,设置高的优先级,不是很急的任务,设置低的优先级慢慢处理。

//setPrioity(num),设置最大优先级
thread.setPriority(Thread.MAX_PRIORITY);
  • 线程优先级测试demo
public class PriorityDemo implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName()+":运行");
        }
    }
    public static void main(String[] args) {
        Thread thread1 = new Thread(new PriorityDemo(),"线程1");
        Thread thread2 = new Thread(new PriorityDemo(),"线程2");
        //设置线程1的优先级最大
        thread1.setPriority(Thread.MAX_PRIORITY);
        //设置线程2的优先级最小
        thread2.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
    }
}

7.守护线程

  • 线程分类
  • 用户线程:用户自己new Thread的线程就是用户线程。
  • 守护线程:任何一个守护线程都是整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。当JVM实例中最后一个非守护线程结束时,也随JVM一起退出。
  • 守护线程的用处:jvm垃圾清理线程、主线程。
  • 建议: 尽量少使用守护线程,因其不可控不要在守护线程里去进行读写操作、执行计算逻辑
  • 守护线程demo
public class DaemonDemo implements Runnable {
    @Override
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new DaemonDemo(),"线程1");
        //设置为守护线程
        thread1.setDaemon(true);
        thread1.start();
        Thread.sleep(2000L);
    }
}



相关文章
|
4天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
1月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
29 3
|
2月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
59 3
|
2月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
2月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
3月前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
65 0
|
3月前
|
程序员 调度 C++
解锁Ruby并发编程新境界!Fiber与线程:轻量级VS重量级,你选哪一派引领未来?
【8月更文挑战第31天】Ruby提供了多种并发编程方案,其中Fiber与线程是关键机制。Fiber是自1.9版起引入的轻量级并发模型,无需独立堆栈和上下文切换,由程序员控制调度。线程则为操作系统级别,具备独立堆栈和上下文,能利用多核处理器并行执行。通过示例代码展示了Fiber和线程的应用场景,如任务调度和多URL数据下载,帮助开发者根据需求选择合适的并发模型,提升程序性能与响应速度。
46 0
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
40 1
C++ 多线程之初识多线程
|
21天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
15 3
|
21天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
14 2