【Java学习笔记】多线程与线程池

多线程与线程池

    • 一、多线程安全与应用
      • 1、程序、进程与线程的关系
      • 2、创建多线程的三种方式
        • (1)继承Thread类创建线程【不推荐】
        • (2)实现Runnable接口创建线程
        • (3)Callable接口创建线程
      • 3、线程的生命周期
      • 4、初识线程同步并使用sychronized实现线程同步
        • (1)初识线程同步
        • (2)sychronized的锁对象
      • 5、死锁产生的原因与解决方式
      • 6、初识线程池及其基本使用
        • (1)Runnable接口的弊端:
        • (2)线程复用的理念
          • ThreadPool线程池
          • JUC支持的线程池种类
    • 二、线程池
      • 1、线程池——治理线程的最大法宝
        • (1)什么是“池”
        • (2)线程池
        • (3)线程池适合应用的场合
      • 2、创建和停止线程池:
      • 3、线程池应该手动创建还是自动创建:
        • (1)四种线程池自动创建弊端
        • (2)正确的创建线程池的方法:
        • (3)线程池里的线程数量设定为多少比较合适:
        • **(4)常见线程池的特点:**
      • 4、拒绝策略与钩子方法
      • 5、线程池实现原理
      • 6、 **线程池五种状态:**
    • 三、ThreadLocal
      • 2、典型场景2
      • 3、ThreadLocal的两个作用
      • **4、ThreadLocal主要方法:**
      • 5、Thread、ThreadLocal以及ThreadLocalMap三者之间的关系
      • 6、ThreadLocal注意点
        • 1、内存泄漏
        • 2、Value的泄漏
        • 3、如何避免内存泄漏

一、多线程安全与应用

1、程序、进程与线程的关系

进程是程序运行的实例;
当一个程序执行进入内存运行时,即变成一个进程;
进程的资源是彼此隔离的,其他进程不允许访问
线程是进程内执行的“任务”;
线程是进程内的一个“基本任务”,每个线程都有自己的功能,是CPU分配与调度的基本单位;
一个进程内可以包含多个线程,反之一个线程只能隶属于某一个线程;
进程内至少拥有一个“线程”,这个线程叫“主线程”,主线程消亡则进程结束;
单核CPU通过时间片使所有线程达到一个并发执行的效果;
多核CPU达到并行执行的效果;

2、创建多线程的三种方式

(1)继承Thread类创建线程【不推荐】

package org.example;

import java.util.Random;

public class ThreadExample1 {
    class Runner extends Thread {
        @Override
        public void run() {
            Integer speed = new Random().nextInt(10);
            for (int i = 1; i <= 10; i++) {
                System.out.println("第" + i + "秒:" + this.getName() + "跑到:" + (i * speed));
            }
        }
    }

    public void start() {
        Runner runnerA = new Runner();
        runnerA.setName("参赛者A");
        Runner runnerB = new Runner();
        runnerB.setName("参赛者B");
        runnerA.start();
        runnerB.start();
    }
    // 主线程存在,进程存在,主线程消失,进程消失
    public static void main(String[] args) {
        new ThreadExample1().start();
    }
}

当调用thread.start()方法时,线程对象A和B中的run方法执行权移交给了操作系统,由操作系统的线程调度策略来决定先执行哪个方法

(2)实现Runnable接口创建线程

package org.example;

import java.util.Random;

public class ThreadExample2 {
    class Runner implements Runnable {

        @Override
        public void run() {
            Integer speed = new Random().nextInt(10);
            for (int i = 1; i <= 10; i++) {
                System.out.println("第" + i + "秒:" + Thread.currentThread().getName() + "跑到:" + (i * speed));
            }
        }
    }
    public void start() {
        Runner runnerA = new Runner();
        Thread threadA = new Thread(runnerA);
        threadA.setName("参赛者A");
        Runner runnerB = new Runner();
        Thread threadB = new Thread(runnerB);
        threadB.setName("参赛者B");
        threadA.start();
        threadB.start();
    }
    // 主线程存在,进程存在,主线程消失,进程消失
    public static void main(String[] args) {
        new ThreadExample2().start();
    }
}

(3)Callable接口创建线程

package org.example;

import java.util.Random;
import java.util.concurrent.*;

public class ThreadExample3 {
    // callable线程执行后将结果返回
    class Runner implements Callable<Integer> {
        public String name;

        @Override
        public Integer call() throws Exception {
            Integer speed = new Random().nextInt(10);
            Integer result = 0;
            for (int i = 1; i <= 10; i++) {
                System.out.println("第" + i + "秒:" + this.name + "跑到:" + (i * speed));
                result += i * speed;
            }
            return result;
        }
    }
    public void start() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Runner threadA = new Runner();
        threadA.name = "参赛者A";
        Runner threadB = new Runner();
        threadB.name = "参赛者B";
        Runner threadC = new Runner();
        threadC.name = "参赛者C";
        Future<Integer> submit1 = executorService.submit(threadA);
        Future<Integer> submit2 = executorService.submit(threadB);
        Future<Integer> submit3 = executorService.submit(threadC);
        executorService.shutdown();
        System.out.println(threadA.name + "累计跑了" + submit1.get() + "米");
        System.out.println(threadB.name + "累计跑了" + submit2.get() + "米");
        System.out.println(threadC.name + "累计跑了" + submit3.get() + "米");
    }
    // 主线程存在,进程存在,主线程消失,进程消失
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new ThreadExample3().start();
    }
}

3、线程的生命周期

Thread类定义的线程六种状态:

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

4、初识线程同步并使用sychronized实现线程同步

(1)初识线程同步

代码中的同步机制:

  • sychronized(同步锁)关键字的作用就是利用一个特定的对象设置一个锁(绣球),在多线程(游客)并发访问的时候,同时只允许一个线程(游客)可以获得这个锁,执行特定的代码(迎娶新娘)。执行后释放锁,继续由其他线程争抢。

(2)sychronized的锁对象

  • sychronized代码块:任意对象即可
  • sychronized方法:this当前对象【实际使用更多】
  • sychronized静态方法:该类的字节码对象
package org.example;

public class SyncSample {
    class Printer {
        Object lock = new Object();
        public void print() throws InterruptedException {
            // 代码块
            synchronized (lock) {
                Thread.sleep(1000);
                System.out.println("你");
                Thread.sleep(1000);
                System.out.println("好");
                Thread.sleep(1000);
                System.out.println("北");
                Thread.sleep(1000);
                System.out.println("京");
            }
        }

        // 方法【实际使用更广泛】
        public synchronized void print2() throws InterruptedException {
            // 代码块
                Thread.sleep(1000);
                System.out.println("你");
                Thread.sleep(1000);
                System.out.println("好");
                Thread.sleep(1000);
                System.out.println("北");
                Thread.sleep(1000);
                System.out.println("京");
        }

        // 方法【实际使用更广泛】
        public synchronized void print3() throws InterruptedException {
            // 代码块
            Thread.sleep(1000);
            System.out.println("你");
            Thread.sleep(1000);
            System.out.println("好");
            Thread.sleep(1000);
            System.out.println("北");
            Thread.sleep(1000);
            System.out.println("京");
        }
    }

    class PrintTask implements Runnable {
        private Printer printer;

        @Override
        public void run() {
            try {
                printer.print2();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public void start() {
        Printer printer = new Printer();
        for(int i = 0; i < 10; i++) {
            PrintTask printTask = new PrintTask();
            printTask.printer = printer;
            Thread thread = new Thread(printTask);
            thread.start();
        }
    }

    public static void main(String[] args) {
        new SyncSample().start();
    }
}

5、死锁产生的原因与解决方式

死锁是在多线程情况下最严重的问题,在多线程对公共资源(文件、数据)等进行操作时,彼此不释放自己的资源,而去试图操作其他线程的资源,而形成交叉引用,就会产生死锁。

解决死锁最根本的建议是:

  • 尽量减少公共资源的引用
  • 用完马上释放公共资源
  • 增加超时失败机制

6、初识线程池及其基本使用

并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥的问题,JDK1.5推出的java.util.concurrent(并发工具包)来解决这些问题。

(1)Runnable接口的弊端:

  • Runnable新建线程,性能差
  • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,严重时会占有过多系统资源导致死机或内存溢出

(2)线程复用的理念

ThreadPool线程池
  • 重用存在的线程,减少线程对象创建、消亡的开销
  • 线程总数可控,提高资源的利用率
  • 提供额外功能,定时执行、定期执行、监控等
JUC支持的线程池种类

在Java.util.concurrent中,提供了工具类Executors(调度器)对象来创建线程池,可创建的线程池有四种:

1、FixedThreadPool:定长线程池

public static void main(String[] args) {
        // 创建一个定长线程池
        // 定长线程池的特点是固定线程总数,空闲线程用于执行任务,如果线程都在使用,后续任务则处理等待状态
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for(int i = 0; i < 100; i++) {
            // 执行runnable对象
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "---" + index);
                }
            });
        }
        // 需要返回值,使用submit callable接口实现
        threadPool.shutdown();
    }

2、CachedThreadPool:可缓存线程池

// 创建可缓存线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();
        // 可缓存线程池的特点,无限大,如果线程中没有可用的线程则创建,有空闲则利用起来
        for(int i = 0; i < 100; i++) {
            // 执行runnable对象
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "---" + index);
                }
            });
        }
        // 需要返回值,使用submit callable接口实现
        threadPool.shutdown();

3、SingleThreadPool:单线程池

4、ScheduledThreadPool:调度线程池

public static void main(String[] args) {
        // 创建可调度线程池:定时执行任务
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
        threadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(new Date() + "延迟执行1秒,每三秒执行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }

二、线程池

1、线程池——治理线程的最大法宝

(1)什么是“池”

需要的时候可以快速使用,不用重新收集

(2)线程池

  • 如果不使用线程池,每个任务都新开一个线程处理
  • 加快响应速度
  • 合理利用CPU和内存
  • 统一管理

(3)线程池适合应用的场合

批量计算任务、服务器处理请求、excel解析
实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理

2、创建和停止线程池:

(1)线程池构造方法的参数

参数名类型含义
corePoolSizeint核心线程数
maxPoolSizeint最大线程数
keepAliveTimelong保持存活时间
workQueueBlockingQueue任务存储队列
threadFactorythreadFactory当线程池需要新的线程的时候,会使用ThreadFactory来创建新的线程
HandlerRejectExecutionHandler当线程池无法接受你所提交的任务的拒绝策略
  • corePoolSize指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务
  • 最大量maxPoolSize:在核心线程数的基础上,额外增加的线程数的上限
  • 添加线程规则
    最开始线程总数小于corePoolSize时,即使有线程处于空闲状态,新任务到来时,也会创建一个新线程;直到线程数等于corePoolSize,再来任务时,会把任务放入队列去等待;直到队列也满了,如果此时线程数小于maxPoolSize,则会再创建新线程来执行任务;如果队列已满,并且线程数已经扩大到等于maxPoolSize时,再尝试添加任务时,会被拒绝;

(2)增减线程的特点

  • 通过设置corePoolSize和maxPoolSize相同,就可以创建固定大小的线程池
  • 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它
  • 通过设置maxPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务
  • 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize

(3)线程存活时间:keepAliveTime
如果线程池当前的线程数多于corePoolSize,那么如果多于的线程空闲时间超过keepAliveTime,它们就会被终止

(4)创建线程:ThreadFactory
默认使用Executors.defaultThreadFactory()
创建出来的线程都在同一个线程组
如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级是否是守护线程等
通常情况下,使用默认的线程工厂基本上就可以满足绝大多数的需要

(5)工作队列:
有3种最常见的队列类型

  1. 直接交换:SynchronousQueue
  2. 无界队列:LinkedBlockingQueue
  3. 有界队列:ArrayBlockingQueue

3、线程池应该手动创建还是自动创建:

(1)四种线程池自动创建弊端

  • newFixedThreadPool:容易造成大量内存占用,可能会导致OOM
  • newSingleThreadExecutor:当请求堆积的时候,可能会占用大量的内存
  • CachedThreadPool可缓存线程池
    特点:具有自动回收多余线程的功能
    弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM
  • newScheduledThreadPool

(2)正确的创建线程池的方法:

  • 根据不同的业务场景,设置线程池参数;
  • 比如:内存有多大,给线程取什么名字等等

(3)线程池里的线程数量设定为多少比较合适:

  • CPU密集型(加密、计算hash等):最佳线程数为CPU核心数的1-2倍

  • 耗时IO型(读写数据库、文件、网络读写等):最佳线程数一般会大于CPU核心数很多倍

     参考Brain Goetz推荐的计算方法:
     线程数 = CPU核心数*(1 + 平均等待时间/平均工作时间)
    

(4)常见线程池的特点:

  • FixedThreadPool

    核心线程池和最大线程数相同,队列满后,无法再增加线程

  • CachedThreadPool

    具有自动回收多余线程的功能,队列没有容量,线程数可以很多

  • ScheduledThreadPool

    支持定时及周期性任务执行的线程池

  • SingleThreadExecutor

    只会用唯一的工作线程来执行任务(用到的场景并不多)

(5)停止线程池的正确方法:
1、shutdown
运行这个方法之后并不一定会停止,事实上,这个方法仅仅是初始化整个关闭过程。运行这个方法,会把正在执行的和队列中等待的都执行完毕之后再关闭,但是不会再增加任务。
2、isShutdown
3、isTerminated:线程都结束了返回true
4、awaitTermination
5、shutdownNow

4、拒绝策略与钩子方法

任务太多,怎么拒绝:

  • 拒绝时机
    当Executor关闭时,提交新任务会被拒绝,以及当Executor对最大线程和工作队列容量使用有限边界并且已经饱和时

4种拒绝策略:

  • AbortPlicy:抛出异常
  • DiscardPolicy:默默丢弃
  • DiscardOlderPolicy:丢弃掉老的
  • CallerRunsPolicy

钩子方法,给线程池加点料:

  • 每个执行任务前后
  • 日志、统计

5、线程池实现原理

(1)线程池组成部分:

  • 线程池管理器
  • 工作线程
  • 任务队列
  • 任务接口(Task)

(2)Executor家族:

  • 线程池、ThreadPoolExecutor、ExecutorService、Executor、Executors等这么多和线程池先关的类,都是什么关系
  • Executor:是一个接口,里面只有一个方法execute
  • ExecutorService:继承Executor,增加了一些新的方法,拥有初步管理线程池的方法
  • Executors:工具类,帮我们快速创建线程池用的

img
(3)线程池实现任务复用的原理:

  • 相同线程执行不同任务
  • runWorker 方法(进入到源码)

6、 线程池五种状态:

https://img-blog.csdnimg.cn/img_convert/1fb21acd6c08160bfb0d353aec82c63e.jpeg

(1)execute方法:执行任务
(2)使用线程池的注意点:

  • 手动创建线程池
  • 线程数合理
  • 考虑多个线程池间的影响

三、ThreadLocal

2、典型场景2

  • 用ThreadLocal保存一些业务内容(用户权限信息、从用户系统获取到的用户名、userId等)
  • 这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的
  • 在线程生命周期内,都通过这个静态ThreadLocal实例的get()方法取的自己set过的那个对象,避免了将这个对象作为参数传递的麻烦
  • 强调同一个请求(同一个线程内)不同方法间的共享
  • 不需要重写initialValue()方法,但是必须手动调用set()方法
public class ThreadLocalSample2 {
    public static void main(String[] args) {
        Service1 service1 = new Service1();
        service1.process();
        new Service2().process();
    }
}

class Service1 {
    public void process() {
        User user = new User("三三");
        UserHolderContext.holder.set(user);
    }
}

class Service2 {
    public void process() {
        UserHolderContext.holder.get();
    }
}

class UserHolderContext {
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
    String name;

    public User(String name) {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

3、ThreadLocal的两个作用

1、让某个需要用到的对象在线程间隔离(每个线程都有自己独立的对象)
2、在任何方法中都可以轻松获取到该对象

场景一:initialValue
在ThreadLocal第一次get的时候把对象初始化出来,对象的初始化时机可以由我们控制

场景二:set
ThreadLocal里面对象的生成时机不由我们控制,例如拦截器生成的用户信息,用ThreadLocal.set直接放到ThreadLocal中,以便后续使用

使用ThreadLocal带来的好处:

  • 达到线程安全

  • 不需要加锁,提高执行效率

  • 更高效地利用内存、节省开销

    相比于每个任务都新建一个SimpleDateFormat,显然用ThreadLocal可以节省内存和开销

  • 免去传参的繁琐

    –不需要每次都传同样的参数

    –ThreadLocal使得代码耦合度更低、更优雅

4、ThreadLocal主要方法:

(1)initialValue() 初始化

  • 该方法会返回当前线程对应的“初始值”,这是一个延迟加载的方法,只有在调用get的时候,才会触发
  • 当线程第一次使用get方法访问变量时,将调用此方法
  • 每个线程最多调用一次此方法,但如果已经调用了remove()后,再调用get(),则可以再次调用此方法
  • 如果不重写本方法,这个方法会返回null。一般使用匿名内部类的方法来重写initialValue()方法

(2)void set(T t):为这个线程设置一个新值

(3)T get():得到这个线程对应的Value,如果是首次调用get(),则会调用initialize来得到这个值

(4)void remove():删除对应这个线程的值

5、Thread、ThreadLocal以及ThreadLocalMap三者之间的关系

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

6、ThreadLocal注意点

1、内存泄漏

某个对象不再使用,但是占有的内存无法回收

2、Value的泄漏

(1)ThreadLocalMap的每个Entry都是一个对key的弱引用,同时,每个Entry都包含一个value的强引用
(2)正常情况下,当一个线程终止,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了
(3)但是,如果线程不终止(需要保持很久),那么key对应的value就不能被回收,这种情况下就会发生内存泄漏,就可能出现OOM

3、如何避免内存泄漏

调用remove方法,就会删除对应的Entry对象,可以避免内存泄漏,所以使用ThreadLocal之后,应该调用remove()方法

【注:学习来源——慕课网】

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

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

相关文章

基础入门 HTTP数据包Postman构造请求方法请求头修改状态码判断

文章目录数据-方法&头部&状态码请求requestResponse状态码案例-文件探针&登录爆破工具-Postman自构造使用数据-方法&头部&状态码 请求request 1、常规请求-Get 2、用户登录-Post •get&#xff1a;向特定资源发出请求&#xff08;请求指定页面信息&#x…

为什么这么NB?huatuo革命Unity热更新

最近huatuo(华佗)热更新解决方案火爆了unity开发圈,起初我觉得热更新嘛&#xff0c;不就是内置一个脚本解释器脚本语言开发&#xff0c;如xLua, ILRuntime, puerts。Huatuo又能玩出什么花样&#xff0c;凭什么会这么NB&#xff0c;引起了那么多程序员的关注与称赞呢&#xff1f…

单片机——IIC协议与24C02

1、基础知识 1.1、IIC串行总线的组成及工作原理 I2C总线只有两根双向信号线。一根是数据线SDA&#xff0c;另一根是时钟线SCL。 1.2、I2C总线的数据传输 I2C总线进行数据传送时&#xff0c;时钟信号为高电平期间&#xff0c;数据线上的数据必须保持稳定&#xff0c;只有在时钟…

Linux实操之进程管理

文章目录一、基本介绍二、显示系统执行的进程基本介绍三、ps详解四、终止进程kill和killall介绍:●基本语法常用选项五、查看进程树pstree基本语法常用选项一、基本介绍 1&#xff0e;在LINUX中&#xff0c;每个执行的程序都称为一个进程。每一个进程都分配一个ID号(pid,进程号…

【SCL】实现简单算法--冒泡排序

使用SCL语言实现一个冒泡排序的简单算法 文章目录 目录 文章目录 前言 二、实现排序 1.读取存储器地址&#xff08;PEEK&#xff09;指令 2.编写程序 总结 前言 本文我们来一起使用SCL来实现一个简单的算法——冒泡排序&#xff1b;它可以对少量数据进行从小到大或从大到小排序…

【Linux】GDB的安装与使用

安装安装gdb的具体步骤如下&#xff1a;1、查看当前gdb安装情况rpm -qa | grep gdb如果有&#xff0c;则可以先删除&#xff1a;rpm -e --nodeps 文件名如果没有&#xff0c;则进行下一步。2、下载gdb源码包或者直接apt安装。apt命令安装&#xff1a;sudo apt install gdb源码包…

Qt之QPainter绘制多个矩形/圆形(含源码+注释)

一、绘制示例图 下图绘制的是矩形对象&#xff0c;但是将绘制矩形函数&#xff08;drawRect&#xff09;更改为绘制圆形&#xff08;drawEllipse&#xff09;即可绘制圆形。 二、思路解释 绘制矩形需要自然要获取矩形数据&#xff0c;因此通过鼠标事件获取每个矩形的rect数…

一个完整的渗透学习路线是怎样的?如何成为安全渗透工程师?

前言 1/我是如何学习黑客和渗透&#xff1f; 我是如何学习黑客和渗透测试的&#xff0c;在这里&#xff0c;我就把我的学习路线写一下&#xff0c;让新手和小白们不再迷茫&#xff0c;少走弯路&#xff0c;拒绝时间上的浪费&#xff01; 2/学习常见渗透工具的使用 注意&…

SpringBoot集成 SpringSecurity安全框架

文章目录一、CSRF跨站请求伪造攻击二、项目准备三、认识 SpringSecurity3.1 认证&#x1f380;①直接认证&#x1f380;②使用数据库认证3.2 授权&#x1f361;①基于角色授权&#x1f361;②基于权限的授权&#x1f361;③使用注解判断权限3.3 "记住我"3.4 登录和注…

【JavaEE】如何将JavaWeb项目部署到Linux云服务器?

写在前面 大家好&#xff0c;我是黄小黄。不久前&#xff0c;我们基于 servlet 和 jdbc 完善了博客系统。本文将以该系统为例&#xff0c;演示如何将博客系统部署到 Linux 云服务器。 博客系统传送门&#xff1a; 【JavaEE】前后端分离实现博客系统&#xff08;页面构建&#…

arcpy基础篇(3)-处理空间数据

ArcPy的数据访问模块arcpy.da&#xff0c;可以控制会话、编辑操作、游标、表或要素类与NumPy数组之间相互转换的函数以及对版本化和复本工作流的支持。 1.使用游标访问数据 游标是一个数据库术语&#xff0c;它主要用于访问表格中的每一行记录或者向表中插入新的记录。在ArcG…

【数据结构】Java实现单链表

目录 1. ArrayList的缺陷 2. 链表 2.1 链表的概念及结构 2.2 接口的实现 3. 动手实现单链表 3.1 重写SeqList接口方法 3.2 在当前链表头部添加节点&#xff08;头插&#xff09; 3.3 在 第index位置添加节点&#xff08;任意位置&#xff09; 3.4 在当前链表尾部添加…

Python|蓝桥杯进阶第四卷——图论

欢迎交流学习~~ 专栏&#xff1a; 蓝桥杯Python组刷题日寄 蓝桥杯进阶系列&#xff1a; &#x1f3c6; Python | 蓝桥杯进阶第一卷——字符串 &#x1f50e; Python | 蓝桥杯进阶第二卷——贪心 &#x1f49d; Python | 蓝桥杯进阶第三卷——动态规划 ✈️ Python | 蓝桥杯进阶…

四、快速上手 ODM 操作 Mongodb

文章目录一、ODM 的选择和安装二、MongoEngine 模型介绍三、文档的嵌套模型四、使用 ODM 查询数据4.1 查询一个文档4.2 条件查询4.3 统计、排序和分页五、使用 ODM 新增数据六、使用 ODM 修改和删除数据一、ODM 的选择和安装 MongoEngine&#xff1a; 使用最为广泛的 ODM。htt…

【C++】命名空间

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、命名空间产生的背景二、命名空…

基础篇:07-Nacos注册中心

1.Nacos安装部署 1.1 下载安装 nacos官网提供了安装部署教程&#xff0c;其下载链接指向github官网&#xff0c;选择合适版本即可。如访问受阻可直接使用以下最新稳定版压缩包&#xff1a;&#x1f4ce;nacos-server-2.1.0.zip&#xff0c;后续我们也可能会更改为其他版本做更…

图论学习(五)

极图 l部图的概念与特征 定义&#xff1a;若简单图G的点集V有一个划分&#xff1a; 且所有的Vi非空&#xff0c;Vi内的点均不邻接&#xff0c;设G是一个l部图。 如果l2&#xff0c;则G就是偶图。n阶无环图必是n部图。若l1<l2≤n&#xff0c;则任意的l1部图也是l2部图。…

【毕业设计】基于SpringBoot+Vue论坛管理系统【源码(完整源码请私聊)+论文+演示视频+包运行成功】

您好&#xff0c;我是码农飞哥&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通 &#x1f601; 2. 毕业设计专栏&…

JavaScript学习笔记(7.0)

<!--* Author: RealRoad1083425287qq.com* Date: 2023-03-13 14:50:18* LastEditors: Mei* LastEditTime: 2023-03-13 15:08:54* FilePath: \vscode\鼠标跟随.html* Description: * * Copyright (c) 2023 by ${git_name_email}, All Rights Reserved. --> <!DOCTYPE …

Vue3(递归组件) + 原生Table 实现树结构复杂表格

一、递归组件 什么是递归&#xff0c;Javascript中经常能接触到递归函数。也就是函数自己调用自己。那对于组件来说也是一样的逻辑。平时工作中见得最多应该就是菜单组件&#xff0c;大部分系统里面的都是递归组件。文章中我做了按需引入的配置&#xff0c;所以看不到我引用组…