Java并发编程实战(线程控制操作详解)

简介: 在使用Java实际编程中,多线程可以说是无所不在,凡是需要并发执行的都可以用到它,一个应用程序中不用多线程将会是很糟糕的事情,所以掌握线程以及它的控制操作是非常重要的。

 通过本篇文章来今天带大家一文掌握线程控制操作,感谢您的观看。

目录

📌前言

📱一、线程的基本概念

✏️1.并行和并发

✏️2.进程和线程

💻二、线程的运行状态

🔦三、线程编程实践

📝1.线程两种定义方法

📝2.启动线程

📝3.同时定义和启动线程

📝 4.线程弹出与暂停

📝5.线程等待与唤醒

📝6.线程中断


一、线程的基本概念

1.并行和并发

并行:多个CPU核心同时工作,处理不同的任务。

并发:多个任务交替使用 CPU 核心工作,以提高 CPU 利用率。

2.进程和线程

进程:程序的一次执行。由操作系统创建并分配资源,执行一个单独的任务。

进程是系统进行资源分配和调度的独立单位,每个进程都有自己的内存空间和系统资源。进程内所有线程共享堆存储空间,保存程序中定义的对象和常量池。

Windows系统中,每个运行的 Java 程序都是一个独立的进程。

线程:进程内的执行单元,不分配单独的资源,执行一个单独的子任务。

线程是进程内调度和分派的基本单位,共享进程资源。每个线程有自己的独立的栈存储空间,保存线程执行的方法以及基本类型的数据。

运行的 Java 程序内含至少一个主线程 main ,用户可以在 Java 程序中自定义并调用多个线程。 JVM 垃圾回收线程也是一个独立的线程。

图片.png

二、线程的运行状态

线程除创建状态 New 和结束状态 Terminate 外,主要有以下几种运行状态:

①运行(Running) :CPU 正在执行线程。

②就绪(Runnable) :线程一切就绪,等待 CPU 执行。

运行/就绪状态 统称为可运行状态 Runnable。 Java 程序中,线程在 运行/就绪状态 之间的切换由 JVM 自动调度,开发者无法获知。线程之间的调度采用分优先级多队列时间片轮转算法。进程在执行完 CPU 时间片切换到就绪状态之前会先保存自己的状态,下次进入运行状态时再重新加载。

③阻塞(Blocked) :线程因缺少其他资源,比如请求资源被上锁而暂停执行。在获得资源后进入就绪状态。

④等待(Waitting) :线程接受了等待指令,释放资源暂停执行。在超时/接受唤醒指令后进入就绪状态。

图片.png

三、线程操作实践

Runnable 接口内唯一声明了 run 方法,由 Thread 类实现。开发者在 run 方法中定义运行时线程将要执行的功能,线程开启后由Java虚拟机自动调用并执行。如果开发者主动调用 run 方法,则只会当作普通方法执行。

1.线程两种定义方法

①继承 Thread 类,重写 run 方法。

public class MyThread extends Thread {
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }
}

image.gif

②实现 Runnable 接口,实现 run 方法。推荐使用,避免了单继承的局限性。

public class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

image.gif

2.启动线程

Thread 类定义了 start 方法。调用 start 方法后,系统会开启一个新线程进入就绪状态:由 JVM 会自动对线程进行调度,在运行时调用并执行线程的 run 方法。一个线程只能启动一次。

①如果自定义线程类继承 Thread 类,直接启动。

public class Main {
   public static void main(String[] args) {
       MyThread t1 = new MyThread();
       MyThread t2 = new MyThread("ThreadName");
       t1.start();
       t2.start();
   }
}

image.gif

②如果自定义线程类实现 Runnable 接口,则需要借助 Thread 类启动线程。

public class Main {
   public static void main(String[] args) {
       MyThread mythread = new MyThread();
       Thread t1 = new Thread(mythread);                   // 由系统指定默认线程名 Thread-X
       Thread t2 = new Thread(mythread, "ThreadName");     // 开发者自定义线程名 
       t1.start();
       t2.start();
   }
}

image.gif

3.同时定义和启动线程

通过匿名内部类方式,我们可以实现同时定义和启动线程的简洁写法。

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            public void run(){
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}
class Test {
   public void method() {
       System.out.println(Thread.currentThread().getName());
   }
}

image.gif

4.线程弹出与暂停

Thread 类定义了 yield 方法。当前线程执行到 Thread.yield() 方法,会停止运行进入就绪状态。但线程切换到就绪状态后,什么时候被 JVM 调度回运行状态开发者无法控制。

public class ThreadDemo {
    public static void main(String[] args) {
       MyThread mythread = new MyThread();
       Thread t1 = new Thread(mythread);              
       Thread t2 = new Thread(mythread);    
       t1.start();
       t2.start();
    }
    static class MyThread implements Runnable {
        @Override
        public void run() {
            int count = 0;
            for (int i = 0; i < 10000; i++) {
                Thread.yield();                    // 切换到就绪状态
                count++;
                System.out.println(count);
            }         
        }
    }
}

image.gif

Thread 类定义了 sleep 方法。当前线程执行到 Thread.sleep(1000) 方法,会停止运行进入阻塞状态,但仍会保持对象锁,其他线程不可访问其资源。直到超时后进入就绪状态。调用 sleep 方法需要捕获或抛出 InterruptException 异常。

public class ThreadDemo {
    public static void main(String[] args) {
       MyThread mythread = new MyThread();
       Thread t1 = new Thread(mythread);              
       Thread t2 = new Thread(mythread);    
       t1.start();
       t2.start();
    }
    static class MyThread implements Runnable {
        @Override
        public void run() {
            int count = 0;
            for (int i = 0; i < 10000; i++) {
                try{
                    Thread.sleep(1000);             // 当前线程暂停 1s
                } catch(InterruptException e){
                    e.printStackTrace();
                }
                count++;
                System.out.println(count);
            }         
        }
    }
}

image.gif

5.线程等待与唤醒

①线程等待

当前线程执行 obj.wait() 方法,线程会停止运行并释放对象锁 obj,其他线程可以访问其资源。同时线程进入 obj 对象的等待池,直到被 notify 方法唤醒进入就绪状态。调用 wait 方法需要捕获或抛出 InterruptException 异常。

wait 方法允许计时等待。当前线程执行 obj.wait(1000) 方法,计时结束后线程会被自动唤醒进入就绪状态。

②线程唤醒

当前线程执行 obj.notify() 方法,会随机从 obj 对象等待池中选择一个线程唤醒,使其进入就绪状态。但是 notify 方法不会释放当前进程的对象锁,如果该线程持有 obj 对象的锁,当前线程释放锁后被唤醒的其他线程才能被执行。如果想被唤醒线程先执行,notify 方法后添加 wait 方法释放锁。

当前线程执行 obj.notifyall() 方法,会将所有 obj 对象等待池中所有线程唤醒。

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread("t");
        synchronized(t) {                         // 对 t 设置对象锁
            try {
                t.start();
                System.out.println("1");
                t.wait();                         // 当前线程释放 t 锁,进入 t 对象等待池
                System.out.println("4");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyThread extends Thread {
        @Override
        public void run() {
            synchronized (this) {                 // 对 t 设置对象锁        
                Thread.sleep(1000);
                System.out.println("2");
                this.notify();                    // 随机唤醒一个 t 对象等待池中的线程
                System.out.println("3");
            }
        }
    }
}

image.gif

6.线程中断

调用 t.stop() 方法可以强制终止线程 t 运行,但强制中断线程可能会造成意想不到的问题,已不推荐使用。

目前主要采用设置线程中断标志的方式,向线程发送中止信号。由线程自行终止运行:

    • 执行 t.interrupt() 方法,将线程 t 中断标志设为 true 。
    • 执行 t.isInterrupted() 方法,查看线程 t 中断标志。
    • 执行 t.interrupted() 方法,查看线程 t 中断标志然后将其设为 false 。
    public class ThreadDemo {
        public static void main(String[] args) {
           MyThread mythread = new MyThread();
           Thread t = new Thread(mythread);              
           t.start();
           try {
                Thread.sleep(500);
            } catch (InterruptedException e) {}
           t.interrupt();                            // 设置中断标志为 true
        }
        static class MyThread implements Runnable {
            @Override
            public void run() {
                while (true) {
                    System.out.print("hello");
                    if(this.isInterrupted()){        // 查看中断标志,若为 true 结束循环
                        break;
                    }
                }
            }        
        }
    }

    image.gif


    相关文章
    |
    1天前
    |
    消息中间件 缓存 NoSQL
    Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
    Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
    |
    1天前
    |
    数据采集 存储 Java
    高德地图爬虫实践:Java多线程并发处理策略
    高德地图爬虫实践:Java多线程并发处理策略
    |
    1天前
    |
    存储 安全 Java
    Java并发编程中的高效数据结构:ConcurrentHashMap解析
    【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
    |
    1天前
    |
    数据采集 前端开发 测试技术
    《手把手教你》系列技巧篇(三十一)-java+ selenium自动化测试- Actions的相关操作-番外篇(详解教程)
    【4月更文挑战第23天】本文介绍了网页中的滑动验证码的实现原理和自动化测试方法。作者首先提到了网站的反爬虫机制,并表示在本地创建一个没有该机制的网页,然后使用谷歌浏览器进行验证。接着,文章详细讲解了如何使用WebElement的click()方法以及Action类提供的API来模拟鼠标的各种操作,如右击、双击、悬停和拖动。
    6 2
    |
    2天前
    |
    缓存 Java
    【Java基础】简说多线程(上)
    【Java基础】简说多线程(上)
    6 0
    |
    2天前
    |
    Java API 调度
    [Java并发基础]多进程编程
    [Java并发基础]多进程编程
    |
    2天前
    |
    Java API 调度
    [AIGC] 深入理解Java并发编程:从入门到进阶
    [AIGC] 深入理解Java并发编程:从入门到进阶
    |
    2天前
    |
    前端开发 Java 测试技术
    Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
    Java从入门到精通:4.1.1参与实际项目,锻炼编程与问题解决能力
    |
    2天前
    |
    Dubbo Java 应用服务中间件
    Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
    Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
    |
    2天前
    |
    SQL Java 数据库连接
    Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句
    Java从入门到精通:2.3.2数据库编程——了解SQL语言,编写基本查询语句