深入理解Java并发编程:并行与并发、进程与线程、优先级、休眠与让步

简介: 深入理解Java并发编程:并行与并发、进程与线程、优先级、休眠与让步

前言


Java多线程是一种并发编程方式,允许Java应用程序同时执行多个独立任务。它通过创建和管理多个线程来实现,

每个线程代表一个独立的执行流。多线程可以提高程序性能、资源利用率和响应能力。Java提供了Thread类和Runnable接口来创建和操作线程,


还包括同步机制(synchronization)来确保线程安全。多线程适用于处理并行计算、异步操作、GUI编程等场景。然而,多线程编程也需要小心处理竞态条件、死锁等问题,以确保程序正确运行。


🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗





并发、并行、进程、线程概念


并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

在操作系统中,安装了多个程序,并行指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即


微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。


而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行


执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核


越多,并行处理的程序越多,能大大的提高电脑运行的效率。




线程与进程


  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程




进程

打开活动监视器


线程



线程调度:


● 分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。


● 抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。


大部分操作系统都支持多进程并行运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。


实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而

CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。


其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。




创建线程


继承Thread类

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程



示例:

public class MyThread extends Thread {
    //定义指定线程名称的构造方法
    public MyThread(String name) {
        //调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }
    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

测试

public class Demo1 {
    public static void main(String[] args) {
        //创建自定义线程对象
        MyThread mt = new MyThread("新建的线程");
        //开启新线程
        mt.start();
        //在主方法中执行for循环
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程:" + i);
        }
    }
}





实现Runnable接口


  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

测试

public class Demo2 {
    public static void main(String[] args) {         //创建自定义类对象  线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "新建的线程");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程" + i);
        }
    }
}

继承Thread 和实现Runnable的区别


如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。总结:


*实现Runnable接口比继承Thread类所具有的优势**:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。




线程常用方法


方法名

说明

public static void sleep(long millis)

当前线程主动休眠 millis 毫秒。

public static void yield()

当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。

public final void join()

允许其他线程加入到当前线程中。

public void setPriority(int)

线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。

public void setDaemon(boolean)

设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程)




线程的优先级

  • 我们可以通过传递参数给线程的 setPriority() 来设置线程的优先级别
  • 调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。优先级   : 只能反映 线程 的 中或者是 紧急程度 , 不能决定 是否一定先执行Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

static int MAX_PRIORITY 线程可以具有的最高优先级,取值为10。 static int MIN_PRIORITY 线程可以具有的最低优先级,取值为1。 static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。

示例:

/**
 * 优先级
 *
 */
public class PriorityThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "============" + i);
        }
    }
}

测试:

public class TestPriority {
    public static void main(String[] args) {
        PriorityThread p1 = new PriorityThread();
        p1.setName("p1");
        PriorityThread p2 = new PriorityThread();
        p2.setName("p2");
        PriorityThread p3 = new PriorityThread();
        p3.setName("p3");
        p1.setPriority(1);
        p3.setPriority(10);
        //启动
        p1.start();
        p2.start();
        p3.start();
    }
}




线程的休眠

使用线程的 sleep() 可以使线程休眠指定的毫秒数,在休眠结束的时候继续执行线程

示例:

class SleepThread extends Thread {
    @Override
    public void run() {
        String[] names = new String[]{"zs", "ls", "ww", "z6"};
        int index = (int) (Math.random() * 4);
        for (int i = 3; i > 0; i--) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("倒计时:" + i);
        }
        System.out.println("抽中学员为:" + names[index]);
    }
}

测试:

public class TestSleep {
    public static void main(String[] args) {
        new SleepThread().start();
    }
}




线程的让步


  • Thread.yield() 方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。
  • yield() 做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证**yield()**达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
  • 案例:创建两个线程A,B,分别各打印1000次,从1开始每次增加1,其中B一个线程,每打印一次,就yield一次,观察实验结果.

示例:

class Task1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("A:" + i);
        }
    }
}
class Task2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("B:" + i);
            Thread.yield();
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        new Thread(new Task2()).start();
        new Thread(new Task1()).start();
    }
}


sleep()和yield()的区别


sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;


yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行


sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU


占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把


CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程


另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield()  方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得


CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O

阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。




线程的合并


  • Thread 中,join()方法的作用是调用线程等待该线程完成后,才能继续往下运行。
  • join是Thread类的一个方法,启动线程后直接调用,即join() 的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join() 方法后面的代码,只有等到子线程结束了才能执行。

  • 为什么要用join()方法在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join() 方法了。


示例:

class JoinThread extends Thread {
    public JoinThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始运行");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->子线程:" + i);
        }
        System.out.println(Thread.currentThread().getName() + "线程结束运行");
    }
}
public class JoinDemo {
    public static void main(String[] args) {
        System.out.println("主线程开始运行。。。");
        // 新加入的子线程  
        JoinThread t1 = new JoinThread("新加入的线程");
        t1.start();
//        try {
//            t1.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println("主线程开始结束。。。");
    }
}





最后


本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

相关文章
|
6天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
6天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
18 2
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
17 1
|
算法 Java 数据库
JAVA并发处理经验(四)并行模式与算法4:并行搜索模式
一、前言 在并行搜索模式中,主要是内存数据很大,需要查找的情况。因为我们通常需要查找数据库返回的结果据很少,在几百条以内。
1137 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
17天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####