学习笔记071——Java中的【线程】

ops/2024/12/18 17:25:02/

文章目录

  • 1、基础
  • 2、进程和线程
  • 3、什么是多线程
  • 4、Java 中线程的使用
  • 5、Java 中创建线程的方式
    • 5.1、继承 Thread 类
    • 5.2、实现 Runnable 接口
    • 5.3、继承 Thread 和实现 Runnable 接口的区别
    • 5.4、实现 Runnable 接口的优化
  • 6、线程的状态
  • 7、线程调度
  • 8、线程同步
  • 9、线程安全的单例模式
    • 9.1、字符串常量池
    • 9.2、包装类常量池
  • 10、死锁
  • 11、重入锁

1、基础

提升程序性能非常重要的一种方式,使用多线程可以让程序充分利用 CPU 的资源,提高 CPU 的使用率。

线程的优点:

1、CPU 资源得到更合理的利用

2、程序设计更加简洁

3、程序响应更快,运行效率更高

线程的缺点:

1、需要更多的内存空间来支持多线程

2、多线程并发访问可能会影响数据的准确性

3、数据被多线程共享可能会出现死锁

2、进程和线程

什么是进程?计算机正在运行的一个独立的应用程序。

什么是线程线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成。

进程和线程都是应用程序在执行过程中的概念,动态的,如果应用程序没有运行起来,那么就不存在进程或线程的概念。

进程和线程的区别?

进程在运行的时候,会有自己独立的内存空间,每个进程所占用的内存都是独立的,互不影响。

多个线程是共享内存空间的,但是线程的执行是相互独立的,线程必须依赖于进程才能执行,单独的线程是无法执行的,由进程来控制多个线程的执行。

3、什么是多线程

在同一个进程中,多个线程同时执行,这里的同时执行是一个假象,并不是真正的同时执行。

系统会为每个线程分配 CPU 资源,在某个具体的时间段内 CPU 资源会被某个线程所占用,在下一个时间段内被另外一个线程所占用,多个线程交替占用 CPU 资源,因为线程运行速度很快,所以看起来是同时在执行的。

java">package com.htl.test;public class Test {public static void main(String[] args) {for (int i = 0; i < 10; i++) {System.out.println("++++++++++main");}Test2 test2 = new Test2();test2.test();}
}

Java 程序的 main 方法就是一个主线程,无论在 main 方法中创建多个对象,调用多少个方法,它们都是单线程,有主线程来依次排队完成多个任务的执行。

程序中写的业务代码可以看作是一个个任务,这些任务是需要通过线程来执行的。

java">package com.htl.test;public class Test {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println("thread------------------");}}).start();new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println("===============OK================");}}).start();}
}

取快递的例子

java">package com.htl.test;public class Express {private Integer id;public Express(Integer id) {this.id = id;}public void get() {System.out.println("开始取快递...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("完成取快递...");}
}
java">package com.htl.test;public class Test3 {public static void main(String[] args) {//100个快递,单线程//100S全部取完for (int i = 0; i < 100; i++) {Express express = new Express(i);express.get();}
//        for (int i = 0; i < 100; i++) {
//            Express express = new Express(i);
//            new Thread(()->{
//                express.get();
//            }).start();
//        }}
}

4、Java 中线程的使用

Java 中创建线程有几种方式?

  • 继承 Thread
  • 实现 Runnable
  • 实现 Callable

继承 Thread

Thread 类是 JDK 提供的专门用来创建线程对象的类。

创建一个自定义的线程类,继承 Thread

线程和任务

任务就是具体的事情,线程是完成这件事情的具体的对象

张三去取快递

取快递就是任务

张三就是线程

在这里插入图片描述

Java 中的 Thread 类如何绑定任务?

把当前这个线程要执行的任务定义到 run 方法中即可。

java">package com.htl.test2;public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("MyThread.........");}}
}
java">package com.htl.test2;public class Test {public static void main(String[] args) {MyThread myThread = new MyThread();//启动myThread.start();for (int i = 0; i < 100; i++) {System.out.println("+++++++++++++main++++++++++++++++++");}}
}

注意:创建子线程对象之后,一定要调用 start 方法,才能让线程启动,去争夺 CPU 资源,才是多线程,如果调用 run 方法,那么线程根本没有启动,不会去争夺 CPU 资源,所以仍然是单线程

5、Java 中创建线程的方式

5.1、继承 Thread 类

java">package com.htl.test2;public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("MyThread.........");}}
}
java">package com.htl.test2;public class Test {public static void main(String[] args) {MyThread myThread = new MyThread();//启动myThread.start();}
}

5.2、实现 Runnable 接口

java">package com.htl.test3;public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"---"+i);}}
}
java">package com.htl.test3;public class Test {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}

1、自定义一个类,实现 Runnable 接口。

2、创建一个 Thread,将第一步创建的 Runnable 对象注入到 Thread 对象中,启动 Thread 对象。

Java 中线程的使用需要两个参与角色,一个是线程,一个是任务。

任务是描述要做的事情,线程是具体执行任务的对象。

张三取快递

张三就是线程 Thread

取快递就是任务 Runnable

5.3、继承 Thread 和实现 Runnable 接口的区别

无论哪种形式,最终都要将任何和线程进行集成

集成 Thread 的方式在定义类的时候已经将线程和任务整合到了一起

实现 Runnable 接口的方式是将任务和线程分开的,使用的时候再整合到一起

继承

实现接口

实际开发中推荐使用实现接口的方式进行开发,可以做到解耦合。

5.4、实现 Runnable 接口的优化

1、单独定义一个类实现接口

java">package com.htl.test4;public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--" + i);}}
}
java">package com.htl.test4;public class Test {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}

2、不用单独定义一个类,而使用内部类

java">package com.htl.test4;public class Test2 {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}static class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--" + i);}}}
}

3、匿名内部类

java">package com.htl.test4;public class Test3 {public static void main(String[] args) {Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--" + i);}}};Thread thread = new Thread(runnable);thread.start();}
}

4、lambda 表达式

java">package com.htl.test4;public class Test4 {public static void main(String[] args) {Thread thread = new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "--" + i);}});thread.start();}
}

Lambda 表达式是指将方法的实现作为参数进行传递

某个类中,需要使用到接口,构造器中需要传入接口来创建对象

接口只能有一个抽象方法,因为如果有多个方法,那么 Lambda 传入的方法实现就无法确定到底是接口中的哪个方法了。

6、线程的状态

线程一共有 5 种状态,线程可以在不同的状态之间进行切换。

  • 创建状态:实例化一个新的线程对象,还未启动。
java">new Thread();
  • 就绪状态:启动线程对象,调用 start() 方法,进入线程池等待抢占 CPU 资源。
java">new Thread().start();
  • 运行状态:线程对象获取到了 CPU 资源,在某个时间段内执行任务。

  • 阻塞状态:正在运行的线程暂停执行任务,释放所占用的 CPU 资源,并且在解除阻塞状态之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取 CPU 资源。

  • 终止状态:线程运行完毕或者因为异常导致线程中断。

在这里插入图片描述

java">package com.htl.test6;public class Test {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "-------"+i);}},"张三").start();new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(i + "================" + Thread.currentThread().getName());}},"李四").start();}
}

在这里插入图片描述

7、线程调度

7.1、线程休眠

线程暂停执行,从运行状态进入阻塞状态,让出 CPU 资源给其他线程

调用 sleep 方法实现线程休眠。

sleep(long millis)

java">package com.htl.test6;public class Test2 {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(i);if(i == 50){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}
}
java">package com.htl.test6;public class Test2 {public static void main(String[] args) {Thread thread = new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(i);}});try {thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}thread.start();for (int i = 0; i < 100; i++) {System.out.println("======main======");}}
}

上述代码表示,先等待 2 s ,然后主线程的循环和子线程的循环交替执行。

java">package com.htl.test6;public class Test2 {public static void main(String[] args) {Thread thread = new Thread(()->{for (int i = 0; i < 100; i++) {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i);}});thread.start();for (int i = 0; i < 100; i++) {System.out.println("======main======");}}
}

直接运行主线程的循环,再每隔 2s 执行一次子线程的循环。

java">package com.htl.test6;public class Test2 {public static void main(String[] args) {Thread thread = new Thread(()->{try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0; i < 100; i++) {System.out.println(i);}});//        try {
//            Thread.sleep(2000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }thread.start();for (int i = 0; i < 100; i++) {System.out.println("======main======");}}
}

直接运行主线程的循环,间隔 2s ,运行子线程的循环。

sleep 方法在哪调用,就是让当前线程休眠,无论通过哪种形式调用

线程做了两件事

1、启动子线程

2、循环

7.2、线程合并

将指定的某个线程加入到当前线程中,合并为一个线程,由原本的两个线程交替执行变成一个线程中的两个任务顺序执行。

join()

线程甲和线程乙,线程甲在执行到某个时间节点的时候,调用线程乙的 join 方法,从当前时间节点开始,CPU 资源全部交给线程乙,被它独占,线程甲就进入阻塞状态,直到乙执行完毕,甲进入就绪状态,继续等待争夺 CPU 资源。

java">package com.htl.test;public class Test {public static void main(String[] args) {JoinRunnbale joinRunnbale = new JoinRunnbale();Thread thread = new Thread(joinRunnbale);thread.start();for (int i = 0; i < 100; i++) {if(i == 20){try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(i + "+++++++++++++++main");}}
}/*** 任务类*/
class JoinRunnbale implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(i + "-----------------JoinRunnable");}}
}

join() 存在重载:join(long millis),某个时间节点调用了线程乙的 join(long millis),从这一时刻起,CPU 资源被乙独占,线程甲进入阻塞状态,持续时间为 millis,到点之后,无论乙是否执行完毕,它不再独占资源,而是恢复两个线程争夺资源。

java">package com.htl.test;public class Test {public static void main(String[] args) {JoinRunnbale joinRunnbale = new JoinRunnbale();Thread thread = new Thread(joinRunnbale);thread.start();for (int i = 0; i < 100; i++) {if(i == 20){try {thread.join(8000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(i + "+++++++++++++++main");}}
}/*** 任务类*/
class JoinRunnbale implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i + "-----------------JoinRunnable");}}
}

7.3、线程礼让

在某个时间点,让线程暂停抢占 CPU 资源的行为,将 CPU 资源让给其他线程使用。

只是某个时间点(瞬间)礼让一次,过了这个时间点,又开始参与争夺 CPU 资源。

yield()

java">package com.htl.test;public class Test2 {public static void main(String[] args) {YieldThread1 thread1 = new YieldThread1();thread1.setName("线程甲");YieldThread2 thread2 = new YieldThread2();thread2.setName("线程乙");thread1.start();thread2.start();}
}class YieldThread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i == 5){Thread.yield();}System.out.println(Thread.currentThread().getName() + "-----------" + i);}}
}class YieldThread2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "-----------" + i);}}
}

8、线程同步

8.1、线程同步的实现

异步和同步

有两个任务 A 和 B

异步指 A 和 B 同时进行,互不影响。

同步指 A 和 B 顺序执行,同一时间内只能执行一个任务,排队。

线程同步存在的意义是什么?

当多个线程同时访问一个共享数据的时候,可能会造成数据的不安全。

java">package com.htl.test2;public class Test {public static void main(String[] args) {Account account = new Account();Thread thread1 = new Thread(account,"张三");Thread thread2 = new Thread(account,"李四");thread1.start();thread2.start();}
}class Account implements Runnable{private static int num;@Overridepublic void run() {num++;try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");}
}

在这里插入图片描述

上述就是多个线程同时访问共享数据造成的数据不安全,如何解决?

只需要让多个线程排队(同步)即可。

最简单的同步方式就是使用 synchronized 关键字来实现。

相当于给资源上锁,要访问资源就必须拿到锁,如果锁被其他线程拿走了,那么当前线程就只能处于阻塞状态,等待锁的释放。

java">package com.htl.test2;public class Test {public static void main(String[] args) {Account account = new Account();Thread thread1 = new Thread(account,"张三");Thread thread2 = new Thread(account,"李四");thread1.start();thread2.start();}
}class Account implements Runnable{private static int num;@Overridepublic synchronized void run() {num++;try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");}
}

synchronized 可以修饰实例方法(非 static),也可以修饰静态方法,两者有区别。

synchronized 除了可以锁定方法之外,还可以锁定代码块。

java">package com.htl.test3;public class Test {public static void main(String[] args) {for (int i = 0; i < 5; i++) {Thread thread = new Thread(()->{
//                Test.test();Test test = new Test();test.test();});thread.start();}}public void test(){synchronized (this){System.out.println("start...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("end...");}}
}
java">package com.htl.test3;public class Test {public static void main(String[] args) {for (int i = 0; i < 5; i++) {Thread thread = new Thread(()->{Test.test();});thread.start();}}public static void test(){synchronized (Test.class){System.out.println("start...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("end...");}}
}

synchronized 关键字是用来实现线程同步的,可以锁方法,也可以锁代码块,无论锁谁,底层的原理都是一样的,就是看锁定的东西在内存中是一个还是多个,如果是一个,就会实现同步,5 个人 1 个厕所。

如果锁定的东西内存中是多个,就不会同步,5 个人 5 个厕所,同时进行。

9、线程安全的单例模式

一个类只能有一个实例化对象,由多个线程共享该对象资源。

java">package com.htl.test;public class SingletonDemo {private static SingletonDemo instance;public static SingletonDemo getInstance(){if(instance == null){instance = new SingletonDemo();}return instance;}private SingletonDemo(){}}
java">package com.htl.test;public class Test {public static void main(String[] args) {for (int i = 0; i < 100; i++) {System.out.println(SingletonDemo.getInstance());}}
}

线程情况下的单例模式,需要用 synchronized 关键字

java">package com.htl.test;public class SingletonDemo {private static SingletonDemo instance;public synchronized static SingletonDemo getInstance(){if(instance == null){instance = new SingletonDemo();}return instance;}private SingletonDemo(){System.out.println("创建了SingletonDemo对象");}}

9.1、字符串常量池

内存中的一块特定的区域,专门用来存放字符串对象,为了节省空间。

String str = new String(“abc”);

String str2 = new String(“abc”);

通过构造器的方式创建字符串对象,直接将创建好的对象在堆内存中存放,不考虑是否有重复,而使用字符串常量池,获取一个对象的时候,首先在字符串常量池中查找是否存在,如果存在,直接返回,否则再创建新的对象。

9.2、包装类常量池

跟字符串常量池类似,使用包装类对象的时候也可以使用常量池来节约资源。

除了通过构造器,其他创建对象的方式都会使用常量池。

包装类使用常量池有取值区间的限制,-128到127之间会使用常量池,一旦超过这个范围,不会使用常量池,而是使用堆。

10、死锁

在这里插入图片描述

几个人围着一张桌子吃饭,每人发一根筷子,要求每个人必须凑够两根筷子才能吃饭,每个人都希望得到别人的筷子,同时都不愿意把自己的筷子让出来,就形成一个互斥的状态,导致程序无法执行,卡住。

java">package com.htl.test2;public class Data {
}
java">package com.htl.test2;public class DeadLockRunnable implements Runnable {public int num;private static Data data1 = new Data();private static Data data2 = new Data();@Overridepublic void run() {if(num == 1){System.out.println(Thread.currentThread().getName() + "获取到了data1,等待获取data2");//获取data1synchronized (data1){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//获取data2synchronized (data2){System.out.println(Thread.currentThread().getName() + "用餐完毕");}}}if(num == 2){System.out.println(Thread.currentThread().getName() + "获取到了data2,等待获取data1");synchronized (data2){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (data1){System.out.println(Thread.currentThread().getName() + "用餐完毕");}}}}
}
java">package com.htl.test2;public class Test {public static void main(String[] args) {DeadLockRunnable runnable1 = new DeadLockRunnable();runnable1.num = 1;DeadLockRunnable runnable2 = new DeadLockRunnable();runnable2.num = 2;new Thread(runnable1,"张三").start();new Thread(runnable2,"李四").start();}
}

如何解决死锁问题,不要让线程同时执行,按照先后顺序来执行,不要构成互斥条件。

java">package com.htl.test2;public class Test {public static void main(String[] args) {DeadLockRunnable runnable1 = new DeadLockRunnable();runnable1.num = 1;DeadLockRunnable runnable2 = new DeadLockRunnable();runnable2.num = 2;new Thread(runnable1,"张三").start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(runnable2,"李四").start();}
}

11、重入锁

ReentrantLock 是对 synchronized 的升级,synchronized 是通过 JVM 实现,ReentrantLock 是通过 JDK 实现,让开发者可以用面向对象的思想来操作同步锁。

重入锁是指可以多次上锁,上锁和解锁方式和 synchronized 不同

synchronized 是自动上锁,自动解锁,只需要添加关键字,不需要开发者手动操作上锁和解锁。

ReentrantLock 需要开发者手动上锁、手动解锁。

java">package com.htl.test3;import java.util.concurrent.locks.ReentrantLock;public class Account implements Runnable {private static int num;private ReentrantLock reentrantLock = new ReentrantLock();@Overridepublic void run() {//上锁reentrantLock.lock();num++;System.out.println(Thread.currentThread().getName() + "是当前的第" + num + "位访客");//解锁reentrantLock.unlock();}
}
java">package com.htl.test3;public class Test {public static void main(String[] args) {Account account = new Account();new Thread(account).start();new Thread(account).start();}
}

使用 ReentrantLock 可以让锁定的粒度更细,不需要上锁的代码就可以分离出来。

ReentrantLock 除了在使用上更加灵活之外,还具备限时性的特点,是指在等待某个资源的时候可以设置一个等待时间,一旦超出这个时间还没有拿到锁,则中断线程,不会一直等下去,而 sychronized 就会一直等下去。

tryLock(long time,TimeUnit unit)

java">package com.htl.test3;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;public class TimeLock implements Runnable {private ReentrantLock reentrantLock = new ReentrantLock();@Overridepublic void run() {try {if(reentrantLock.tryLock(3, TimeUnit.SECONDS)){System.out.println(Thread.currentThread().getName() + "获取到了锁");Thread.sleep(5000);}else{System.out.println(Thread.currentThread().getName() + "没有拿到锁");}} catch (InterruptedException e) {e.printStackTrace();} finally {if(reentrantLock.isHeldByCurrentThread()){reentrantLock.unlock();}}}
}
java">package com.htl.test3;public class Test {public static void main(String[] args) {TimeLock timeLock = new TimeLock();new Thread(timeLock,"张三").start();new Thread(timeLock,"李四").start();}
}

http://www.ppmy.cn/ops/142967.html

相关文章

selenium自动化测试基础知识

目录 一、概念知识 (一)三大核心组件 (二)Selenium 自动化测试的工作原理 (三)Selenium 支持的操作 (四)Selenium 自动化测试的优点 (五)Selenium 自动化测试的缺点 (六)Selenium 自动化测试的应用场景 总结 二、实操例子 使用前提--安装步骤 注意事项 (一)浏览器的…

LruCache(本地cache)生产环境中遇到的问题及改进

问题&#xff1a;单机qps增加时请求摘要后端&#xff0c;耗时也会增加&#xff0c;因为超过了后端处理能力&#xff08;最大qps&#xff0c;存在任务堆积&#xff09;。 版本一 引入LruCache。为了避免数据失效&#xff0c;cache数据的时效性要小于摘要后端物料的更新时间&…

高并发-缓存预热

缓存预热的必要性 缓存预热是为了在系统面临高并发请求时&#xff0c;确保热点数据已经被加载到缓存中&#xff0c;从而提高系统响应速度和稳定性。通过缓存预热&#xff0c;可以避免因数据未命中而导致的数据库压力激增。 典型应用场景 电商系统&#xff1a;在秒杀活动期间&…

火山引擎声音复刻API-2.0

火山引擎声音复刻API-2.0使用解释 付费模式解释&#xff1a; 1.字符版本按照字符付费 2.并发版 是按 并发连接数 或 同时使用量 来收费的付费模式&#xff0c;常见于软件、API 服务、云服务等产品中。 上传音频文件demo代码 import base64 import os import requestshost …

基于单片机的智能化教室环境系统设计

本设计的智能化教室环境系统选用STC89C52做为核心控制器&#xff0c;通过红外传感装置检测教室人数&#xff0c;温度感应模块及光强感应模块检测当前教室内的温度和光照强度&#xff0c;以此来为单片机提供数据来控制灯光与风扇。控制模式设有自动和手动&#xff0c;可调节温度…

优化算法之遗传算法思想和应用实例

优化算法有很多&#xff0c;常用的有粒子群&#xff08;蚁群&#xff09;算法&#xff0c;模拟退火、遗传算法、免疫算法&#xff08;IA&#xff09;、差分进化算法&#xff08;Differential Evolution, DE&#xff09;、梯度下降等。这些算法思想有一些共性&#xff0c;大概是…

汽车租赁系统(数据库存储)

一、系统简介 本系统是一个汽车租赁管理系统&#xff0c;旨在为用户提供便捷的车辆租赁服务。系统包括用户注册、登录、查看车辆信息、租赁车辆、更换车辆、付款、还车等功能&#xff0c;并且管理员可以添加、删除、修改车辆信息以及查看营业额。 二、数据库设计 1. 需求分析…

RK3576 Android14,内存大于4G时UVC应用无法申请内存

最近有个项目需要将Linux虚拟成UVC摄像头&#xff0c;开发过程中遇到一个奇怪的事情&#xff0c;通过V4l2框架接口申请内存时&#xff0c;相同的板子&#xff0c;只是内存一个4G一个8G。4G的内存可以申请成功&#xff0c;8G就不行。提示“内存不足” 内存更大反而内存不足&…