多线程和并行程序设计

可运行对象:可以理解为一个任务,是Runnable接口的实例。

线程:一个任务从头到尾的执行流程,本质上是便于任务执行的对象。

线程状态:线程状态是操作系统中线程生命周期的一个阶段。

进程:内存中运行的应用程序。一个进程中可以启动多个线程。

任务类中一般包含一些属性和方法,这些属性和方法可以用来描述任务的状态和行为。

下面是定义一个任务类并创建一个线程的示例。

public class Task implements Runnable {
    private String name;
    public Task(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        // 执行任务
        System.out.println("Executing task: " + name);
    }

    public static void main(String[] args) {
        // 创建一个任务
        Task task = new Task("Task 1");
        // 创建一个线程来执行任务
        Thread thread = new Thread(task);
        // 启动线程
        thread.start();
    }
}

调用start方法后,run方法变成了Thread默认要执行的普通方法。

我觉得考...

程序改错

1.forFun 类实现了 Runnable 接口,但是在 forFun 构造函数中,它创建了一个新的 forFun 对象,并尝试在一个新的线程中运行它。然而,这个新的 forFun 对象并没有实现 Runnable 接口,所以它不能作为 Runnable 传递给 Thread 构造函数

public class forFun implements Runnable {
    public static void main(String[] args) {
        new forFun();
    }
    public forFun(){
        new Thread(this).start();
    }

    @Override
    public void run() {
        System.out.println("1");
    }
}

这样,就输出5遍test

2. 调用了两次start

每个线程只能启动一次,删掉一个start就好了

使用Platform.runLater的目的是什么?

告诉系统在应用程序线程中运行Runnable对象。

★线程状态(这个肯定考)

  1. 新建(New):当一个线程被创建,但还没有开始运行时,它处于新建状态。

  2. 可运行(Runnable):线程已经准备好运行,但可能正在等待CPU的调度。

  3. 运行(Running):线程正在CPU上执行。

  4. 阻塞(Blocked):线程正在等待某个事件(如I/O操作完成),所以它暂时停止执行,等待事件发生。

  5. 等待(Waiting):线程在等待另一个线程执行某个操作。

  6. 计时等待(Timed Waiting):线程在等待另一个线程执行某个操作,但有时间限制。

  7. 终止(Terminated):线程已经执行完毕,或者被其他线程中断。

        当我们创建一个新的线程时,它最初处于新建new状态。然后,当我们调用start()方法时,线程开始运行,进入可运行Runnable状态。如果CPU调度器选择执行这个线程,那么它就会进入运行状态。如果线程在运行过程中遇到需要等待的事件(如I/O操作),那么它就会进入阻塞blocked状态。当等待的事件完成后,线程会再次进入可运行状态,等待CPU调度器的选择。如果是主动等待,那么进入waiting状态,等待被唤醒后再次进入Runnable状态,如果是主动睡眠,就进入time waiting状态,等待睡眠时间期限满后,进入Runnable状态。注意,当running状态异常中断或执行完毕,则进入dead阶段。

看图加深理解,上面是我自己总结的

使用线程池的好处?

使用线程池可以有效地管理和调度线程,提高系统的并发性能,同时可以有效地管理系统的资源,避免系统资源耗尽,提高系统的稳定性和响应性。

分为两种,同步锁Synchronized和重入锁ReentrantLock

1.同步锁

public class LockExample {
    private final Object lock = new Object();

    public void method() {
        synchronized (lock) {
            // 这里是临界区,只有一个线程可以进入
            // 其他线程需要等待这个线程释放锁
        }
    }
}

2.重入锁

 同一个线程,可以多次获取这个锁。但是一定要记得及时释放锁,以免出现死锁。

import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private final ReentrantLock lock = new ReentrantLock();
    public void method() {
        lock.lock();
        try {
            // 这里是临界区,只有一个线程可以进入
            // 其他线程需要等待这个线程释放锁
        } finally {
            lock.unlock();
        }
    }
}

还有另一种描述的方式

1.创建 private static Lock lock=new ReentrantLock();

2.得到 lock.lock()

3.释放 lock.unlock(); 

4.创建锁条件 lock.newCondition()

await() 释放锁,挂起线程。一旦条件满足,它就会被唤醒,再次获得lock。

signal()/signalAll():将condition.await()中FIFO队列中第一个Node唤醒(或者全部唤醒)。

死锁:两个或多个进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

如何避免死锁:实行资源排序,让各线程加锁的顺序保持相同。

 存款提款的那个程序,应该会考吧(后面有)

 只要余额发生变化,存款任务都会通知提款任务。当唤醒提款任务时,条件(balance<amount)的判断结果可能仍然为true。如果使用if语句,提款任务有可能导致不正确的提款。

wait() 这个方法应该在一个循环中使用,而不是在一个if语句中。 修改后,只有异常情况中wait才会被唤醒。

synchronized(object){
    while(!condition){
        try{
            object.wait();
        }
        catch(InterruptedException ex){
            // 处理异常
        }
    }
}

Buffer类 

 

Buffer类中的read和write通常是在单线程环境下调用的,以确保数据的正确性和一致性。

当调用read方法时,如果队列为空,那么read方法会一直等待,直到有新的数据可以读取。

当调用write方法时,如果队列已满,那么write方法也会阻塞,直到队列中有足够的空间来存储新的数据。

阻塞队列 

(1)阻塞队列:支持两个附加操作的队列,这两个附加的操作支持阻塞的插入和移除方法。

阻塞队列是线程安全的队列访问方式

当阻塞队列需要插入元素,若队列已满,则线程会阻塞直至队列非满。

当阻塞队列需要删除元素,若队列为空,则线程会阻塞直至队列非空。

(2)Java中支持的阻塞队列:

ArrayBlockingQueue

LinkedBlockingQueue

PriorityBlockingQueue

 信号量 Semaphore

创建:Semaphore semaphore = new Semaphore(3);

获取一个信号量:Semaphore.acquire();

释放一个信号量:Semaphore.release();

锁只能锁一个共享资源,信号量可以锁多个共享资源。

=>对多个资源的访问控制用信号量,对一个资源的访问控制用锁。

同步合集

同步合集:实现线程安全的Collections。

ArrayList不是同步的,让它实现synchronizedList使其同步。

 Fork/Join

是一个抽象类

Fork/Join框架:Fork分割,Join合并。用于执行并行任务,将大问题分割成子问题。

定义:

public abstract class ForkJoinTask<V> implements Future<V>, Serializable{ }

V:任务执行的结果的类型

Future<V>:表示ForkJoinTask实现了Future接口,因此可以用来获取任务执行结果。

RecursiveAction(用于不返回值的任务)和RecursiveTask(用于返回值的任务),是ForkJoinTask的两个子类

娱乐一下,ForkPool

RecursiveAction mainTask = new SortTask(list);
ForkJoinPool pool = new ForkJoinPool();
pool.invoke(mainTask);

一个包含了同步锁Synchronized的示例代码,实现启动1000个线程,每个线程给初始值为0的变量sum加1

import java.util.concurrent.*;
 
public class forFun {
    private Integer sum = new Integer(0);
    public static void main(String[] args) {
        forFun test = new forFun();
        System.out.println("What is sum ? " + test.sum);
    }
 
    public forFun() {
        synchronized(this){//同步代码块,确保只有一个线程可以访问以下代码
            ExecutorService executor = Executors.newFixedThreadPool(1000);//创建大小1000 的线程池
            for (int i = 0; i < 1000; i++) {
                executor.execute(new SumTask());//将SumTask交给线程池
            }
            executor.shutdown();
            while(!executor.isTerminated()) {//等待任务完成
            }
        }
    }
    class SumTask implements Runnable {// 定义一个实现Runnable接口的SumTask类
        public void run() {
            synchronized(this){// 同步代码块,确保只有一个线程可以访问以下代码
                int value = sum.intValue() + 1;
                sum = new Integer(value);
            }
        }
    }
}

来个好玩的,账户存款/取款任务

有线程池,也有同步锁。lz之前跟着导师做区块链天天用Solidity写这种,看到就无比亲切,爽翻了

import java.util.concurrent.*;

public class forFun {
    private static Account account = new Account();
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2); // 创建固定大小为2的线程池
        executor.execute(new DepositTask());
        executor.execute(new WithdrawTask());
        executor.shutdown();
        System.out.println("Deposit Task\t\tWithdraw Task\t\tBalance");
    }

    // 存款任务
    public static class DepositTask implements Runnable {
        public void run() {
            try {
                while (true) {
                    account.deposit((int) (Math.random() * 10) + 1);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    // 取款任务
    public static class WithdrawTask implements Runnable {
        public void run() {
            while (true) {
                account.withdraw((int) (Math.random() * 10) + 1);
            }
        }
    }

    // 账户类
    public static class Account {
        private int balance = 0;

        // 获取账户余额
        public int getBalance() {
            return balance;
        }

        // 存款方法,使用 synchronized 保证线程安全
        public synchronized void deposit(int amount) {
            balance += amount;
            System.out.println("Deposit " + amount +
                    "\t\t\t\t\t" + getBalance());
            notifyAll(); // 唤醒其他等待线程
        }

        // 取款方法,使用 synchronized 保证线程安全
        public synchronized void withdraw(int amount) {
            try {
                while (balance < amount)  // 当余额不足时,进入等待状态
                    wait();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }

            balance -= amount;
            System.out.println("\t\t\tWithdraw " + amount +
                    "\t\t" + getBalance());
        }
    }
}

猜你喜欢

转载自blog.csdn.net/mynameispy/article/details/135306516