线程知识点总结

Java线程是Java并发编程中的核心概念之一,它允许程序同时执行多个任务。以下是关于Java线程的一些关键知识点总结:

1. 线程的创建与启动

  • 继承Thread类:创建一个新的类继承Thread类,并重写其run()方法。通过创建该类的实例并调用start()方法来启动线程。

  • 实现Runnable接口:创建一个类实现Runnable接口,并实现run()方法。然后将该类的实例作为参数传递给Thread类的构造器,之后通过Thread对象调用start()方法。

  • 使用Executor框架(推荐):从Java 5开始引入,提供了一个更强大的线程管理机制,如Executors类可以创建固定大小的线程池、单线程执行器等,提高了线程复用和管理的效率。

在Java中,线程的创建和启动主要通过以下方式实现:

1. 继承Thread类

步骤如下:

  1. 定义一个新类继承自Thread
  2. 在新类中重写Thread类的run()方法。在这个方法里定义需要并行执行的代码逻辑。
  3. 创建新类的实例。
  4. 调用实例的start()方法来启动线程。注意,不要直接调用run()方法,因为那样会把run()当作普通方法在当前线程中执行,而不是启动新线程。

示例代码;

class MyThread extends Thread {
    public void run() {
        System.out.println("通过继承Thread类创建线程");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start(); // 启动线程
    }
}
2. 实现Runnable接口

步骤如下:

  1. 定义一个类实现Runnable接口
  2. 实现Runnable接口中的run()方法,放入线程需要执行的代码。
  3. 创建实现了Runnable接口的类的实例。
  4. 创建Thread类的实例,并将实现了Runnable接口的类的实例作为参数传递给Thread的构造函数。
  5. 调用Thread实例的start()方法来启动线程。

示例代码:

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("通过实现Runnable接口创建线程");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start(); // 启动线程
    }
}
3使用Executor框架(推荐)

从Java 5开始,还可以使用Executor框架来管理和控制线程,比如使用Executors类创建线程池,这种方式更加灵活且易于管理线程生命周期和资源。

示例代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            System.out.println("通过Executor框架创建线程");
        });
        executor.shutdown(); // 关闭线程池
    }
}

这三种方式中,使用Runnable接口和Executor框架更为灵活和推荐,因为它们提供了更好的解耦和线程池管理能力。

2. 线程状态

Java线程有以下几种状态:

Java线程在其生命周期中会经历多种状态,这些状态根据Java线程API定义,主要包括以下几种:

  • 新建(New): 线程刚被创建,尚未启动。当通过new关键字创建一个Thread对象时,线程处于此状态。

  • 可运行(Runnable): 线程可以被JVM调度执行。这个状态可以细分为两个子状态:

     (1)就绪(Ready): 线程已经具备运行条件,正在等待CPU分配时间片以便执行。     (2)运行中(Running): 线程获得CPU时间片,正在执行线程的run()方法。
  • 阻塞(Blocked): 线程因为某种原因(如等待锁、I/O操作等)而暂停执行,此时线程不会被分配CPU时间片,直到导致阻塞的原因解除。

  • 等待(Waiting): 线程因为调用了Object.wait()Thread.join()或者LockSupport.park()等方法而进入等待状态。这种状态下,线程必须等待其他线程执行特定动作(如通知notify()notifyAll())才能继续执行。

  • 超时等待(Timed Waiting): 与等待状态相似,但是有一个明确的等待时间限制,例如通过Thread.sleep(long millis)Object.wait(long timeout)Thread.join(long millis)等方法设置了等待时间。超过这个时间后,线程会自动恢复到可运行状态,无需其他线程显式唤醒。

  • 终止(Terminated): 线程执行完毕或因异常结束,线程生命周期结束。

3. 线程同步

为了防止多线程环境下的数据不一致问题,Java提供了以下同步机制:

  • synchronized关键字:用于方法或代码块,保证同一时刻只有一个线程可以访问被synchronized保护的代码或方法。
  • Lock接口(java.util.concurrent.locks):比synchronized更灵活,提供了更多的锁操作,如公平锁、非公平锁、可重入锁等。
  • volatile关键字:用于变量,确保了多线程之间的可见性,但不保证原子性。

Java线程同步是为了避免多线程环境下对共享资源的不正确访问而导致的数据不一致性问题。Java提供了多种线程同步机制来保障线程安全,主要包含以下几种:

1. synchronized关键字
  • 方法同步:在方法声明上使用synchronized关键字,这样一次只能有一个线程访问该方法。
public synchronized void synchronizedMethod() {
    // 方法体
}
  • 代码块同步:可以在特定的代码块上使用synchronized,指定一个对象作为锁。
public void synchronizedBlockMethod() {
    synchronized(this) {
        // 需要同步的代码块
    }
}
2. Lock接口

java.util.concurrent.locks.Lock接口提供了比synchronized更灵活的锁定机制,它允许尝试非阻塞地获取锁、能够被中断地等待锁以及超时获取锁等特性。

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

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

    public void method() {
        lock.lock();
        try {
            // 需要同步的代码
        } finally {
            lock.unlock();
        }
    }
}
3. volatile关键字

虽然volatile不是一种同步机制,但它能保证变量的可见性和部分有序性,适用于状态标记量的读写操作。

private volatile boolean flag = false;

public void setFlag(boolean newValue) {
    flag = newValue;
}

public boolean getFlag() {
    return flag;
}
4. 原子类(Atomic)

Java提供了java.util.concurrent.atomic包下的原子类,如AtomicIntegerAtomicBoolean等,它们通过CAS(Compare and Swap,比较并交换)操作实现线程安全的更新操作。

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}
5. Condition接口

Condition接口(在Lock接口的实现类中提供)用于更复杂的线程间协调,它是对传统Object类中的wait()notify()notifyAll()方法的改进版本,可以和Lock一起使用,实现更细粒度的线程同步。

总结

选择合适的线程同步机制取决于具体的应用场景。对于简单的同步需求,synchronized关键字往往是首选;对于更复杂的并发控制,LockCondition提供了更高的灵活性;而对于基本类型的原子操作,原子类提供了简单且高效的解决方案。正确应用这些同步机制,可以有效地避免竞态条件,保证程序的线程安全。

4. 线程间通信

  • wait()、notify()、notifyAll():这些方法定义在Object类中,用于线程间的等待/通知机制,必须在同步上下文中使用。
  • Condition接口(java.util.concurrent.locks):作为Lock接口的一部分,提供了更灵活的线程间协调行为,可以替代传统的wait/notify。

Java中线程间的通信主要是指一个线程向另一个线程发送信号,或者多个线程之间互相协作完成任务的能力。Java提供了多种机制来支持线程间的通信,以下是一些常用的方法:

1. wait(), notify(), notifyAll()

这三个方法都是在Object类中定义的,常用于线程间的同步和通信。它们必须在同步代码块或同步方法中使用。

  • wait():使当前线程等待,并释放对象的监视器锁。等待直到其他线程调用该对象的notify()notifyAll()方法。
  • notify():唤醒在此对象监视器上等待的单个线程,选择是任意的,并且在Java中不可预测。
  • notifyAll():唤醒在此对象监视器上等待的所有线程。
2. Condition接口

java.util.concurrent.locks.Condition接口提供了类似wait()notify()notifyAll()的功能,但功能更强大,它可以和Lock配合使用,实现更灵活的线程间协调行为。

  • await():类似wait(),使当前线程等待,并释放锁。
  • signal():唤醒一个等待的线程,类似notify()
  • signalAll():唤醒所有等待的线程,类似notifyAll()
3. CountDownLatch

java.util.concurrent.CountDownLatch是一个同步辅助类,它允许一个或多个线程等待其他线程完成一系列操作后再继续执行。

CountDownLatch latch = new CountDownLatch(n);
// n个线程完成任务后调用latch.countDown();
latch.await(); // 其他线程在此等待,直到计数器为0
4. CyclicBarrier

java.util.concurrent.CyclicBarrier也是同步辅助类,它允许一组线程相互等待,直到到达某个公共屏障点后再一起继续执行。

CyclicBarrier barrier = new CyclicBarrier(parties);
// 线程执行到barrier点时调用await()
barrier.await();
5. Semaphore

java.util.concurrent.Semaphore是一个计数信号量,可以控制同时访问特定资源的线程数量。

Semaphore semaphore = new Semaphore(permits);
semaphore.acquire(); // 获取许可
// 执行代码
semaphore.release(); // 释放许可
6. Exchanger

java.util.concurrent.Exchanger用于在线程间进行数据交换,两个线程通过exchange()方法交换数据,只有双方都准备好时才会发生交换。

Exchanger<String> exchanger = new Exchanger<>();
// 线程A
String dataA = "From A";
String received = exchanger.exchange(dataA); // 等待与B交换数据
// 线程B
String dataB = "From B";
received = exchanger.exchange(dataB); // 等待与A交换数据

正确使用这些线程间通信机制,可以有效地帮助开发人员解决多线程环境下的同步和协作问题。

5. 线程池

  • Executor框架:提供了一组线程池相关的类,如ThreadPoolExecutorScheduledThreadPoolExecutor等,用于管理和控制线程的创建、执行和销毁,提高性能和资源利用率。

Java线程池是Java并发编程中的一个重要概念,它是一种基于池化概念管理线程的技术,可以重复使用预先创建的线程,以减少线程创建和销毁的开销,提高响应速度和整体性能。Java通过java.util.concurrent.Executor框架来支持线程池的创建和管理,其中最常用的接口和类包括:

1. Executor接口

这是最顶层的执行者接口,它定义了一个execute(Runnable command)方法来执行给定的任务。

2. ExecutorService接口

扩展了Executor接口,提供了更丰富的管理任务和线程池的方法,如提交Callable任务、关闭线程池等。

3. ThreadPoolExecutor类

实现了ExecutorService接口,是最常见的线程池实现类,提供了高度可配置的线程池实现。

4. ScheduledExecutorService接口

扩展了ExecutorService接口,支持计划执行任务,即定时或周期性任务。

5. ScheduledThreadPoolExecutor类

实现了ScheduledExecutorService接口,用于支持定时及周期性任务的线程池。

6. Executors工厂类

提供了一系列静态方法来创建不同类型的线程池,包括:

  • newFixedThreadPool(int nThreads): 创建一个固定大小的线程池,可重用固定数量的线程,适合执行大量短期异步任务。
  • newSingleThreadExecutor(): 创建一个只有一个线程的线程池,确保所有的任务按照顺序执行。
  • newCachedThreadPool(): 创建一个可缓存线程池,如果线程池长度超过处理所需,可灵活回收空闲线程,若无可回收,则新建线程。
  • newScheduledThreadPool(int corePoolSize): 创建一个固定核心线程数的线程池,支持定时及周期性任务执行。
  • newWorkStealingPool(int parallelism): (Java 8引入)创建一个拥有多个任务队列的线程池,使用工作窃取算法来充分利用CPU资源。
使用示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 10; i++) {
            Runnable worker = new WorkerThread("" + i);
            executor.execute(worker); // 提交任务到线程池执行
        }
        
        // 关闭线程池,不再接受新的任务,已提交的任务会执行完
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("所有任务已完成");
    }
}

class WorkerThread implements Runnable {
    private String command;

    public WorkerThread(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始. 命令 = " + command);
        processCommand();
        System.out.println(Thread.currentThread().getName() + " 结束.");
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

合理使用线程池可以有效地管理线程生命周期、提高系统资源的使用效率,以及提升系统的响应速度和吞吐量。但同时也需要注意,不当的配置和使用可能会导致资源耗尽、死锁等问题,因此需要仔细设计和监控线程池的使用情况。

6. 并发工具类

Java并发包(java.util.concurrent)还提供了许多实用的并发工具类,如:

  • CountDownLatch:允许一个或多个线程等待其他线程完成一系列操作。
  • CyclicBarrier:让一组线程等待所有线程到达某个屏障后再一起执行后续操作。
  • Semaphore:控制同时访问特定资源的线程数量。
  • FutureCallable:用于异步计算,可以获取线程执行的结果。

Java并发编程库(java.util.concurrent包及其子包)提供了丰富的工具类来帮助开发者高效、安全地处理并发问题。以下是一些常用的并发工具类:

1. CountDownLatch

java.util.concurrent.CountDownLatch是一个同步辅助类,允许一个或多个线程等待其他线程完成一系列操作。初始化时设置一个计数器,每当一个任务完成就递减计数器,计数器为0时,所有等待的线程被释放。

2. CyclicBarrier

java.util.concurrent.CyclicBarrier也是一种同步辅助类,它允许一组线程相互等待,直到达到一个共同的屏障点,然后所有线程一起继续执行。与CountDownLatch不同的是,它可以在重置后重复使用。

3. Semaphore

java.util.concurrent.Semaphore是一个计数信号量,可以用来控制同时访问特定资源的线程数量,或者控制同时执行的任务数量。

4. Exchanger

java.util.concurrent.Exchanger用于两个线程之间交换对象,当两个线程都到达同步点并且准备好交换时,Exchanger会交换这两个线程的对象。

5. Phaser

java.util.concurrent.Phaser是一个可重用的同步栏栅,它支持注册多个 parties,并在每个阶段(phase)等待所有 parties 到达屏障点。

6. ConcurrentCollections

Java并发包提供了线程安全的集合类,如ConcurrentHashMapCopyOnWriteArrayListConcurrentSkipListMap等,这些集合类在高并发环境下表现更好。

7. Locks
  • ReentrantLock: 可重入互斥锁,提供比synchronized更灵活的锁定机制,如公平锁、非公平锁、尝试获取锁等。
  • ReentrantReadWriteLock: 支持读写分离的锁,允许多个读线程同时访问,但只允许一个写线程或一个读线程和一个写线程同时访问。
8. Executors

java.util.concurrent.Executors工厂类提供了创建不同类型线程池的方法,如newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool等。

9. Future & Callable
  • Future: 代表异步计算的结果,提供了检查计算是否完成、获取结果、取消计算等方法。
  • Callable: 类似于Runnable,但可以返回结果,并且可以抛出异常,通常与FutureTask和线程池一起使用。
10. ForkJoinPool 和 RecursiveTask / RecursiveAction

Fork/Join框架,用于并行处理大数据集,通过将大任务拆分成小任务并行处理。RecursiveTask用于有返回值的任务,RecursiveAction用于没有返回值的任务。

这些工具类覆盖了并发编程中的大多数场景,合理运用它们可以大大提高并发程序的性能和可靠性。

7. 死锁与避免

死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力干涉,它们都将无法推进下去。避免死锁的方法包括:

  • 避免一个线程同时获取多个锁。
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用lock的tryLock方法代替内部锁机制。
  • 按顺序加锁,确保所有线程按照相同的顺序请求锁。

在Java中,死锁是指两个或多个线程互相等待对方持有的锁,从而导致所有线程都无法继续执行的情况。

四个必要条件:
  1. 互斥条件:资源不能被多个线程同时占有,至少有一个资源必须是独占的。
  2. 请求与保持条件:已经持有至少一个资源的线程尝试获取额外的资源。
  3. 不可抢占条件:已分配给线程的资源在该线程释放前不能被其他线程抢占。
  4. 循环等待条件:存在一种线程资源的循环等待链,每个线程都持有下一个线程所需的资源,并等待先前线程释放资源。
避免死锁:
  1. 避免嵌套锁:尽量减少在一个线程中同时获取多个锁的需求。如果必须获取多个锁,确保所有线程以相同的顺序获取锁。
  2. 使用超时锁:使用tryLock(long time, TimeUnit unit)方法尝试获取锁,如果在指定时间内无法获取到锁,则放弃并处理相应的逻辑,而不是无限等待。
  3. 锁顺序:总是按照固定的顺序获取锁,这样可以避免循环等待的条件,例如定义全局的锁顺序或者使用锁的自然顺序(如锁对象的hashcode排序)。
  4. 锁分解:将大范围的锁分解为多个小范围的锁,减少锁的竞争。
  5. 使用并发工具类:利用java.util.concurrent包提供的高级并发工具,如SemaphoreCountDownLatchCyclicBarrierExchanger等,这些工具设计时已经考虑了线程安全和死锁问题。
  6. 按需加锁:尽量减少锁的使用,只有在真正需要同步资源时才加锁,不必要的锁会导致不必要的阻塞和潜在的死锁风险。
  7. 检测与恢复:虽然Java标准库本身不直接提供死锁检测工具,但在复杂系统中可以设计监控和诊断机制,定期检查线程状态,一旦检测到死锁,可以采取重启线程或事务等恢复措施。

通过上述策略,可以大大降低程序中死锁发生的概率,提高系统的稳定性和响应性。

掌握以上知识点有助于深入理解Java线程机制,并在实际开发中有效利用多线程提升程序性能。

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

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

相关文章

代码随想录算法训练营第三十二天| 122.买卖股票的最佳时机II,55. 跳跃游戏 ,45.跳跃游戏II

122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int[] prices) {if(prices.length 0){return 0;}int min prices[0];int result 0;for(int i1;i<prices.length;i){if(prices[i] > min){result (prices[i]…

【SQL】牛客网SQL非技术入门40道代码|练习记录

跟着刷题&#xff1a;是橘长不是局长哦_哔哩哔哩_bilibili 6查询学校是北大的学生信息 select device_id, university from user_profile where university 北京大学 7查找年龄大于24岁的用户信息 select device_id, gender, age, university from user_profile where age…

【C++初阶学习】第十三弹——优先级队列及容器适配器

C语言栈&#xff1a;数据结构——栈(C语言版)-CSDN博客 C语言队列&#xff1a;数据结构——队列&#xff08;C语言版&#xff09;-CSDN博客 C栈与队列&#xff1a;【C初阶学习】第十二弹——stack和queue的介绍和使用-CSDN博客 前言&#xff1a; 在前面&#xff0c;我们已经…

使用 C# 学习面向对象编程:第 1 部分

介绍 C# 完全基于面向对象编程 (OOP)。首先&#xff0c;类是一组相似的方法和变量。在大多数情况下&#xff0c;类包含变量、方法等的定义。当您创建此类的实例时&#xff0c;它被称为对象。在此对象上&#xff0c;您可以使用定义的方法和变量。 步骤1. 创建名为“LearnClass…

⌈ 传知代码 ⌋ 记忆大师

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

从信号灯到泊车位,ARMxy如何重塑城市交通智能化

城市智能交通系统的高效运行对于缓解交通拥堵、提高出行安全及优化城市管理至关重要。ARMxy工业计算机&#xff0c;作为这一领域内的技术先锋&#xff0c;正以其强大的性能和灵活性&#xff0c;悄然推动着交通管理的智能化升级。 智能信号控制的精细化管理 想象一下&#xff0…

[发布]嵌入式系统远程测控软件-基于Qt

目录 一. 引言二. 软件功能2.1 原理2.2 软件功能2.3 运行环境 三. 软件操作使用3.1 软件界面3.2 软件功能使用详解3.2.1 连接3.2.2 数据监测3.2.3 数据修改3.2.4 数据保存 3.3 软件的硬件连接 四. 通信协议——STM32移植篇4.1 通信协议4.2 STM32如何传输浮点数4.3 简单移植&…

使用Redis的优势以及会引发的问题

优势 ①使用redis代表着高性能还有高并发&#xff0c;高性能很好理解&#xff0c;redis会缓存我们访问的数据。他是基于内存的&#xff0c;第一次访问数据库我们可能需要800ms&#xff0c;但是访问后如果使用redis进行缓存&#xff0c;第二次乃至后面访问相同的数据就只需要去…

嵌入式仪器模块:示波器模块和自动化测试软件

示波器模块 • 32 位分辨率 • 125 MSPS 采样率 • 支持单通道/双通道模块选择 • 低速模式可实现实时功率分布和整机功率检测 • 高速模式可实现信号分析和上电时序测量 应用场景 • 抓取并分析波形的周期、幅值、异常信号等指标 • 电源纹波与噪声分析 • 信号模板比…

档案数字化管理的工具有哪些

档案数字化管理的工具可以包括以下几种&#xff1a; 1. 扫描仪/数字拍摄仪&#xff1a;用于将纸质文件数字化为电子文件的工具。 2. OCR&#xff08;光学字符识别&#xff09;软件&#xff1a;用于将扫描或拍摄的图像文件转换为可编辑的文本文件。 3. 文件管理系统/专久智能电子…

RAG检索与生成的融合

1、rag定义 检索增强生成 (RAG) 模型代表了检索系统和生成模型两大不同但互补组件完美结合的杰作。通过无缝整合相关信息检索和生成与背景相关的响应&#xff0c;RAG模型在人工智能领域达到了前所未有的复杂程度。 2、rag工作流程 2.1、rag整体框架 query通过llm处理后&…

doris FE 在Windows环境下编译调试开发环境

前言&#xff1a; doris fe 在win下调试运行&#xff0c;和正常java项目有一些差异&#xff0c;主要是有与be&#xff08;c&#xff09;通信代码的生成 在win环境下不能直接生成&#xff0c;因此需要现在linux下生成之后&#xff0c;再拷贝到本地来&#xff0c;然后进行编译&a…

王学岗鸿蒙开发(北向)——————(十三)音乐播放器

AudioRenderer适合录音 AVPlayer:简单的本地单曲播放 MP3文件放置的地方 import media from ohos.multimedia.media import common from ohos.app.ability.common; Entry Component struct Index {//第1步&#xff1a;avPlayer:media.AVPlayer nullasync onPageShow(){//第…

665. 非递减数列(中等)

665. 非递减数列 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;665. 非递减数列 2.详细题解 判断在最多改变 1 个元素的情况下&#xff0c;该数组能否变成一个非递减数列&#xff0c;一看到题目&#xff0c;不就是遍历判断有几处不…

解决阿里云的端口添加安全组仍然无法扫描到

发现用线上的网站扫不到这个端口&#xff0c;这个端口关了&#xff0c;但是没有更详细信息了 我用nmap扫了一下我的这个端口&#xff0c;发现主机是活跃的&#xff0c;但是有防火墙&#xff0c;我们列出云服务器上面的这个防火墙list&#xff0c;发现确实没有5566端口 参考&a…

Python框架scrapy有什么天赋异禀

Scrapy框架与一般的爬虫代码之间有几个显著的区别&#xff0c;这些差异主要体现在设计模式、代码结构、执行效率以及可扩展性等方面。下面是一些关键的不同点&#xff1a; 结构化与模块化&#xff1a; Scrapy&#xff1a;提供了高度结构化的框架&#xff0c;包括定义好的Spider…

⌈ 传知代码 ⌋ Flan-T5 使用指南

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

Unity | Shader基础知识(番外:了解内置Shader-Standard<二>)

目录 前言 一、Standard参数详解 1.NormalMap法线贴图 2.HeightMap高度贴图 3.Occlusion遮挡贴图 4.DetailMask细节遮挡 5.Emission自发光 6.Tiling铺地砖和Offset偏移度 二、作者的碎碎念 前言 Unity | Shader基础知识(番外&#xff1a;了解内置Shader-Standard&#x…

【qsort函数】

前言 我们要学习qsort函数并利用冒泡函数仿照qsort函数 首先我们要了解一下qsort&#xff08;快速排序&#xff09; 这是函数的的基本参数 void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); 简单解释一下 base&#xff1a;指向…

MySQL-数据处理函数

026-distinct去重 select job from emp;加个 distinct 就行了 select distinct job from emp;注意&#xff1a;这个去重只是将显示的结果去重&#xff0c;原表数据不会被更改。 select 永远不会改变原数据 select distinct deptno, job from emp order by deptno asc;027-数…