java实战(五):理解多线程与多线程实现冒泡排序及可视化

多线程

  • 1.多线程理解
    • 1.1线程概念
    • 1.2线程的创建和启动
    • 1.3线程的同步与互斥
    • 1.4线程的状态和生命周期
    • 1.5线程间的通信
    • 1.6处理线程的异常和错误
    • 1.7实践
  • 2.效果
  • 3.代码

1.多线程理解

1.1线程概念

线程:计算机中能够执行独立任务的最小单位。在操作系统中,每个程序都运行在一个或多个线程中。线程可以同时执行多个任务,使得程序能够并发执行,提高了程序的效率和响应能力。

与进程不同,线程是在进程内部创建和管理的。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间和文件句柄等。每个线程有自己的执行路径和状态,可以独立执行不同的任务。

线程的创建和调度由操作系统负责,它会为每个线程分配资源,并按照一定的调度策略来决定线程的执行顺序。线程之间可以通过共享内存或消息传递等方式进行通信和同步。

多线程编程可以提高程序的性能和响应能力,特别适用于需要同时处理多个任务或需要实时交互的应用程序。然而,多线程编程也带来了一些挑战,如线程安全性、竞态条件和死锁等问题,需要仔细考虑和处理。

1.2线程的创建和启动

线程的创建和启动可以通过继承Thread类或实现Runnable接口来实现。

  1. 继承Thread类:
    • 创建一个继承自Thread类的自定义线程类,重写run()方法,在run()方法中定义线程的执行逻辑。
    • 在自定义线程类中,可以添加其他成员变量和方法,用于线程的控制和数据传递。
    • 在主程序中,创建自定义线程类的实例,并调用start()方法启动线程。

示例代码如下:

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

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}
  1. 实现Runnable接口:
    • 创建一个实现了Runnable接口的类,实现run()方法,在run()方法中定义线程的执行逻辑。
    • 在主程序中,创建Runnable接口实现类的实例,并将其作为参数传递给Thread类的构造方法。
    • 调用Thread类的start()方法启动线程。

示例代码如下:

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

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

无论是继承Thread类还是实现Runnable接口,都需要重写run()方法,在run()方法中定义线程的执行逻辑。线程的实际执行逻辑应该写在run()方法中。

通过调用start()方法来启动线程,start()方法会在后台创建一个新的线程,并调用run()方法来执行线程的逻辑。

需要注意的是,不要直接调用run()方法来启动线程,这样只会在当前线程中执行run()方法,而不会创建新的线程。

1.3线程的同步与互斥

线程的同步和互斥是为了保证多个线程之间的正确协作和共享资源的安全访问。

  1. 同步:线程同步是指多个线程按照一定的顺序执行,以达到协作的目的。常用的同步机制有:

    • 使用synchronized关键字:通过在方法或代码块前加上synchronized关键字,可以确保同一时间只有一个线程可以执行被synchronized修饰的代码段。
    • 使用Lock接口ReentrantLock类Lock接口提供了更灵活的锁定机制,可以使用lock()方法获取锁,使用unlock()方法释放锁。
  2. 互斥:线程互斥是指多个线程之间对共享资源的访问进行控制,保证同一时间只有一个线程可以访问共享资源,避免数据的不一致性和冲突。常用的互斥机制有:

    • 使用synchronized关键字:通过在方法或代码块前加上synchronized关键字,可以确保同一时间只有一个线程可以执行被synchronized修饰的代码段。
    • 使用Lock接口ReentrantLock类Lock接口提供了更灵活的锁定机制,可以使用lock()方法获取锁,使用unlock()方法释放锁。
    • 使用信号量(Semaphore):信号量可以控制同时访问某个资源的线程数量,通过acquire()方法获取信号量,release()方法释放信号量。

同步和互斥机制可以保证线程之间的协作和共享资源的安全访问,避免了数据竞争和不一致性的问题。

需要注意的是,在使用同步和互斥机制时,要避免死锁和活锁等问题,合理设计和使用锁定机制。

当多个线程同时访问共享资源时,可以使用同步和互斥机制来确保数据的一致性和避免冲突。以下是两个简单的例子:

  1. 使用synchronized关键字:
public class Counter {
    private int count;

    public synchronized void increment() {
        count++;
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(counter.getCount()); // 输出结果应为2000
    }
}

在上述例子中,Counter类中的increment()方法使用了synchronized关键字,确保了对count变量的访问是互斥的。两个线程分别执行increment()方法,通过对count进行加一操作,最终得到的结果应为2000。

  1. 使用Lock接口ReentrantLock类
public class Counter {
    private int count;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(counter.getCount()); // 输出结果应为2000
    }
}

在上述例子中,Counter类中的increment()方法使用了Lock接口ReentrantLock类,通过lock()方法获取锁,使用unlock()方法释放锁。两个线程分别执行increment()方法,通过对count进行加一操作,最终得到的结果应为2000。

这些例子展示了如何使用同步和互斥机制来确保多个线程对共享资源的安全访问。通过使用synchronized关键字、Lock接口ReentrantLock类等机制,可以避免数据竞争和不一致性的问题。

1.4线程的状态和生命周期

在这里插入图片描述

  1. 新建状态(New):线程对象被创建,但还没有调用start()方法。

  2. 就绪状态(Runnable):调用线程对象的start()方法后,线程进入就绪状态,等待CPU分配时间片。

  3. 运行状态(Running):当线程获得CPU时间片后,进入运行状态,执行run()方法中的代码。

  4. 阻塞状态(Blocked):线程在某些情况下会进入阻塞状态,暂时停止执行,直到满足某个条件后才能继续执行。

  5. 等待状态(Waiting):线程在某些情况下会进入等待状态,等待其他线程的唤醒。

  6. 计时等待状态(Timed Waiting):线程在某些情况下会进入计时等待状态,等待一段时间或满足某个条件后继续执行。

  7. 终止状态(Terminated):线程执行完run()方法或发生异常导致线程终止后,进入终止状态。

需要注意的是,线程的状态不是固定的,线程可以在不同的状态之间转换。例如,一个线程在运行状态下可能被阻塞或进入等待状态,然后再次回到运行状态。

1.5线程间的通信

线程间的通信是指多个线程之间通过共享的内存或其他方式进行信息交换和数据传递的过程。在Java中,线程间的通信可以通过以下几种方式实现:

  1. 共享变量:多个线程可以通过共享的变量进行通信。通过对共享变量的读写操作,线程可以传递信息和数据。需要注意的是,对于共享变量的读写操作需要进行同步,以确保线程安全。

  2. 等待/通知机制:通过使用Object类wait()notify()notifyAll()方法,线程可以进行等待和唤醒操作。一个线程可以调用wait()方法进入等待状态,等待其他线程调用notify()notifyAll()方法来唤醒它。

下面是一个简单的例子,演示了线程间通过共享变量和等待/通知机制进行通信:

public class Message {
    private String content;
    private boolean isAvailable = false;

    public synchronized void send(String message) {
        while (isAvailable) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        content = message;
        isAvailable = true;
        notifyAll();
    }

    public synchronized String receive() {
        while (!isAvailable) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String message = content;
        isAvailable = false;
        notifyAll();
        return message;
    }
}

public class Sender implements Runnable {
    private Message message;

    public Sender(Message message) {
        this.message = message;
    }

    @Override
    public void run() {
        String[] messages = {"Hello", "World", "Goodbye"};
        for (String msg : messages) {
            message.send(msg);
            System.out.println("Sent: " + msg);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Receiver implements Runnable {
    private Message message;

    public Receiver(Message message) {
        this.message = message;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            String receivedMsg = message.receive();
            System.out.println("Received: " + receivedMsg);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Message message = new Message();
        Thread senderThread = new Thread(new Sender(message));
        Thread receiverThread = new Thread(new Receiver(message));

        senderThread.start();
        receiverThread.start();
    }
}

在上述例子中,Message类表示一个消息对象,包含一个共享的字符串变量content和一个标志位isAvailableSender线程通过调用send()方法向Message对象发送消息,Receiver线程通过调用receive()方法接收消息。通过使用synchronized关键字和wait()notify()方法,实现了线程间的等待和唤醒操作,确保了消息的正确传递。

1.6处理线程的异常和错误

在处理线程的异常和错误时,我们可以采取以下几种方式:

  1. 使用try-catch块捕获异常:在线程的run()方法中,可以使用try-catch块来捕获可能发生的异常,并在catch块中进行相应的处理。这样可以确保异常不会导致线程终止,而是继续执行后续的代码。
public class MyThread implements Runnable {
    @Override
    public void run() {
        try {
            // 执行可能抛出异常的代码
        } catch (Exception e) {
            // 处理异常
        }
    }
}
  1. 在线程内部抛出异常:如果在线程的run()方法中抛出了异常,可以通过在run()方法中直接抛出异常,然后在线程的调用方(例如主线程)中捕获并处理异常。
public class MyThread implements Runnable {
    @Override
    public void run() {
        // 执行可能抛出异常的代码
        throw new RuntimeException("Something went wrong");
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Thread myThread = new Thread(new MyThread());
            myThread.start();
            myThread.join();
        } catch (Exception e) {
            // 处理异常
        }
    }
}
  1. 使用UncaughtExceptionHandler处理未捕获的异常:如果在线程中的异常没有被捕获,可以通过设置线程的UncaughtExceptionHandler来处理未捕获的异常。UncaughtExceptionHandler是一个接口,可以自定义实现来处理异常。
public class MyThread implements Runnable {
    @Override
    public void run() {
        // 执行可能抛出异常的代码
        throw new RuntimeException("Something went wrong");
    }
}

public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 处理未捕获的异常
    }
}

public class Main {
    public static void main(String[] args) {
        Thread myThread = new Thread(new MyThread());
        myThread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        myThread.start();
    }
}

通过设置UncaughtExceptionHandler,可以在发生未捕获的异常时进行处理,例如记录日志、发送通知等。

1.7实践

  1. 生产者-消费者模型:
    这是一个经典的多线程问题,其中一个线程(生产者)生成数据,另一个线程(消费者)消费数据。这个模型可以用于解决生产者和消费者之间的数据交互问题。

关键部分:

  • 创建一个共享的缓冲区,用于生产者和消费者之间的数据交换。
  • 使用synchronized关键字来确保在访问共享缓冲区时的线程安全。
  • 生产者线程在缓冲区未满时生成数据,并通知消费者线程。
  • 消费者线程在缓冲区非空时消费数据,并通知生产者线程。
  1. 并行计算:
    多线程可以用于并行计算,将一个大任务分解成多个小任务,然后并行执行这些小任务,最后将结果合并。

关键部分:

  • 创建一个线程池,用于管理并发执行的任务。
  • 将大任务分解成多个小任务,每个小任务实现Runnable接口。
  • 将小任务提交给线程池进行并行执行。
  • 等待所有任务执行完成后关闭线程池。
  1. 多线程网络编程:
    多线程可以用于处理并发的网络请求,每个请求都可以在独立的线程中进行处理,提高系统的并发处理能力。

关键部分:

  • 创建一个服务器套接字,监听指定的端口。
  • 当有客户端连接时,为每个客户端创建一个独立的线程来处理请求。
  • 在线程中处理客户端的输入和输出流,实现具体的业务逻辑。
  1. 多线程图像处理:
    多线程可以用于图像处理,例如对一张大图进行分块处理,每个线程处理一个块,最后将处理后的块合并成最终的图像。

关键部分:

  • 读取图像文件并创建BufferedImage对象。
  • 将图像分成多个块,每个块由一个线程处理。
  • 在每个线程中,对指定的图像块进行处理,例如应用滤镜、调整亮度等操作。
  • 最后将处理后的图像块合并成最终的图像。
    当然,下面是四个经典的实际应用程序,涉及到Java多线程的实践和练习。我将提供简要的代码示例,以帮助您更好地理解。
  1. 生产者-消费者模型:
    这是一个经典的多线程问题,其中一个线程(生产者)生成数据,另一个线程(消费者)消费数据。这个模型可以用于解决生产者和消费者之间的数据交互问题。
import java.util.LinkedList;

class ProducerConsumer {
    private LinkedList<Integer> buffer = new LinkedList<>();
    private int capacity = 5;

    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (this) {
                while (buffer.size() == capacity) {
                    wait();
                }
                System.out.println("Producer produced: " + value);
                buffer.add(value++);
                notify();
                Thread.sleep(1000);
            }
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (buffer.isEmpty()) {
                    wait();
                }
                int value = buffer.removeFirst();
                System.out.println("Consumer consumed: " + value);
                notify();
                Thread.sleep(1000);
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();

        Thread producerThread = new Thread(() -> {
            try {
                pc.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                pc.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}
  1. 并行计算:
    多线程可以用于并行计算,将一个大任务分解成多个小任务,然后并行执行这些小任务,最后将结果合并。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Task implements Runnable {
    private int taskId;

    public Task(int taskId) {
        this.taskId = taskId;
    }

    @Override
    public void run() {
        System.out.println("Task " + taskId + " is running.");
        // 执行任务的逻辑
    }
}

public class Main {
    public static void main(String[] args) {
        int numTasks = 10;
        ExecutorService executor = Executors.newFixedThreadPool(numTasks);

        for (int i = 0; i < numTasks; i++) {
            executor.submit(new Task(i));
        }

        executor.shutdown();
        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  1. 多线程网络编程:
    多线程可以用于处理并发的网络请求,每个请求都可以在独立的线程中进行处理,提高系统的并发处理能力。
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

class ClientHandler implements Runnable {
    private Socket clientSocket;

    public ClientHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    @Override
    public void run() {
        try {
            InputStream input = clientSocket.getInputStream();
            OutputStream output = clientSocket.getOutputStream();

            // 处理客户端请求的逻辑
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int port = 8080;
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                Thread clientThread = new Thread(new ClientHandler(clientSocket));
                clientThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. 多线程图像处理:
    多线程可以用于图像处理,例如对一张大图进行分块处理,每个线程处理一个块,最后将处理后的块合并成最终的图像。
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

class ImageProcessor implements Runnable {
    private BufferedImage image;
    private int startX;
    private int startY;
    private int width;
    private int height;

    public ImageProcessor(BufferedImage image, int startX, int startY, int width, int height) {
        this.image = image;
        this.startX = startX;
        this.startY = startY;
        this.width = width;
        this.height = height;
    }

    @Override
    public void run() {
        // 图像处理逻辑,例如对指定区域进行滤镜处理等
    }
}

public class Main {
    public static void main(String[] args) {
        String imagePath = "path/to/image.jpg";
        try {
            BufferedImage image = ImageIO.read(new File(imagePath));
            int numThreads = 4;
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();
            int blockWidth = imageWidth / numThreads;
            int blockHeight = imageHeight / numThreads;

            for (int i = 0; i < numThreads; i++) {
                for (int j = 0; j < numThreads; j++) {
                    int startX = i * blockWidth;
                    int startY = j * blockHeight;
                    Thread thread = new Thread(new ImageProcessor(image, startX, startY, blockWidth, blockHeight));
                    thread.start();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.效果

​​​​​​​​​​在这里插入图片描述
在这里插入图片描述

3.代码

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BubbleSortVisualization extends JFrame {
    private int[] array;
    private int[] sortedArray;
    private JPanel originalBarPanel;
    private JPanel sortedBarPanel;
    private JTextField lengthField;
    private JTextField threadField;

    public BubbleSortVisualization() {
        setTitle("冒泡排序可视化");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        // 顶部输入框和按钮
        JPanel inputPanel = new JPanel();
        inputPanel.setLayout(new GridLayout(1,6));
        JLabel lengthLabel = new JLabel("数组长度:");
        lengthField = new JTextField(10);

        JLabel threadLabel = new JLabel("线程数:");
        threadField = new JTextField(10);

        JButton generateButton = new JButton("生成数组");
        JButton sortButton = new JButton("开始排序");
        generateButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                generateArray();
            }
        });

        inputPanel.add(lengthLabel);
        inputPanel.add(lengthField);
        inputPanel.add(threadLabel);
        inputPanel.add(threadField);
        inputPanel.add(generateButton);
        inputPanel.add(sortButton);

        // 中间柱状图面板
        originalBarPanel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                drawOriginalBars(g);
            }
        };

        sortedBarPanel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                drawSortedBars(g);
            }
        };

        // 底部排序按钮

        sortButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int threads = Integer.parseInt(threadField.getText());
                sortArray(threads);
            }
        });

        JPanel barPanelContainer = new JPanel(new GridLayout(2, 1));

        barPanelContainer.add(originalBarPanel);
        barPanelContainer.add(sortedBarPanel);

        add(inputPanel, BorderLayout.NORTH);
        add(barPanelContainer, BorderLayout.CENTER);

        setSize(1500,600);


        setLocationRelativeTo(null);
        setVisible(true);
        array= new int[]{2, 5, 1, 6, 8, 7, 9, 11, 15, 17};
        originalBarPanel.repaint();
        sortedArray=new int[]{1,2,5,6,7,8,9,11,15,17};
        sortedBarPanel.repaint();
    }

    private void generateArray() {
        try {
            int length = Integer.parseInt(lengthField.getText());
            array = new int[length];
            sortedArray = new int[length];
            Random random = new Random();
            for (int i = 0; i < length; i++) {
                int num = random.nextInt(100);
                array[i] = num;
                sortedArray[i] = num;
            }
            originalBarPanel.repaint();

        } catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(this, "请输入有效的数组长度", "错误", JOptionPane.ERROR_MESSAGE);
        }
        lengthField.setText(" ");
    }

    private void sortArray(int threads) {
        ExecutorService executorService = Executors.newFixedThreadPool(threads);
        int chunkSize = array.length / threads;
        for (int i = 0; i < threads; i++) {
            int start = i * chunkSize;
            int end = (i == threads - 1) ? array.length : (i + 1) * chunkSize;
            executorService.execute(new BubbleSortRunnable(start, end));
        }
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            // 等待所有线程完成排序
        }
        bubbleSort(sortedArray); // 主线程进行冒泡排序
        sortedBarPanel.repaint();
        threadField.setText(" ");
    }

    private void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

    private void drawOriginalBars(Graphics g) {
        int barWidth = originalBarPanel.getWidth() / array.length;
        int barHeightScale = originalBarPanel.getHeight() / Arrays.stream(array).max().getAsInt();
        g.setColor(Color.BLUE);
        for (int i = 0; i < array.length; i++) {
            int barHeight = array[i] * barHeightScale;
            g.fillRect(i * barWidth, originalBarPanel.getHeight() - barHeight, barWidth, barHeight);
        }
    }

    private void drawSortedBars(Graphics g) {
        int barWidth = sortedBarPanel.getWidth() / sortedArray.length;
        int barHeightScale = sortedBarPanel.getHeight() / Arrays.stream(sortedArray).max().getAsInt();
        g.setColor(Color.RED);
        for (int i = 0; i < sortedArray.length; i++) {
            int barHeight = sortedArray[i] * barHeightScale;
            g.fillRect(i * barWidth, sortedBarPanel.getHeight() - barHeight, barWidth, barHeight);
        }
    }

    private class BubbleSortRunnable implements Runnable {
        private int start;
        private int end;

        public BubbleSortRunnable(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        public void run() {
            for (int i = start; i < end - 1; i++) {
                for (int j = start; j < end - i - 1; j++) {
                    if (sortedArray[j] > sortedArray[j + 1]) {
                        int temp = sortedArray[j];
                        sortedArray[j] = sortedArray[j + 1];
                        sortedArray[j + 1] = temp;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new BubbleSortVisualization();
            }
        });
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/210230.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【web安全】ssrf漏洞的原理与使用

前言 菜某对ssrf漏洞的总结。 ssrf的作用 主要作用&#xff1a;访问外界无法访问的内网进行信息收集。 1.进行端口扫描&#xff0c;资源访问 2.指纹信息识别&#xff0c;访问相应的默认文件 3.利用漏洞或者和payload进一步运行其他程序 4.get类型漏洞利用&#xff0c;传参数…

Dart编程基础 - 一种新的编程语言

Dart编程基础 – 一种新的编程语言 Dart Programming Essentials - A New Type of Programming Language By JacksonML Dart is a client-optimized language for fast apps on any platform From dart.dev 在1999年之前&#xff0c;和我一样对计算机技术感兴趣的伙伴们&…

Glide结合OkHttp保证短信验证接口携带图形验证码接口返回Cookie值去做网络请求

一、实现效果 二、步骤 注意&#xff1a;仅展示核心部分代码 1、导入依赖 api com.github.bumptech.glide:glide:4.10.0 kapt com.github.bumptech.glide:compiler:4.10.0 api com.squareup.okhttp3:okhttp:3.11.0 api com.squareup.okhttp3:logging-interceptor:3.11.02、自…

非应届生简历模板(13篇)

无论您是职场新人还是转行求职者&#xff0c;一份出色的简历都是获得心仪岗位的关键。本文为大家精选了13篇专业的非应届生简历模板&#xff0c;无论您的经验如何&#xff0c;都可以灵活参考借鉴&#xff0c;提升自己的简历质量。让简历脱颖而出&#xff0c;轻松斩获心仪职位&a…

【【FPGA 之 MicroBlaze定时器中断实验】】

FPGA 之 MicroBlaze定时器中断实验 AXI Timer 具有 AXI 总线接口&#xff0c;能够产生不同时间周期和占空比的时钟、脉冲产生电路、产生与时间有关的中断和用于电机控制的脉宽调制信号。 AXI Timer IP 核提供了一个 AXI4 Lite 接口用于与处理器通信&#xff1b;它内部有两个可…

OpenCV-Python:计算机视觉框架

1.背景 俗话说“工欲善其事必先利其器”&#xff0c;想要学好计算机视觉&#xff0c;需要借助于相关的计算机视觉库&#xff0c;这样在进行学习的时候可以达到事半功倍的效果。 2.早期计算机视觉框架概述 Matlab的最早历史可以追溯到1970年&#xff0c;开始是作为数据处理工…

VL53-400激光测距传感器

一、产品简介 先由激光二极管对准目标发射激光脉冲。经目标反射后激光向各方向散射。部分散射光返回到传感器接收器&#xff0c;被光学系统接收后成像到雪崩光电二极管上。雪崩光电二极管是一种内部具有放大功能的光学传感器&#xff0c;因此它能检测极其微弱的光信号。记录并…

数据库设计实践:粒度的理解与应用示例

粒度是描述数据存储和表示的详细程度。在数据库设计中&#xff0c;理解和正确选择粒度是非常重要的&#xff0c;因为它直接影响到数据的存储效率、查询性能和数据分析的灵活性。 文章目录 粒度的类型&#xff1a;案例粒度选择的考虑因素实际应用 粒度的类型&#xff1a; 细粒度…

UI自动化Selenium find_elements和find_element的区别

# 如果获取的element是list&#xff0c;那么需要用find_elements方法&#xff1b;此方法会返回list&#xff0c;然后使用len() 方法&#xff0c;计算对象的个数&#xff1b; # find_element方法返回的不是list对象&#xff0c;所以导致没办法计算对象个数 # 1.返回值类型不同…

智慧工地一体化解决方案(里程碑管理)源码

智慧工地为管理人员提供及时、高效、优质的远程管理服务&#xff0c;提升安全管理水平&#xff0c;确保施工安全提高施工质量。实现对人、机、料、法、环的全方位实时监控&#xff0c;变被动“监督”为主动“监控”。 一、建设背景 施工现场有数量多、分布广&#xff0c;总部统…

2023.11.30 关于 MyBatis 动态 SQL 的使用

目录 引言 if 标签 trim 标签 where 标签 set 标签 foreach 标签 引言 动态 sql 是 MyBatis 的强大特性之一允许你根据输入的参数动态地构建 sql 语句从而在运行时根据不同的条件生成不同的 sql 核心思想 基于提供的数据和条件&#xff0c;能够修改、增加、删除 sql…

极智芯 | 解读国产AI算力 昆仑芯产品矩阵

欢迎关注我的公众号 [极智视界],获取我的更多经验分享 大家好,我是极智视界,本文分享一下 解读国产AI算力 昆仑芯产品矩阵。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq 昆仑芯来源于百度,2018 年…

用CHAT 写一份销售人员激励方案

问CHAT &#xff1a;写一份销售人员早会激励方案 CHAT回复&#xff1a; 标题&#xff1a;鼓舞斗志&#xff0c;迎接新的一天 -- 销售人员早会激励方案 一、会议的氛围设定&#xff1a; 深呼吸&#xff0c;准备开始一天的事业&#xff1a;清晨的阳光&#xff0c;温暖而明亮&…

redis------在java中操作redis

Redis&#xff08;非关系型数据库&#xff09;简介 redis下载 点击即可进入redis中文网进行下载 百度网盘windows版本 提取码 DMH6 redis主要特点 基于内存存储&#xff0c;读写性能高 适合存储热点数据&#xff08;热点商品、资讯、新闻&#xff09; 企业应用广泛 redis不同…

【代码】CNN-GRU-Attention基于卷积神经网络和门控循环单元网络结合注意力机制的多变量回归预测

程序名称&#xff1a;CNN-GRU-Attention基于卷积神经网络和门控循环单元网络结合注意力机制的多变量回归预测 实现平台&#xff1a;matlab 代码简介&#xff1a;为更准确地预测&#xff0c;提出基于注意力机制的CNN-G&#xff32;U预测模型。该模型主要借助一维卷积单元提取数…

Filebeat使用指南

Filebeat介绍主要优势主要功能配置日志的解析Kibana中设置日志解析安装步骤安装Filebeat安装监控通过prometheus监控 Filebeat和Logstash的主要区别 Filebeat介绍 Filebeat是使用Golang实现的轻量型日志采集器&#xff0c;也是Elasticsearch stack的一员。它可以作为一个agent…

全网关键词采集,免费关键词采集软件使用方法

网站的SEO优化已经成为企业提升在线可见性的不二选择。而关键词的选择和使用则是SEO优化的核心。本文将专心分享关键词采集的正确用法&#xff0c;助您在SEO的道路上掌握正确的方向。 关键词采集&#xff1a;SEO的基础 让我们明确关键词采集的重要性。在搜索引擎的世界里&…

计算机组成学习-数据的表示和运算总结

1、进制与编码 1.1 进位计数法 常用的进位计数法有十进制、二进制、八进制、十六进制等。十六进制每个 数位可取0〜9、A、B、C、D、E、F中的任意一个&#xff0c;其中A、B、C、D、E、F分别表示 10〜15。 八进制数字通常以前缀 "0"&#xff08;零&#xff09;加上数…

【openssl】Window系统如何编译openssl

本文主要记录如何编译出windows版本的openss的lib库 1.下载openssl&#xff0c;获得openssl-master.zip。 a.可以通过github&#xff08;网址在下方&#xff09;上下载最新的代码、今天是2023.12.1我用的master版本&#xff0c;下载之后恭喜大侠获得《openssl-master.zip》 …

Redis高可用集群架构

高可用集群架构 哨兵模式缺点 主从切换阶段&#xff0c; redis服务不可用&#xff0c;高可用不太友好只有单个主节点对外服务&#xff0c;不能支持高并发单节点如果设置内存过大&#xff0c;导致持久化文件很大&#xff0c;影响数据恢复&#xff0c;主从同步性能 高可用集群…