JUC总结系列篇 (二) : 对线程的理解和使用总结

news2024/12/26 23:43:44

文章内容:

一.为什么需要多线程
二.线程的创建
三.线程的方法sleep(),run(),wait(),yeid(),join(),interrupt()等方法归纳总结
四.线程的状态及其转换
五.线程的交替执行案例
六.多个线程依次执行案例
七.多线程并发带来的线程安全问题

一.为什么需要多线程?

  • 相比与以前的单核时代,目前的计算机都是多CPU的,多线程可以更好的利用CPU,提高CPU的利用率
  • 在IO密集时,多线程可以提高在线程IO阻塞时CPU的利用率(一个线程被阻塞不占用CPU的时候,另一个线程就可以利用空闲的CPU)
  • 多线程的并发可以提高程序的运行速度(一个任务一个任务依次执行肯定是比多个任务同时进行慢的)
  • 当然多线程的并发操作也会伴随着线程安全问题(另写文章总结

二.线程的创建

从本质上说创建线程只有一种方式,就是构造一个 Thread 类,这是创建线程的唯一方式。
而要想实现线程执行的内容,却有三种方式:

  • 通过实现 Runnable 接口的方式重写 run() 方法
  • 继承 Thread 类重写 run() 方法的方式
  • 实现Callable并重写call()方法,利用Callable构造一个FutureTask<>,最后把FutureTask当作参数来构造Thread对象,call()方法的内容就是线程执行的内容 (call方法有返回值,而run方法无返回值)

实现 Runnable 接口:

    public static class MyRunable implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "    通过实现Runable的方式");
        }
    }
        new Thread(new MyRunable()).start();
        new Thread(new MyRunable()).run();

在这里插入图片描述
从这里也可以看出run方式和start方式的执行过程是不一致的,直接run是在当前run的线程中执行,而start是重新开辟一条新线程执行(后面会总结)

继承 Thread 类重写 run() 方法的方式:

    public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "    通过继承Thread的方式");
        }
    }
        new MyThread().start();
        new MyThread().run();

在这里插入图片描述

Tips:

实现 Runnable 接口比继承 Thread 类实现线程要好在哪里:

  • 从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明,Runnable传入给Thread执行就好
  • 在某些情况下可以提高性能,使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程。如果使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。
    对于线程池的使用总结,可以点击JUC总结系列篇 (一):对Java线程池的理解和使用总结
  • Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,限制了代码未来的可拓展性。

实现Callable并重写call()方法

    public static class MyCallable implements Callable<String>{

        @Override
        public String call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "    通过实现Callable的方式");
            return "Call方法执行完毕返回值";
        }
    }

第一种使用:无需获取返回值

          new Thread(new FutureTask<>(new MyCallable())).start();
          new Thread(new FutureTask<>(new MyCallable())).run();

在这里插入图片描述
第二种使用:获取线程的返回值,通过Future的get方法

        FutureTask<String> task = new FutureTask<>(new MyCallable());
        Thread t1 = new Thread(task);
        t1.start();
        try {
            t1.join();
            System.out.println(task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

在这里插入图片描述
注意上面必须使用join方法,否则main函数可能先执行完了,就获取不到返回值了。
比如:

        FutureTask<String> task = new FutureTask<>(new MyCallable());
        Thread t1 = new Thread(task);
        t1.start();
        if(!t1.isAlive()){
            try {
                System.out.println(task.get());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }else{
            System.out.println("t1线程还在执行中,拿不到返回值");
        }

在这里插入图片描述
关于join方法,后面会讲解
#

三. 线程的sleep(),run(),wait(),yeid(),join(),interrupt()等方法归纳总结

sleep():

    public static native void sleep(long millis) throws InterruptedException;
  • 静态方法,参数是毫秒,需要指定等待的时间,可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态(注意是在哪个线程里面调用,就使哪个线程进入休眠阻塞)
  • 相当于让线程睡眠,交出CPU,让CPU去执行其他的任务,但是要注意,sleep方法是不会释放锁的(does not lose owner ship of any monitors),抱着锁睡觉,也就是说如果有synchronized同步块,线程获取到锁调用sleep后,其他线程仍然不能访问共享数据(因为拿不到锁)
  • 执行该方法和线程优先级无关,可以让其他同优先级或者高/低优先级的线程得到执行的机会
    使用:
    public static class MyRunable implements Runnable{

        @Override
        public void run() {

            System.out.println(Thread.currentThread().getName() + "    线程进入休眠" + System.currentTimeMillis());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "    线程结束休眠" + System.currentTimeMillis());
        }
    }

new Thread(new MyRunable()).start();

在这里插入图片描述

  • Tips:
    操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源比较多,也就是说CPU优先执行优先级高的线程的概率高,但是不能保证优先级高就一定会先被执行。
    优先级一共分为1~10个等级,数字越大优先级越高,默认5,超出范围则抛出java.lang.IllegalArgumentException异常。

    可以使用 getPriority获取线程的优先级setPriority设置线程的优先级
    举个栗子:
    public static class MyHighPriorityRunable implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "    高优先级输出");
        }
    }
    public static class MyLowPriorityRunable implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "    低优先级输出");
        }
    }
    public static void main(String[] args) {
        Thread hPThread = new Thread(new MyHighPriorityRunable());
        hPThread.setPriority(10);
        Thread lPThread = new Thread(new MyLowPriorityRunable());
        lPThread.setPriority(1);
        lPThread.start();
        hPThread.start();
    }

在这里插入图片描述
改变优先级依旧随机输出:

        Thread hPThread = new Thread(new MyHighPriorityRunable());
        hPThread.setPriority(1);
        Thread lPThread = new Thread(new MyLowPriorityRunable());
        lPThread.setPriority(10);
        lPThread.start();
        hPThread.start();

在这里插入图片描述

根据例子可以看出,设置优先级也不能保证优先级高的先输出

执行线程时调用run()和start()辨析:

        new Thread(new MyRunable()).start();
        new Thread(new MyRunable()).run();

为什么不能直接调用 run() 方法,而需要调用 start() 方法?

  • 调用 start() 方法可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行,还是在原来的线程里执行run方法
  • 直接执行 run() 方法,会把 run() 方法当成一个 当前线程下的普通方法去执行,并不会开启新线程去执行它,所以这并不是多线程工作。
  • start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
    public static class MyRunable implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "  run方法里开启的线程");
                }
            }).run();
        }
    }

new Thread(new MyRunable()).start();

在这里插入图片描述
若执行线程的方法改成

new Thread(new MyRunable()).run();

在这里插入图片描述
以上就是t.run()和t.start()的区别

yield():

public static native void yield();
  • 静态方法
  • 调用yield方法会让当前线程交出CPU权限`,让CPU去执行其他的线程,它跟sleep方法类似,同样不会释放锁,但是yield不能控制具体的交出CPU的时间,
  • 注意,调用yield方法并不会让线程进入到阻塞状态,而是让线程重新回到就绪状态,等待重新获得CPU执行时间的机会,这一点和sleep的不一样的。
  • yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

很少使用,因此不写案例。

wait():

  • 这个其实是Object的方法
  • 一般需要和notify()以及notifyAll()一起使用,用于协调多个线程对共享数据的存取,必须synchronized语句块内使用,持有拥有对象的锁
  • 该方法会释放对象的“锁标志”。当调用后会使当前线程暂停执行(进入等待阻塞状态),并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。

notify()/notifyall():

  • notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
  • notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

案例:利用synchronized + wait + notify使线程交替进行

先从字母开始,然后字母,数字线程交替执行

        Object o = new Object();
        char[] chs1 = "12345".toCharArray();
        char[] chs2 = "ABCDEF".toCharArray();
        int len1 = chs1.length;
        int len2 = chs2.length;
        CountDownLatch latch = new CountDownLatch(1);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (o) {
                    int i = 0;
                    for (char num : chs1) {
                        i++;
                        System.out.print(num);
                        try {
                            if(i < len2){
                                //数字线程后执行,需要i<len2,
                                // 因为当i == len2时,字母线程已经执行完了,不能再唤醒数字线程,会造成线程阻塞
                                o.notify();
                                o.wait();
                            }else{
                                o.notify();
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }

                    }
                }
            }
        });
        Thread t2 = new Thread(()-> {
                synchronized (o) {
                    int i = 0;
                    for (char num : chs2) {
                        try {
                            i++;
                            System.out.print(num);
                            latch.countDown();
                            if(i <= len1){
                                //字母线程先执行,所以需要 i<= len1,
                                // 因为i==len1的时候,数字线程还没有打印完,还可以唤醒字母线程
                                o.notify();
                                o.wait();
                            }else{
                                o.notify();
                            }
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }

        });
        t1.start();
        t2.start();

在这里插入图片描述
该案例用到了synchronized和CountDownLatch保证同步和按顺序执行,属于并发编程的同步解决方案,后面会另写文章总结
读者看不懂没关系,主要记住多线程交替工作的思路抢锁–>执行–>让锁就可以了,synchronized就是一把 “锁”

join()方法:

  • 方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行。
  • 如在main主线程当中,调用了thread.join()方法,则main方法会等待thread线程执行完毕或者等待一定的时间,如果调用的是无参join方法,则等待执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。
  • join(mills): 传入参数>0则等待线程多少毫秒后并发执行,传入参数若是 <0 则报错,0则等待无限长,直到线程执行完毕,(在A线程里调用B.join(10),那A线程就要等B线程10毫秒后俩者再并发执行)

经典案例:实现十个线程依次计算,最后打印出总和,第1个线程计算1+2+…+10,第2个线程计算11+12+…+20,以此类推

public class CalculateThread extends Thread {

    private int stratNum;
    public static int sum;//10个线程和

    public CalculateThread(int startNum) {
        this.stratNum = startNum;
    }
    public static synchronized void add(int value) {
        sum = sum + value;
    }

    public void run() {
        int sum = 0;

        for (int i = 0; i < 10; i++) {
            sum = sum + stratNum + i;
        }
        System.out.println(Thread.currentThread().getName()+" 计算后和为"+sum);

        add(sum);
    }

    public static void main(String[] args) throws Exception {
        Thread[] threadList = new Thread[10];//线程数组
        for (int i = 0; i < 10; i++) {
            threadList[i] = new CalculateThread(10 * i + 1);
            threadList[i].start();
            threadList[i].join();//每个线程1开始,就排好join 执行完后下一个线程才能执行
        }
        System.out.println("10个线程计算结果相加后和为: " + sum);

    }

}

在这里插入图片描述

Runable和Thread中的run()方法(区别去上面提到的执行时调用的run方法):

  • run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,线程等待获得CPU执行时间,一旦获得了CPU执行时间,便进入run方法区执行具体的任务

interrupt():

  • 顾名思义,就是中断的意思,单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就是说它可以用来中断一个正处于阻塞状态的线程。
  • 根据描述,线程在调用wait和sleep后也是处于阻塞状态的,因此是可以响应中断
  • 注意,调用run方法线程不会处于阻塞状态,所以在run方法中写while(true) 也不会响应中断,不过可以搭配isInterrupted()中断线程
  • Thread.interrupted()是一个静态方法,返回中断值,默认是false,被中断后是true
    它会返回调用线程(而不是被调用线程)的中断标志位,返回后重置中断标志位(又置为false)。
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

案例

  • Thread.interrupted()
             while (true){
                 if(isInterrupted()){
                     System.out.println(Thread.interrupted());
                     System.out.println(Thread.interrupted());
                     return;
                 }
             }
        MyThread t1 = new MyThread();
        t1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.interrupt();

在这里插入图片描述
可知在调用Thread.interrupted()后,中断标志位又变为false了

  • 利用isInterrupted() + interrupt()来中断 run方法中的while(true)
    public static class MyThread extends Thread{
        @Override
        public void run() {
            while(true){
                if(this.isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "  " + Thread.interrupted());
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "    通过继承Thread的方式    " +  Thread.interrupted());
            }

        }
    }

        MyThread t1 = new MyThread();
        t1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.interrupt();

在这里插入图片描述

  • 中断sleep()
    public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "    通过继承Thread的方式    " +  Thread.interrupted());
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "    通过继承Thread的方式    " +  Thread.interrupted());
        }
    }
        MyThread t1 = new MyThread();
        t1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.interrupt();

在这里插入图片描述

  • 中断wait()
    public static class MyThread extends Thread{
        @Override
        public void run() {
            Object o = new Object();
            System.out.println(Thread.currentThread().getName() + "    通过继承Thread的方式    " +  Thread.interrupted());
            try {
                synchronized (o){
                    o.wait();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "    通过继承Thread的方式    " +  Thread.interrupted());
        }
    }

        MyThread t1 = new MyThread();
        t1.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.interrupt();

在这里插入图片描述

wait和sleep的区别和联系

  • sleep() 不会释放锁,wait() 会释放锁。
  • 都可以暂停线程的执行
  • wait()通常用来进行线程间的交互/通信,通过Notify() 或 NotifyAll()唤醒,而sleep()用来暂停执行
  • wait() 方法被调用后,不会自动苏醒。需要别的线程来调用同一个锁对象上的 notify/notifyAll 方法。sleep() 方法调用后,经过设置后的时间后会苏醒,也可以通过调用 wait(long timeout),超时后也能自动苏醒。
    上述中断wait案例中:若把wait设置个时间,比sleep少,那么线程就赶在被中断前苏醒,就不会被中断了
o.wait(500);
Thread.sleep(2000);

四.线程的状态以及转换

  • NEW:
    初始/新建状态,线程被创建了,但还没有执行start()方法;
    Thread state for a thread which has not yet started.
  • RUNNABLE(就绪状态和运行状态统称):
    运行状态,这里对应操作系统中的就绪和运行状态,统称为运行中
    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.(在JVM是运行状态,但在操作系统的其他程序可能是等待中(就绪))
  • BLOCKED:
    阻塞状态,表示线程因为锁被阻塞了
    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 Object.wait.
    在等待锁去进入一个synchronized修饰的代码块或方法时,以及调用了wait方法(把锁给让出去)后再等待锁去进入一个synchronized修饰的代码块或方法时会处于这个状态
  • WAITING
    等待状态,线程进入等待状态,需要等待其他线程通过 通知 中断 来唤醒
    Thread state for a waiting thread. A thread is in the waiting state due to calling one of the following methods:
    Object.wait with no timeout
    Thread.join with no timeout
    LockSupport.park
    A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.
    以下时候线程会处于这个状态:
    1.Object.wait(),不传入参数
    2.Thread.join(),不传入参数
    注意上面是对象调用,因为wait和join都不是静态方法
    3.LockSupport.park()
    4.举例子,调用wait后等待notify/notifyall唤醒时,以及等待join进来的线程执行完后等
  • TIME_WAITING:
    超时等待状态,不同于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:
    Thread.sleep
    Object.wait with timeout
    Thread.join with timeout
    LockSupport.parkNanos
    LockSupport.parkUntil
    以下时候线程会处于这个状态:
    1.Object.wait(mills),传入参数
    2.Thread.join(mills),传入参数
    注意上面是对象调用,因为wait和join都不是静态方法
    3.LockSupport.parkNanos
    4.LockSupport.parkUntil
    5.Thread.sleep()
  • TERMINATED:
    终止状态,表示线程已经执行完毕
    Thread state for a terminated thread. The thread has completed execution.
    由于每个人的翻译不一样,看的教材也不一样,所以对线程状态的描述也不太一致,其他博客可能也有不一样的称呼。
  • 状态转移图:
    在这里插入图片描述

五.线程交替执行案例----翻上去

六.多线程依次执行案例 ----翻上去

七.多线程并发带来的安全性问题

并发安全性问题的根源:

  • 原子性 : 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。(经典转账问题,一个扣钱一个加钱,必须保证原子性) synchronized可以保证代码片段的原子性
  • 可见性 : 当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。volatile和synchronized关键字都可以保证共享变量的可见性
  • 有序性 : 代码在执行的过程中的先后顺序,JAVA在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序,volatile关键字可以禁止指令进行重排序优化。

在计算机内部,CPU,内存,I/O设备的速度有着极大的差异,CPU巨快,为了合理充分利用CPU的高性能,平衡这三者的速度差异,计算机体系结构,操作系统,编译程序 都做出了贡献

  • 通过设置缓存(计算机体系结构):将一些关键的,常用的数据放置在CPU缓存中,以提高数据的获取速度,均衡了CPU的快速而磁盘读取缓慢的速度差异。但设置缓存也导致了 可见性 的问题。

  • 操作系统增加了进程,线程,以分时复用CPU,进而充分利用CPU,均衡CPU和I/O设备的速度差异,在进行线程切换的时候容易导致了原子性问题

  • 编译程序优化指令执行次序,使得缓存能够得到更加合理的利用(局部性原理),但这也带来有序性的问题,代码的实际执行顺序与代码书写顺序并不一致

解决多线程并发带来的安全性问题的本质其实就是解决原子性,可见性,有序性 带来的问题
由于这是属于多线程并发的内容,因此会另起文章总结。
如volatile,以及锁机制Synchronized和Lock等的原理和使用届时都会归纳总结。

参考与感谢

由于这是很久之前学习的知识做的本地笔记,现今我重新实践并且做归纳总结,因此不知道具体参考了哪些教程和博客。所以我在此感谢所有参考到的教材作者以及博客作者,创造不易。也感谢在这个计数开源的时代,让我能找到很多优秀教程进行学习,并且输出。

强烈推荐这篇文章可以让初学者进行归纳学习,毕竟是站在巨人的肩膀上总结的精华。喜欢的可以点赞加关注

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux项目自动化构建工具-make/Makefile

一、前言 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力。一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件…

前端入门笔记 04 —— Web(html CSS)布局

响应式布局 屏幕尺寸变化&#xff0c;需要响应式网页设计RWD web页面适应不同屏幕宽度因素 液态站点&#xff0c;拉伸充满浏览器窗口 小屏幕挤成一团&#xff0c;大屏幕空白间隙过大固定宽度 像素为单位固定尺寸 小屏幕滚动&#xff0c;大屏幕空白 实现 设置meta标签媒体查…

数据结构入门5-1(数和二叉树)

目录 注 树和二叉树的定义 树的定义 树的基本术语 二叉树的定义 树和二叉树的抽象数据类型定义 二叉树的性质和存储结构 二叉树的性质 二叉树的存储结构 1. 顺序存储结构 2. 链式存储结构 遍历二叉树和线索二叉树 遍历二叉树&#xff08;traversing binary tree&a…

加密与安全

目录 一、编码算法 1.1、ASCII 1.1.1、ASCII简介 1.1.2、ASCII产生原因 1.1.3、表达方式 1.1.4、标准表 1.1.5、大小规则 1.2、Unicode 1.2.1简介 1.2.2编码和实现 1.3、汉字编码 1.3.1、GB2312-80 标准 1.3.2、GBK 编码标准 1.3.3、GB18030编码标准 1.4、URL编…

【Node】中Express框架连接Mysql实现用户注册接口

Node.js中Express框架连接Mysql实现用户注册接口 处理用户注册接口简单分为三步&#xff1a; 1、注册校验 2、完善逻辑 3、拆分模块 拆分模块能够使部分功能能够复用&#xff0c;封装好各个模块使得模块间只能通过有限的接口互相访问&#xff0c;从而降低耦合&#xff0c;拆分模…

LeetCode[1046]最后一块石头的重量

难度&#xff1a;简单 题目&#xff1a; 有一堆石头&#xff0c;每块石头的重量都是正整数。每一回合&#xff0c;从中选出两块最重的 石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果如下&#xff1a;如果 x …

『年度总结』时光如梭 | 再见 2022 | 你好 2023

⭐创作时间2022年12月31日⭐ ✨结果一直到现在才发&#xff0c;说真的写年度总结还是第一次写比较不熟练&#xff0c;去年有这个活动也有佬叫我参加&#xff0c;不过没参加。今年想着有时间来写下的&#xff0c;结果写到现在才发&#xff0c;这东西说真的挺难写的&#…

机器视觉(九):图像配准

目录&#xff1a; 机器视觉&#xff08;一&#xff09;&#xff1a;概述 机器视觉&#xff08;二&#xff09;&#xff1a;机器视觉硬件技术 机器视觉&#xff08;三&#xff09;&#xff1a;摄像机标定技术 机器视觉&#xff08;四&#xff09;&#xff1a;空域图像增强 …

python简单爬虫

爬虫真是一件有意思的事儿啊&#xff0c;之前写过爬虫&#xff0c;用的是urllib2、BeautifulSoup实现简单爬虫&#xff0c;scrapy也有实现过。最近想更好的学习爬虫&#xff0c;那么就尽可能的做记录吧。这篇博客就我今天的一个学习过程写写吧。 一 正则表达式 正则表达式是一…

格式化电脑重装系统怎么操作

​电脑一但中毒的电脑必须重装系统&#xff0c;而且需要格式化后重装系统&#xff0c;才能将病毒铲除&#xff0c;那么如何将电脑格式化后重装系统呢&#xff1f;能够实现电脑格式化重装系统的方法是U盘重装和光盘重装&#xff0c;由于部分电脑没有光驱&#xff0c;建议用U盘&a…

Redis常见集群方案

Redis常见集群方案 Redis集群方案目前主流的有三种&#xff0c;分别是Twemproxy、Codis和Redis Cluster。 Redis Cluster Redis Cluster 集群是去中心化通过客户端分片的结构&#xff0c;集群元数据信息分布在每个节点上&#xff0c;主备切换依赖于多个节点协商选主。 Red…

C++11之lambda表达式

文章目录一、引入原因二、lambda 表达式的语法1. lambda 表达式各部分说明2.捕捉列表说明三、lambda 表达式的本质一、引入原因 如果待排序元素为自定义类型&#xff0c;需要用户定义排序时的比较规则。 比如&#xff1a; struct Goods {string _name; // 名字double _pr…

idea调试npm、tomcat远程服务(包括docker部署方式)

前言 idea调试npm、tomcat远程服务&#xff1a;包括docker部署方式及非docker部署方式 博客地址&#xff1a;芒果橙的个人博客 【http://mangocheng.com】 调试npm项目 1. 新增一个npm项目 2. 配置package.json及启动脚本 3. debug模式启动 远程调试docker部署的项目 1. 配置…

XMLTomcatHttp协议

XML&Tomcat&Http协议 学习目标 了解配置文件的作用了解常见的配置文件类型掌握properties文件的编写规范掌握xml文件的编写了解xml文件的约束掌握xml文件的解析掌握Tomcat的安装掌握Tomcat的使用掌握Tomcat在IDEA中的使用了解HTTP协议的发展历程了解HTTP1.0和HTTP1.1…

ConcurrentHashMap源码阅读笔记:initTable()方法

一、非常重要的sizeCtl属性 initTable()方法的作用是初始化哈希表&#xff0c;初始化哈希表就要有确定哈希表容量、创建哈希表并将哈希表的引用赋值、修改哈希表的阈值等步骤。initTable()方法里面采用了不加锁方式来确保在高并发的环境下创建哈希表的全部步骤都只能由一个线程…

Android入门第57天-使用OKHttp多线程制作像迅雷一样的断点续传功能

简介今天我们将继续使用OkHttp组件并制作一个基于多线程的可断点续传的下载器来结束Android OkHttp组件的所有知识内容。在这一课里我们会在上一次课程的基础上增加SQLite的使用以便于我们的App可以暂存下载时的实时进度&#xff0c;每次下载开始都会判断是覆盖式还是续传式下载…

(3)go-micro微服务项目搭建

文章目录一 微服务项目介绍二 go-micro安装1.拉取micro镜像2.生成项目目录三 项目搭建使用DDD模式开发项目&#xff1a;四 最后一 微服务项目介绍 账户功能是每一个系统都绕不开的一部分&#xff0c;所以本次搭建的微服务项目就是账户微服务项目&#xff0c;其中向外暴露的功能…

【C语言航路】第十站:指针进阶(一)

目录 一、字符指针 二、指针数组 三、数组指针 1.数组指针的定义 2.数组名和&数组名 3.数组指针的使用 四、数组参数、指针参数 1.一维数组传参 2.二维数组传参 3.一级指针传参 4.二级指针传参 五、函数指针 总结 一、字符指针 我们知道指针有一种类型叫做字符…

Vue3新特性

文章目录一 新特性之组合API1.1 ref&reactive1.2 methods1.3 props和context1.4 完整代码&效果演示二 Vue3新特性之生命周期函数(在setup中)三 父子级组件间数据传递3.1 Provide&Inject四 Fragment&#xff08;碎片&#xff09;一 新特性之组合API 1.1 ref&re…

XMLHttpRequest和Referer

XMLHttpRequest 对象简介 1999年&#xff0c;微软公司发布 IE 浏览器5.0版&#xff0c;第一次引入新功能&#xff1a;允许 JavaScript 脚本向服务器发起 HTTP 请求。这个功能当时并没有引起注意&#xff0c;直到2004年 Gmail 发布和2005年 Google Map 发布&#xff0c;才引起广…