Java多线程基础知识

简介: Java多线程基础知识

一.进程和多线程的概述以及使用场景


进程:一个正在操作系统中运行的exe程序可以理解为一个进程,完全可以将运行在内存中的exe文件理解为进程-----进程就是受操作系统管理的基本运行单元。一个最简单的Java程序的运行也可以叫做一个进程。


所有的应用程序都是由CPU执行的,对于一个CPU而言,在某一个时间点只能运行一个程序,也就是说只能执行一个进程。操作系统会给每一个进程分配一段有限的cpu使用时间,cpu在这段时间中执行某个进程。由于CPU的执行速度非常快,能在极短的时间内在不同的进程之间进行切换,所以给人同时执行多个程序的感觉。


75c999574f844addbbdda5dc64e37cc2.png


线程:在一个进程中,还可以有多个执行单元同时运行,来同时完成一个或多个程序任务。这些执行单元可以看作是一条条线索,被称为线程。


多线程技术的使用场景:(1)阻塞。一旦系统出现了阻塞现象,则可以根据实际情况来使用多线程技术提高开发效率。(2)依赖。业务如果分为2个执行过程,分别是A和B。当A业务发生阻塞时,B业务的执行不依赖于A业务的执行结果,这时就可以通过多线程来提高运行效率。如果B业务不依赖于A业务的结果,则不必使用多线程技术,A业务出结果后再运行B业务,按顺来就行。


二.并发和并行


并发:指两个或多个事件在同一个时间段内发生,同一时刻只能发生一件事。


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


特别注意:Java程序属于优先抢占式地调度,哪个线程的优先级高,哪个线程就有可能优先执行。优先级相同时,就随机选择执行。优先级大小只是指CPU被优先执行的概率的大小,并不一定优先级大就一定先被执行。


三.线程的创建


Java为多线程开发提供了非常优秀的支持,在java中,可以通过以下三个方式来实现多线程:


1.Thread类实现多线程


Thread类是java.lang包下的一个线程类,用来实现Java多线程。其实步骤非常简单。如下


(1)创建一个Thread线程类的子类,同时重写Thread类的run()方法。


(2)创建该子类的实例对象,并通过调用start()方法来启动线程。


start()方法使线程开始执行,Java虚拟机调用该线程的run()方法。结果是程序的main入口方法和创建的线程并发的运行。多次启动同一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。

package demo01;
public class Test {
    public static void main(String[] args) {
        //创建MyThread1线程实例对象
        MyThread1 thread1=new MyThread1("thread1");
        thread1.start();
        MyThread1 thread2=new MyThread1("thread2");
        thread2.start();
    }
}
//定义一个线程类继承自Thread类
class MyThread1 extends Thread{
    //构造方法
    public MyThread1(String name){
        super(name);
    }
    //重写run()方法
    @Override
    public void run() {
        int i=0;
        while (i++<5){
            //打印输出当时运行的线程的名字
            System.out.println(Thread.currentThread().getName());
        }
    }
}

fa295887c5334bbe8a21ffcec819fb5d.png

2.Runnable接口实现多线程


通过继承Thread类来实现多线程的方式有一些局限性,因为java只支持类的单继承。在这种情况下就可以考虑通过实现Runnable接口的方式来实现多线程。


步骤如下:


(1)创建一个Runnable接口的实现类,同时重写接口中的run()方法。


(2)创建Runnable接口的实现类对象。


(3)使用Thread有参构造方法来创建线程实例,并将Runnable接口的实现类对象作为参数传入。


(4)调用线程实例的start()方法来启动线程

package demo02;
public class test {
    public static void main(String[] args) {
        //(2)创建Runnable接口的实现类对象。
        myThread mythread1=new myThread();
        myThread mythread2=new myThread();
        //(3)使用Thread有参构造方法来创建线程实例,并将Runnable接口的实现类对象作为参数传入。
        Thread thread1=new Thread(mythread1,"thread1");
        Thread thread2=new Thread(mythread2,"thread2");
        //(4)调用线程实例的start()方法来启动线程
        thread1.start();
        thread2.start();
    }
}
//(1)定义一个Runnable接口的实现类
class myThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"的run()方法正在运行");
    }
}

59a01c08071540cca9c33d08b54f9a0a.png

3.Callable接口实现多线程


通过Thread类和Runnable接口实现多线程时都要要重写run()方法。但是,该方法却没有返回值,因此无法从多个线程中获取返回值,但实际应用中肯定会用到返回值的。为了解决这个问题,从jdk5开始,java提供了一个Callable接口,来满足这种既能创建多线程,又能有返回值的需求。


通过这个方式实现多线程和Runnable的方式实现多线程差不多,都是通过Thread类的有参构造方法传入各自接口对象为参数来实现。只不过这里传入的不是Runnable对象了,而是Runnable类的子类FutureTask对象作为参数,而FutureTask对象中则封装带有返回值的Callable接口实现类。


实现步骤如下:


(1)创建一个Callable的实现类,同时重写Callable接口的call()方法。


(2)创建实现了Callable接口的实现类对象。


(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象


(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。


(5)调用线程实例的start()方法启动线程。

package demo03;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//(1)创建一个Callable的实现类,同时重写Callable接口的call()方法。
class MyThread implements Callable<Object>{
    @Override
    public Object call() throws Exception {
        int i=0;
        while (i++<5){
            //打印输出当时运行的线程的名字
            System.out.println(Thread.currentThread().getName());
        }
        return i;
    }
}
public class test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //(2)创建实现了Callable接口的实现类对象。
        MyThread mythread1=new MyThread();
        //(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
        FutureTask<Object> ft1=new FutureTask<>(mythread1);
        //(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
        Thread thread1=new Thread(ft1,"thread1");
        //(5)调用线程实例的start()方法启动线程。
        thread1.start();
        //(2)创建实现了Callable接口的实现类对象。
        MyThread mythread2=new MyThread();
        //(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
        FutureTask<Object> ft2=new FutureTask<>(mythread2);
        //(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
        Thread thread2=new Thread(ft2,"thread2");
        //(5)调用线程实例的start()方法启动线程。
        thread2.start();
        System.out.println("thread1返回的结果为:"+ft1.get());
        System.out.println("thread2返回的结果为:"+ft2.get());
    }
}

bb3c97131ba94687813a299966b11152.png

FutureTask实现了RunnableFuture接口,RunnableFuture接口继承自Runnable接口和Future接口。所以FutureTask的本质是Runnable接口和Future接口的实现类。


Future接口的方法:


boolean cancel(boolean running)用于取消任务,running参数为是否取消还在运行的任务


boolean isCancelled()用于判断任务是否被取消成功


boolean isDone()判断任务是否已经完成


V get()用于获取执行结果(返回值),这个方法会发生阻塞,一直等到任务执行完才会执行结果。


V get(long timeout,TimeUnit unit)如果指定时间内没有得到结果就返回null


四.3种多线程实现方式的对比分析。


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


1.可以避免Java中的单继承的局限性


2.线程池只能放入实现Runnable或Callable类的线程,不能直接放入继承Thread类的线程


其它方面的对比,我通过下面一个多窗口售票的案例来对比分析更多优略:


4个窗口卖100张票,这100张票可以看作是共享资源。4个售票窗口可以看成是4个线程。


我们先用第1种方法(继承Thread类)来实现这个案例:

package demo04;
//定义一个继承自thread类的子类
class TicketWindow extends Thread{
    private int tickets=100;
    @Override
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
            }
        }
    }
}
public class test01 {
    public static void main(String[] args) {
        //创建4个线程对象来作为窗口卖票
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
    }
}

0ea494d859814f5783301c7101b739f5.png

运行后会发现重复卖票了,也就是说票没有共享。同一张票被卖了四次。这很明显不是我们想要的答案吧。

这时候就要通过实现Runnable接口来实现多线程:

package demo04;
//定义一个实现了Runnable接口的实现类
class TicketWindow implements Runnable{
    private int tickets=100;
    @Override
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
            }
        }
    }
}
public class test02 {
    public static void main(String[] args) {
        //创建TicketWindow实例对象
        TicketWindow ticketWindow=new TicketWindow();
        new Thread(ticketWindow,"窗口1").start();
        new Thread(ticketWindow,"窗口2").start();
        new Thread(ticketWindow,"窗口3").start();
        new Thread(ticketWindow,"窗口4").start();
    }
}

6d94e80f031440b3843d7c7881d33b67.png

这次就可以共享资源了,不会出现票被重复卖的情况。


五.后台线程


新创建的线程默认是前台线程,对于java程序来说,只要有前台线程在运行,这个进程就不会关闭。如果某个线程在启动之前(start()方法)调用了setDaemon(true)语句,这个线程就会变成一个后台线程。


java中,每次程序运行至少启动2个线程,1个是main线程,另一个是垃圾收集线程。因为每当执行一个java程序时,都会启动一个jvm,每一个jvm其实就是操作系统中启动了一个进程


相关文章
|
13天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
4天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
4天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
|
4天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
27 1
|
12天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
12天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
11天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
17天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
44 9
|
14天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
20天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####