首页 > 其他分享 >一,多线程

一,多线程

时间:2024-10-21 20:43:50浏览次数:1  
标签:Thread lock void 线程 多线程 public

多线程详解:从基础到实践

在现代编程中,多线程是一种常见的并发执行技术,它允许程序同时执行多个任务。本文将详细介绍多线程的基本概念、实现方式、线程控制、线程同步、死锁、线程间通信以及线程池等高级主题。

多线程概述

进程与线程

进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。例如,IDEA、阿里云盘、WeGame、Steam等都是以进程的形式运行的。

线程:是进程中的单个顺序控制流,是一条执行路径。一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路径,则称为多线程程序

Java程序运行原理

思考:Java程序启动时,是单线程程序还是多线程程序?答案是多线程程序,因为它至少包括主线程垃圾回收线程

实现多线程的三种方式

在Java中实现多线程有多种方式,其中三种常见的实现方案包括继承Thread类、实现Runnable接口以及实现Callable接口。下面将详细介绍这三种方式的实现方法,并比较它们的优缺点。

1. 继承Thread类

实现方法:

class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start(); // 启动线程
    }
}

优点:

  • 直接继承Thread类,使用简单直观。

缺点:

  • 无法继承其他类,因为Java不支持多重继承,这限制了继承Thread类的方式使用。
  • 线程执行完毕后无法返回结果给主线程。

2. 实现Runnable接口

实现方法:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start(); // 启动线程
    }
}

优点:

  • 实现Runnable接口的方式没有类的单继承的局限性,可以继承其他类。
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想。

缺点:

  • 线程执行完毕后无法返回结果给主线程。

3. 实现Callable接口

实现方法:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 线程执行的代码
        return 123; // 有返回值
    }
}

public class CallableExample {
    public static void main(String[] args) throws Exception {
        MyCallable callable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread t = new Thread(task);
        t.start();

        // 获取线程执行的结果
        Integer result = task.get();
        System.out.println("Result: " + result);
    }
}

优点:

  • 可以有返回值,主线程可以通过Future对象获取子线程的执行结果。
  • 可以抛出异常,使得异常处理更加灵活。

缺点:

  • 代码相对复杂,需要结合FutureTask使用。
  • 创建和维护的开销相对较大。

比较

  • 继承Thread的方式是最直观的实现多线程的方法,但它限制了类的继承结构,且无法处理线程返回结果。
  • 实现Runnable接口的方式更加灵活,可以避免继承限制,且代码与线程的分离更符合面向对象的设计原则。
  • 实现Callable接口的方式在功能上最为强大,支持返回值和异常处理,适用于需要从子线程获取结果的场景,但实现相对复杂。

在实际开发中,选择哪种方式取决于具体需求。如果需要从线程中获取结果或抛出异常,Callable接口是更好的选择。如果只是简单的执行任务,且不需要返回结果,Runnable接口或继承Thread类都是可行的方案。

线程控制

休眠线程

public static void sleep(long millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

加入线程

public final void join() {
    // 调用join方法,使当前线程等待该线程终止
}

礼让

public static void yield() {
    Thread.yield(); // 礼让CPU给其他线程
}

后台线程

public final void setDaemon(boolean on) {
    Thread.currentThread().setDaemon(on); // 设置为后台线程
}

中断线程

public final void stop()
public void interrupt()

用户线程:优先级高于守护线程。

守护线程【后台线程】:当一个程序没有了用户线程,守护线程也就没有了。

线程同步

线程同步是确保多个线程在访问共享资源时,保持数据一致性和完整性的一种机制。

同步代码块

public synchronized void synchronizedMethod() {
    // 需要同步的代码
}

同步方法

public class SynchronizedExample {
    public synchronized void method() {
        // 需要同步的代码
    }
}

Lock锁的使用

在Java中,synchronized关键字提供了一种内置的同步机制,但它在某些情况下不够灵活。例如,它不允许尝试非阻塞地获取锁,也不支持尝试超时获取锁。为了解决这些问题,Java并发库提供了Lock接口及其实现类,如ReentrantLock

Lock接口

Lock接口提供了比synchronized更复杂的线程同步控制。它允许更灵活的结构,可以具有多个相关联的锁,并且可以被多个不同的线程持有。

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

ReentrantLock类

ReentrantLockLock接口的一个具体实现,它是一个可重入的互斥锁,这意味着同一线程可以多次获得锁。

基本使用

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();

    public void performAction() {
        lock.lock(); // 获取锁
        try {
            // 保护的代码块
            System.out.println("执行任务...");
        } finally {
            lock.unlock(); // 释放锁
        }
    }
}

在上述代码中,我们创建了一个ReentrantLock对象,并在performAction方法中使用lock()unlock()方法来保护代码块,确保同一时间只有一个线程可以执行该代码块。

尝试获取锁

public void performAction() {
    if (lock.tryLock()) {
        try {
            // 保护的代码块
            System.out.println("执行任务...");
        } finally {
            lock.unlock(); // 释放锁
        }
    } else {
        System.out.println("无法获取锁");
    }
}

在这个例子中,我们尝试非阻塞地获取锁。如果获取成功,我们就执行保护的代码块;如果获取失败,我们就打印一条消息。

尝试超时获取锁

import java.util.concurrent.TimeUnit;

public void performActionWithTimeout(long timeout, TimeUnit unit) {
    if (lock.tryLock(timeout, unit)) {
        try {
            // 保护的代码块
            System.out.println("执行任务...");
        } finally {
            lock.unlock(); // 释放锁
        }
    } else {
        System.out.println("在指定的时间内无法获取锁");
    }
}

在这个例子中,我们尝试在指定的时间内获取锁。如果在指定时间内获取成功,我们就执行保护的代码块;如果超时,我们就打印一条消息。

条件对象

ReentrantLock还支持条件对象,它允许线程在某些条件下等待或唤醒。

import java.util.concurrent.locks.Condition;

public class ConditionExample {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean ready = false;

    public void waitForCondition() {
        lock.lock();
        try {
            while (!ready) {
                condition.await(); // 等待条件
            }
            // 条件满足后执行的代码
            System.out.println("条件满足,执行任务...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalCondition() {
        lock.lock();
        try {
            ready = true;
            condition.signalAll(); // 唤醒所有等待的线程
        } finally {
            lock.unlock();
        }
    }
}

在这个例子中,我们创建了一个条件对象,并在waitForCondition方法中使用await()方法等待条件满足。在signalCondition方法中,我们设置条件为满足,并使用signalAll()方法唤醒所有等待的线程。

解析

使用LockReentrantLock的好处是它们提供了更细粒度的控制,包括尝试非阻塞获取锁、尝试超时获取锁以及条件对象。这些特性使得Lock在某些场景下比synchronized关键字更加灵活和强大。例如,当你需要在等待锁时执行更复杂的逻辑,或者需要多个条件控制线程的等待和唤醒时,LockReentrantLock就显得非常有用。

通过使用Lock,我们可以更精确地控制线程的同步,从而提高程序的性能和响应性。同时,Lock也提供了更好的错误处理能力,因为它允许我们在无法获取锁时采取其他措施,而不是简单地阻塞线程。

死锁

死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局。

线程间通信

线程间通信通常涉及到生产者和消费者模式,其中生产者线程生成数据,消费者线程消费数据。

等待唤醒机制

public class CommunicationExample {
    private List<Integer> list = Collections.synchronizedList(new ArrayList<>());

    public void produce() {
        synchronized (list) {
            while (list.size() == 10) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.add(new Integer(1));
            list.notifyAll();
        }
    }

    public void consume() {
        synchronized (list) {
            while (list.size() == 0) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            int item = list.remove(0);
            list.notifyAll();
        }
    }
}

Java 线程组详解

什么是线程组?

Java中的线程组(ThreadGroup)是一个用于管理和组织一组相关线程的容器。每个线程在Java中必须隶属于一个线程组,它不仅提供了一种逻辑上的分组方式,也便于进行批量控制和异常处理等操作。线程组通过树状结构来表示层级关系,从而实现对线程生命周期的集中管理。

线程组的主要作用

  • 组织:将相似或相关的线程放在同一个组内,便于管理。
  • 控制:可以对整个线程组执行操作,如挂起、恢复、中断等。
  • 监视:可以获取线程组的状态信息,如活动线程数、线程组名称等。
  • 安全性:线程组可以用于设置安全性策略,限制组内线程的权限。

如何创建线程组

要创建线程组,你可以使用ThreadGroup类的构造函数。以下是一个创建线程组的示例:

ThreadGroup parentGroup = new ThreadGroup("ParentGroup"); // 创建一个名为ParentGroup的线程组
ThreadGroup childGroup = new ThreadGroup(parentGroup, "ChildGroup"); // 创建一个名为ChildGroup的子线程组

在上面的示例中,我们首先创建了一个名为ParentGroup的线程组,然后在该组内创建了一个名为ChildGroup的子线程组。

线程组的管理

活动线程数

要获取线程组内的活动线程数,可以使用activeCount()方法。该方法返回线程组中当前活动线程的估计数目。

int activeThreads = threadGroup.activeCount();

线程组的中断

通过调用interrupt()方法,你可以中断线程组内的所有线程。这将导致线程组内的每个线程抛出InterruptedException异常。

threadGroup.interrupt();

线程组的异常处理

在Java中,线程组可以实现对一组线程的批量操作和统一管理。例如,通过重写ThreadGroup类的uncaughtException(Thread t, Throwable e)方法,可以在一个线程组中的任意线程抛出未捕获异常时,由该线程组统一进行异常处理。

public class ThreadGroupExceptionHandlerDemo {
    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("MyGroup") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t.getName() + " threw an exception: " + e.getMessage());
            }
        };
        
        Thread thread1 = new Thread(threadGroup, () -> {
            throw new RuntimeException("An unchecked exception from Thread 1");
        });
        
        thread1.start();
    }
}

在这个例子中,所有属于"MyGroup"线程组的线程在其run()方法内抛出未被捕获的异常时,都会触发自定义的uncaughtException()方法,从而实现了对整个线程组内异常的集中处理。

线程组的优先级限制

Java线程组还提供了设置其下所有线程最大优先级的功能,这意味着即使某个线程尝试将优先级设置得高于线程组的最大允许值,最终也会被限制在最大优先级以下。

public class ThreadGroupPriorityLimitDemo {
    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("LimitedPriorityGroup");
        threadGroup.setMaxPriority(6);
        
        Thread highPriorityThread = new Thread(threadGroup, () -> {
            Thread.currentThread().setPriority(9); // This will be capped at 6
            System.out.println("Actual priority of this thread: " + Thread.currentThread().getPriority());
        });
        
        highPriorityThread.start();
    }
}

运行上述代码后,尽管高优先级线程试图将其优先级设为9,但受限于线程组的限制,实际执行时其优先级仍会被调整为6。

总结

线程组在Java多线程编程中提供了层次化的线程组织模型,并通过数据结构属性、创建与继承关系以及权限控制机制,实现了对线程集合的有效管理和安全性保障。合理使用线程组可以提高程序的可维护性和健壮性,同时也能有效地管理和监控线程的执行状态。

线程池

线程池是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。

创建线程池

ExecutorService pool = Executors.newFixedThreadPool(10);

提交任务

pool.submit(() -> {
    System.out.println("任务执行中...");
});

关闭线程池

pool.shutdown();

总结

多线程编程是一种强大的技术,它允许程序同时执行多个任务,提高程序的效率和响应性。通过继承Thread类或实现Runnable接口,我们可以轻松地创建和管理线程。线程同步和死锁是多线程编程中需要特别注意的问题,而线程池则提供了一种高效的方式来管理大量线程。通过这些技术,我们可以构建出高性能、高并发的应用程序。

标签:Thread,lock,void,线程,多线程,public
From: https://www.cnblogs.com/bjynjj/p/18490331

相关文章

  • Javaee---多线程(一)
    文章目录1.线程的概念2.休眠里面的异常处理3.实现runnable接口4.匿名内部类子类创建线程5.匿名内部类接口创建线程6.基于lambda表达式进行线程创建7.关于Thread的其他的使用方法7.1线程的名字7.2设置为前台线程7.3判断线程是否存活8.创建线程方法总结9.start方法10.终......
  • Java多线程技能
      2.创建多线程的方式,有几种?怎么创建继承Thread类(一般不单独用)实现Runnable接口+Thread对象实现Callable接口+FutureTask<>对象+Thread对象线程池+(实现Callable接口+FutureTask<>对象)或者(实现Runnable接口)3.Thread类的常见APIcurrentThread()获取当前......
  • 从零开始写多线程
    1.Java多线程的基本概念1.1线程与进程进程:是操作系统分配资源的基本单位,每个进程都有独立的内存空间。线程:是进程内的一个执行单元,同一进程中的线程共享进程的内存空间,线程间的通信更为高效。1.2线程的好处提高系统响应性:可以实现用户界面与后台处理的并发执行,使得程序......
  • 高效并行计算:使用C++中的std::thread实现多线程编程
    解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界在现代计算中,随着多核处理器的普及,如何充分利用硬件资源以提升程序性能成为关键问题之一。C++标准库提供了丰富的多线程支持,其中std::thread是用于实现并发计算的核心工具之一。通过合理的多线程设计,程序可以实现......
  • 多线程交替顺序打印ABC的多种方式
    面试题:有3个独立的线程,一个只会输出A,一个只会输出B,一个只会输出C,在三个线程启动的情况下,请用合理的方式让他们按顺序打印ABC。使用lock,Conditionimportjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.lock......
  • 从多线程到 epoll:如何优雅地处理高并发请求?
    文章参考于:小林coding最近在学习操作系统,服务器与客户端之间的通信离不开socket编程。然而,基于TCP的socket编程在默认情况下只能实现一对一的通信,因为它采用同步阻塞模型。在服务器处理完当前客户端的请求之前,无法响应其他客户端的请求。这种方式效率不高,显然浪费了......
  • STA模型、同步上下文和多线程、异步调度
    写过任何桌面应用,尤其是WinForm的朋友们应该见过,Main函数上放置了一个[STAThread]的Attribute。而且几乎所有的桌面应用框架,都是由同一个UI线程来执行渲染操作的,这表现为从其他线程修改控件的值就会抛异常:awaitTask.Run(()=>control.Content="");//throwsexception大家......
  • 多线程
    第七章——多线程1、多线程概述1、多线程概述进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。线程:是进程中的单个顺序控制流,是一条执行路径一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路......
  • java_day18_多线程、线程安全问题、死锁、等待唤醒机制
    一、线程1、多线程进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。举例:IDEA,阿里云盘,wegame,steam线程:是进程中的单个顺序控制流,是一条执行路径一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行......
  • 多线程
    进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。举例:IDEA,阿里云盘,wegame,steam线程:是进程中的单个顺序控制流,是一条执行路径一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行......