Java多线程-线程的创建(Thread类的基本使用)

news2025/2/6 9:55:39

文章目录

  • 一. 线程和Thread类
    • 1. 线程和Thread类
      • 1.1 Thread类的构造方法
      • 1.2 启用线程的相关方法
    • 2. 创建第一个Java多线程程序
    • 3. 使用Runnable对象创建线程
    • 4. 使用内部类创建线程
    • 5. 使用Lambada表达式创建线程
    • 6. 多线程并发执行简单演示
    • 7. 多线程并发执行的优势
  • 二. Thread类的属性与方法
    • 1. Thread类中的重要属性
    • 2. Thread类中常用方法总结
      • 2.1 常用方法
      • 2.2 中断线程
      • 2.3 线程等待
      • 2.4 线程调用start和直接调用run的区别
  • 三. Java线程的状态
    • 1. Java线程中的基本状态
    • 2. 线程的状态转移

在Java中如何进行多线程编程呢?
关于线程的操作, 操作系统是提供了一系列的API的, 而Java是一个跨平台的语言, 很多操作系统提供的功能都是被JVM封装好了的, 所以用Java进行多线程编程, 只需要学习Java提供的API即可, Java中进行多线程操作主要是 Thread类.

一. 线程和Thread类

1. 线程和Thread类

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

也就是说在Java中Thread类的实例就对应着一个线程.

1.1 Thread类的构造方法

方法说明
public Thread()无参数构造方法
public Thread(String name)指定线程名创建线程
public Thread(Runnable target)传入实现Runnable接口的对象 (任务对象) 构造线程
public Thread(Runnable target, String name)根据目标任务并指定线程名创建线程
public Thread(ThreadGroup group, Runnable target)根据线程组和任务创建线程(了解)
public Thread(ThreadGroup group, Runnable target, String name)根据线程组和任务创建线程并指定线程名

1.2 启用线程的相关方法

方法说明
public void run()用来封装线程运行时执行的内容, 线程线程创建时必须重写run方法
public synchronized void start()线程创建并执行run方法
public static native void sleep(long millis) throws InterruptedException使线程休眠millis毫秒

2. 创建第一个Java多线程程序

我们这里创建一个MyThread类并继承Thread类, 然后重写run方法.

class MyThread extends Thread{
    //重写run方法
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        //创建MyThread线程对象,注意此时并没有创建线程
        Thread thread = new MyThread();
        //线程创建并运行
        thread.start();
    }
}

注意:

上面的代码中, 使用new创建线程对象, 此时仅仅是创建了一个线程对象, 并没有去创建线程, 当线程对象去调用start方法时才会创建线程并执行线程任务也就是run方法.

执行结果:

img

3. 使用Runnable对象创建线程

上面介绍的是使用子类继承Thread并重写run方法来创建线程, 除了这种方法还可以使用Runnable接口, 这个接口也有一个run方法, 这个Runnable对象可以理解成"任务", 可以将这个任务交给Thread对象去进行构造, 然后执行任务(执行run方法).

同样的, Runnable是一个接口, 子类实现了该接口后, 需要重写run方法, 线程Thread对象构造好后, 此时线程还没有创建, 调用start对象来创建线程并执行即可.

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("使用Runnable描述任务!");
    }
}

public class TestDemo1 {
    public static void main(String[] args) {
        //创建Runnable任务传给Thread对象来进行构造
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        //启动线程
        thread.start();
    }
}

执行结果:
img

这种写法相较于上面的写法让线程和和线程要干的活分离开来, 相较于上面就是在解耦合了, 这种写法更符合 “高内聚, 低耦合的编程风格”, 也是更推荐的写法.

4. 使用内部类创建线程

针对上面的2和3, 我们可以写的更简洁一些, 使用匿名内部类来传入匿名对象来重写run方法.

public class TestDemo2 {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("使用匿名内部类创建线程匿名对象!");
            }
        };
        thread.start();
    }
}

执行结果:

img

public class TestDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用匿名内部类创建线程匿名对象(1)!");
            }
        });
        t.start();
    }
}

执行结果:

img

5. 使用Lambada表达式创建线程

针对上面的3还可以进行简写, 写成lambada表达式的形式, 本质还是使用匿名内部类创建的Thread.

public class TestDemo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> 
                System.out.println("使用lambda表达式来创建线程!"));
        thread.start();
    }
}

执行结果:

img

6. 多线程并发执行简单演示

一个代码程序中有一个main方法, 这里其实有一个main线程(主线程, 这个线程是程序运行时自动创建的), 在一个进程中至少会有一个线程, 如果不使用多线程编程, 一个进程中默认执行的就是这个main线程.

而如果我们再在main中创建一个新线程t并调用, 这个线程t会与mian线程并发执行.

public class TestDemo5 {
    public static void main(String[] args) {
        //thread 线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("thread线程执行中!");
                    //为了让这里的打印慢一点使效果更加明显,可以使用sleep方法设定线程睡眠时间
                    try {
                        Thread.sleep(1000);//每执行一次循环就睡眠1秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

        //main 线程
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程执行中!");
            //为了使效果更加明显,可以使用sleep方法设定线程睡眠时间
            try {
                Thread.sleep(1000);//每执行一次循环就睡眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

img

观察上面的运行结果, 可以发现结果中thread线程和main线程打印顺序是不固定的( “随机” 的), 这个问题有些时候会给我们带来很多麻烦(比如线程安全问题), 属于是Java多线程中的一个 “万恶之源” 了, 也就是说, 这里两个线程的执行顺序完全是随机的, 我们是没有办法去预测和设置的.

这里究其更本在于操作系统调度线程的时候, 线程是 “抢占式执行” 的, 具体哪个线程先执行, 哪个线程后执行是不确定的, 取决于操作系统调度器的具体实现策略.

注意这里的随机只是我们肉眼所看到的是随机的, 内核中线程的执行并不是随机的, 是有优先级的, 但是干预因素太多, 应用程序层面上我们也无法感知到细节也无法修改, 从应用程序(代码)的角度看到的效果, 就好像是线程之间的调度顺序是"随机"的一样, 我们也只能认为是随机的了.

7. 多线程并发执行的优势

假设当前有两个变量, 需要把两个变量各自自增 1000w 次(典型的 CPU 密集型的场景), 可以一个线程, 先针对 a 自增, 然后再针对 b 自增; 还可以两个线程, 分别对 ab 自增; 我们来看看使用两个线程和单独使用一个线程分别所需的时间是多少.

public class TestDemo6 {
    public static void main(String[] args) {
        //多线程
        concurrency();
        //单线程
        serial();
    }

    // 串行执行, 一个线程完成
    public static void serial() {
        // currentTimeMillis 获取到当前系统的 ms 级时间戳.
        long beg = System.currentTimeMillis();

        long a = 0;
        for (long i = 0; i < 100_0000_0000L; i++) {
            a++;
        }
        long b = 0;
        for (long i = 0; i < 100_0000_0000L; i++) {
            b++;
        }

        long end = System.currentTimeMillis();
        System.out.println("单线程执行时间: " + (end - beg) + " ms");
    }

    public static void concurrency() {
        // 使用两个线程分别完成自增.
        Thread t1 = new Thread(() -> {
            long a = 0;
            for(long i = 0; i < 100_0000_0000L; i++) {
                a++;
            }
        });
        Thread t2 = new Thread(() -> {
            long b = 0;
            for(long i = 0; i < 100_0000_0000L; i++) {
                b++;
            }
        });
        // 记录开始执行的时间戳
        long beg = System.currentTimeMillis();
        t1.start();
        t2.start();

        try {
            // 等待两个线程结束后,再获取结束时的时间戳
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 获取执行结束时的时间戳
        long end = System.currentTimeMillis();
        System.out.println("多线程执行时间: " + (end - beg) + " ms");
    }
}

执行结果:

img

根据结果此时两个线程并发执行的时间要单线程执行的时间快很多的, 此时多线程并发就可以提高程序整体的运行效率, 多线程比单线程执行的时间缩短并不是刚好一般的关系, 这是因为多线程能够更充分的利用到多核心cpu的资源, 但是此处我们并不是保证t1和t2是并行执行, 而不是并发执行.

那么多线程并发执行是否一定就优于单线程呢, 其实不然, 如果任务量不够大, 可能多线程相比于单线程并不会有优势, 毕竟创建线程本身还是有开销的; 还有如果在同一进程中一味的增加线程的数量, 也不是一直可以提高运行速度, 要知道我们电脑中CPU核心数量是有限的, 当线程太多, 核心数目有限, 不少的开销反而浪费在了线程调度上, 可能就没办法高效执行任务了.

多线程在IO密集型的任务中, 也是有作用的, 日常使用一些程序,经常会看到"程序未响应", 这是因为程序进行了一些耗时的IO操作(比如一些程序启动要加载数据文件,就涉及到大量的读硬盘操作), 阻塞了界面的响应, 这种情况下使用多线程也是可以有效改善的(让一个线程负责IO, 另一个线程用来响应用户的操作).

二. Thread类的属性与方法

1. Thread类中的重要属性

属性获取该属性的方法
线程的标识(ID)public long getId()
线程的名称public final String getName()
线程的状态public State getState()
线程的优先级public final int getPriority()
线程是否是后台(守护)线程public final boolean isDaemon()
线程是否存活public final native boolean isAlive()
线程是被中断public boolean isInterrupted()

每一个线程都有一个id作为标识, 但并不是每个线程都有唯一的id, 只有处于同一进程的线程id才是相同的, 唯一标识的的说法按进程说才是正确的, 每个进程都拥有唯一的id标识.

上面说过, 线程的名字我们可以通过构造方法手动去设置, 但如果我们创建Thread对象时, 没有指定线程对象的名称, 则会默认命名为Thread-i, 其中i为整数.

同样的, Java中的线程的状态也有运行, 阻塞, 就绪3种状态, 每个线程也有优先级.

Java当中线程分为前台线程和后台线程(也叫守护线程), 其中后台线程不会影响进程的退出, 而前台线程会影响进程的退出, 比如同一进程内有tmain(主线程)两个线程, 当t为前台线程, main方法执行完毕时, 如果此时t线程还没有执行完, 那么t线程会继续执行, 进程等到t线程执行结束后才会退出; 反之, 如果t线程为后台线程, main方法执行完毕, 此时t线程没有执行完也会被强制结束, 也就是是说此时main结束后, 整个进程就随之结束了; 我们代码里手动创建的线程(包括main线程)默认都是前台的, 其他JVM自带的线程都是后台的, 我们可以手动使用setDaemon将线程设置为后台线程.

关于java线程的属性, 我们可以通过java官方的jconsole调试工具查看java线程的一些属性, 这个工具一般在jdkbin目录下.

img

双击打开出现如下界面, 选择需要查看的进程连接查看:

img

img

选择需要查看的进程属性查看:

img

2. Thread类中常用方法总结

2.1 常用方法

方法说明
public void run()该方法用来封装线程运行时执行的内容
public synchronized void start()线程创建并执行run方法
public static native void sleep(long millis) throws InterruptedException使线程休眠millis毫秒
public final void join() throws InterruptedException等待线程结束
public final synchronized void join(long millis) throws InterruptedException等待线程结束, 最多等待millis毫秒
public final synchronized void join(long millis, int nanos) throws InterruptedException指定最多等待时间等待线程, 精确到纳秒
public void interrupt()中断线程, 修改中断标志位状态为true, 如果线程在休眠(阻塞状态)会抛出异常通知, 同时再将标志位设置回false
public static boolean interrupted()判断当前线程的中断标志位是否设置, 调用后会清除线程的中断标志位
public boolean isInterrupted()判断当前线程的中断标志位是否设置, 调用后不会影响线程的标志位
public final synchronized void setName(String name)修改线程对象名称
public static native Thread currentThread()获取当前线程对象

2.2 中断线程

首先要理解好这里中断的意思, 这里的中断, 不是让线程立即停止, 而是通知线程, 你应该要停止了, 至于是否真的停止, 取决于线程代码的具体写法.

  • 方式1: 自己设置一个标志位来来控制线程是否要停止

如果我们想中断一个正在执行的线程, 该如何做呢?

最简单方法就是我们在run方法中定义一个中断标志位(有truefalse两种状态), 每次执行具体任务时需要先判断中断标志位的状态, 如果是false就结束线程, 为true继续执行.

public class TestDemo7 {
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (flag) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        Thread.sleep(3000);
        // 在主线程里就可以随时通过 flag 变量的取值, 来操作 t 线程是否结束.
        flag = false;
    }
}

执行结果:

img

这个代码是否能够起到通过修改flag就让t线程就结束, 完全取决于t线程内部代码的写法, 代码里通过flag 来控制循环; 因此, 这里flag = false只是告诉让这个线程结束, 这个线程是否要结束, 啥时候结束都是线程内部自己代码来决定的.

但这种方法是不够严谨的, 有些时候是不能达到我们想要的效果的, 比如当main线程中设置flagfalse时, 而t线程在此时休眠状态, 此时t线程只有在休眠结束后才能中断, 这种情况下线程t就不能及时的做出响应进行中断, 而我们期望的是flag的值设置为false时, t线程可以立即中断.

  • 方式2: 使用Thread类中自带标志位

首先使用currentThread方法获取线程对象, 然后再调用该对象中的isterrupted方法获取该对象的中断标志位代替我们上面所写的flag标志位, 然后等该线程运行一段时间后使用interrupt方法改变标志位, 中断线程, 写出如下代码观察效果:

public class TestdDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt();
    }
}

执行结果:

img

观察上面的执行结果, 发现t线程并没有按照我们所预期的中断, 在抛出一个InterruptedException异常后, 线程t又继续执行了, 原因时interrupt方法遇到因为调用 wait/join/sleep 等方法而阻塞的线程时会使这些方法抛出异常, 并且清空标志位(设置回false), 这时我们的catch语句里面值输出了异常信息并没有去中断异常, sleep清空标志位的目的在于, 将线程从休眠状态唤醒之后, 线程是否要终止, 还是要过一会儿再终止, 选则权就交给程序员自己了.

那么如何解决呢? 很简单, 所以我们需要在catch语句中加上线程让线程退出的break语句即可.

public class TestdDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt();
    }
} 

执行结果:

img

除了isInterrupted, 还有一个静态方法interrupted能够访问中的标志位, 我们也可以使用该静态方法来作为中断标志位, 仍然使用interrupt方法来中断线程执行.

public class TestDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.interrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);

        t.interrupt();
    }
}

执行结果:

img

2.3 线程等待

线程是一个随机调度的过程, 线程等待要做的事情就是控制两个线程结束的顺序.

上面演示多线程并发执行的优势的代码中就使用了join进行了线程等待, main线程需要等到t1和t2两个线程执行结束后才能执行后面的代码操作.

这里需要理解的是, 使用了join方法是进行线程等待, 那么是谁等谁呢?

其实也好理解, 假设有AB两个线程, 线程A当中调用了B.join()方法, 那么此时线程A表示调用join方法的线程, 线程B表示join方法来自B线程对象, 这种情况下就是A线程等待B线程结束.

join的无参数版本是一个 “死等” 的方法, 还是上面的例子, 如果B线程中出现了bug, 导致B线程不能结束, 此时A线程也就不能正常执行了, 程序就僵住了, 所以为了防止这种死等情况的出现, join方法还有带参数的版本, 可以指定最长等待时间.

public class TestDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        // 此处的 join 就是让当前的 main 线程来等待 t 线程执行结束 (等待 t 的 run 执行完)
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t线程执行完之后再执行这里!");
    }
}

执行结果:

img

2.4 线程调用start和直接调用run的区别

通过上面的内容, 我们知道执行一个线程的任务就是执行线程对象中重写的run方法, 那么我们是否可以直接调用run方法来代替start方法呢?

答案是不行的, 这两种调用方式是有本质区别的, 直接通过线程对象调用run方法就是单纯地调用了Thread对象中的一个普通方法而已, 并没有创建一个新线程来执行run方法, 而是通过main线程来执行的run方法, 而使用start方法, 会创建一个新线程并执行run方法.

三. Java线程的状态

1. Java线程中的基本状态

操作系统中进程的状态有三种分别为阻塞, 就绪和执行, 而java线程中的状态基本上相同, 只是区分的更加细节, 具体如下:

NEW: 安排了工作, 还未开始行动, 就是创建了Thread对象, 但还没有执行start方法(内核里面还没有创建对应PCB), 这个状态是java内部的状态, 与操作系统中线程的状态没有关联.

RUNNABLE: 可工作的, 又可以分成正在工作中和即将开始工作(即正在CPU上执行的任务或者在就绪队列里随时可以去CPU上执行的).

BLOCKED: 线程正在等待锁释放而引起的阻塞状态(synchronized加锁).

WAITING: 线程正在等待等待唤醒而引起的阻塞状态(waitf方法使线程等待唤醒).

TIMED_WAITING: 在一段时间内处于阻塞状态, 通常是使用sleep或者join(带参数)方法引起.

TERMINATED:Thread对象还存在, 但是关联的线程已经工作完成了, 这个状态也是java内部的状态, 与操作系统中线程的状态没有关联.

线程的状态其实是一个枚举类型:Thread.State

img

我们通过遍历来看一下这个枚举有哪些状态:

public class Test {
    public static void main(String[] args) {
        for(Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

执行结果:

img

我们知道操作系统内核中线程是通过一个PBC结构体来描述的, 多个线程是通过链表连在一起的, 但要注意这些线程不一定是在一个链表里, 具体在哪个链表跟线程的状态有关, 内核中有就绪队列和阻塞队列两个队列, 操作系统每次需要调度一个线程去执行就是从就绪队列中选一个节点去执行, 当线程进入阻塞状态, 就会把这个线程从就绪队列中拎出来放到阻塞队列中, 阻塞队列中的线程都是阻塞状态, 暂时不参与CPU的调度执行, 但当这个线程从阻塞状态被唤醒回到就绪队列, 考虑到调度的开销, 唤醒之后不会被立即调度执行的.

比如调用线程Asleep(1000), 对应的线程PCB就要再阻塞队列中待1000ms这么久, 当这个PCB回到了就绪队列, 虽然是sleep(1000), 但是实际上考虑到调度的开销, 对应的线程是无法在唤醒之, 后立即就执行的.实际上的时间间隔大概率要大于1000ms.

2. 线程的状态转移

img

简单演示一下:

public class TestDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                // 这个循环体啥都不干, 也不 sleep
            }
        });
        // 启动之前, 获取 t 的状态, 就是 NEW 状态.
        System.out.println("start之前的状态: "+t.getState());
        t.start();
        System.out.println("t执行中的状态: "+t.getState());
        t.join();
        // 线程执行完毕之后, 就是 TERMINATED 状态
        System.out.println("t结束之后的状态: "+t.getState());
    }
}

执行结果:

img

当我们在t线程中加上sleep方法时, 我们看到的打印出来状态是RUNNABLE还是TIME_WAITING取决于我们t线程当时运行到了哪个环节, 即取决于系统里的调度操作, 获取状态这一瞬间线程是sleep, 还是正在执行.

public class TestDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 启动之前, 获取 t 的状态, 就是 NEW 状态.
        System.out.println("start 之前: " + t.getState());
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("t 执行中的状态: " + t.getState());
        }
        t.join();
        // 线程执行完毕之后, 就是 TERMINATED 状态
        System.out.println("t 结束之后: " + t.getState());
    }
}

执行结果:

img

获取的100次 t 执行中的状态中RUNNABLE的次数是要少于TIMED_WAITING的次数的, 这是因为相比于t在cpu上执行的时间来说, sleep(10)这个时间太长了; 要想让时间更均衡, 需要给t线程的run方法加入更多的计算逻辑.

WAITINGBLOCKED也类似, 只是触发条件不同, 一个是wait, join, 另一个是synchronized加锁等.

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

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

相关文章

【专业数据】六.2020~2022年北京交通大学【新一代电子信息技术】专业复试线/分数线差/计划招生数/复试数/录取数/复试比例/录取率

文章目录 1.专业介绍2.2020-2022年国家线/复试线/分数线差2.1.数据总览2.2.数据指标2.2.1.复试分数线2.2.2.分数线差3.2020-2022年计划招生数/复试数/录取数/复试比例/录取率3.1.数据总览3.2.数据指标3.2.1.复试比例3.2.2.录取率4.参考资料欢迎订阅本专栏:《北交计算机复试经验…

【数据结构】树和二叉树

半山腰很挤&#xff0c;你得去山顶看看 目录 1.树 1.1 树的概念 1.2 树的特征 1.3 树每个结点的关系 1.4 树的表示形式 2.二叉树 2.1 二叉树的概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4 二叉树的存储 2.5 二叉树的基本操作 2.5.1 判断二叉树是否为空 2.…

JAVA类的继承和多态基础笔记(二)

1.继承的基本概念 父类中某一个属性是私有的&#xff0c;通过子类对象就不能访问父类的私有变量。 继承完之后拥有父类全部的东西&#xff0c;但是可以根据实际情况进行重写。 一般所有的类都是继承Object的&#xff0c;实现所有他的方法 像这样是重写了Object类的tostring方法…

[附源码]计算机毕业设计Python共享汽车系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【Numpy基础知识】数组的创建

介绍 来源&#xff1a;Numpy官网&#xff1a;https://numpy.org/doc/stable/user/basics.html 文章目录介绍导包【1】将Python序列转换为Numpy数组【2】通过已有的Numpy数组创建函数创建数组【3】复制、连接或者改变现有数组【4】从磁盘读取数组【5】使用字符串或缓冲区从原始字…

[附源码]Nodejs计算机毕业设计久宠宠物店管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

载天冬酰胺酶磺丁基-β-/酶羟丙基-β-/天门冬酰胺酶环/EGF/维生素E乙酸酯糊精脂质体制备

下面为大家整理的科研内容是载天冬酰胺酶磺丁基-β-/酶羟丙基-β-/天门冬酰胺酶环/EGF/维生素E乙酸酯糊精脂质体制备&#xff0c;和小编一起来看&#xff01; 载天冬酰胺酶磺丁基-β-环糊精制备&#xff1a;采用逆向蒸发法制备ASDL,通过酸碱稳定性,热稳定性,抗胰蛋白酶稳定性,血…

【专业数据】七.2020~2022年北京交通大学【计算机技术】专业复试线/分数线差/计划招生数/复试数/录取数/复试比例/录取率

文章目录 1.专业介绍2.2020-2022年国家线/复试线/分数线差2.1.数据总览2.2.数据指标2.2.1.复试分数线2.2.2.分数线差3.2020-2022年计划招生数/复试数/录取数/复试比例/录取率3.1.数据总览3.2.数据指标3.2.1.复试比例3.2.2.录取率4.参考资料欢迎订阅本专栏:《北交计算机复试经验…

写前端?Python有手就行...

前端除了用jscsshtml&#xff0c;还有没有其它办法&#xff1f;其实python也可以 爬它&#xff01;&#xff08;https://jq.qq.com/?_wv1027&keSp12WR5&#xff09; 1. 安装与基本流程 安装 PyWebIO 和其他的第三方库一样使用pip install PyWebIO就行&#xff0c;没有…

云原生 | Kubernetes - 资源指标管道

目录 Metrics API 度量资源用量 CPU 内存 Metrics 服务器 对于 Kubernetes&#xff0c;Metrics API 提供了一组基本的指标&#xff0c;以支持自动伸缩和类似的用例。 该 API 提供有关节点和 Pod 的资源使用情况的信息&#xff0c; 包括 CPU 和内存的指标。如果将 Metrics …

【设计模式】适配器模式 (七)

文章目录5.2 适配器模式5.2.1 概述5.2.2 结构5.2.3 类适配器模式5.2.4 对象适配器模式5.2.5 应用场景5.2.6 JDK源码解析5.2 适配器模式 5.2.1 概述 如果去欧洲国家去旅游的话&#xff0c;他们的插座如下图最左边&#xff0c;是欧洲标准。而我们使用的插头如下图最右边的。因此…

机器学习笔记之玻尔兹曼机(一)基本介绍

机器学习笔记之玻尔兹曼机——基本介绍引言回顾&#xff1a;玻尔兹曼机的模型表示模型参数的对数似然梯度关于模型参数W\mathcal WW的对数似然梯度关于模型参数L,J\mathcal L,\mathcal JL,J的对数似然梯度引言 在受限玻尔兹曼机——模型表示(Representation)一节中以玻尔兹曼机…

五环三萜类化合物环糊精包合物前体脂质体/呋喃硫胺/6已基环糊精纳米粒制备

小编在这里为大家分享的是五环三萜类化合物环糊精包合物前体脂质体/呋喃硫胺/6已基环糊精纳米粒&#xff0c;一起来看看吧&#xff01; 五环三萜类化合物环糊精包合物前体脂质体制备方法&#xff1a; 通过环糊精将五环三萜类化合物包合提高亲水性,并以脂质体为载体进行包覆,通…

[Linux]线程概念_线程控制(线程与进程的区别与联系 | 线程创建 | 线程等待 | 线程终止 | 线程分离 | LWP)

文章目录线程概念进程和线程的关系线程的优点线程的缺点线程控制Linux线程和接口关系的认识线程创建线程ID及进程地址空间布局线程等待线程终止线程终止状态线程分离LWP和pthread_t线程概念 线程是在进程内部运行的一个执行分支&#xff08;执行流&#xff09;&#xff0c;属于…

我会用12种编程语言,但是偏爱python,看完文章你就知道原因了

刚开始学编程&#xff0c;或者学习一门新的编程语言时&#xff0c;我们编写的第一个程序往往很简单&#xff0c;而且往往这个程序就是输出一个简单的文本。在阅读本文时&#xff0c;你可能心知肚明我指的是哪种最常见的文本。 没错&#xff0c;我说的就是 Hello world。 这段文…

当中医遇上AI,贝叶斯携手昇思打造AI舌诊联合方案

中医的困局 中医迄今已有数千年历史&#xff0c;是老祖宗留给我们最宝贵的财富之一。然而&#xff0c;随着近代医学技术的发展&#xff0c;与声势浩大的西医相比&#xff0c;中医逐渐失势。 近年来&#xff0c;人口老龄化导致慢性病发病率持续增高。国家层面出台《健康中国行…

生态类型 or 生境类型?16S全长测序判断河流中细菌群落构建机理

文献标题&#xff1a; Distinct composition and assembly processes of bacterial communities in a river from the Arid area: Ecotypes or Habitat Types? 研究背景 细菌群落在河流生态系统中起着重要的作用&#xff0c;如元素循环和有机物的降解等。对河流细菌群落的扰…

基于实时迭代的数值鲁棒NMPC双模稳定预测模型(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

还在用明文存储密码吗?快来了解一下加密吧

目录 一. 数据库为什么不能明文存储密码 二. MD5到底是什么&#xff1f; 2.1 MD5概述 2.2 MD5为什么不是加密算法&#xff1f; 2.3 MD5主要特点MD5 2.4 MD5的性质 2.5 为什么用MD5存储密码不好 三. 给密码加盐再落库&#xff0c;增加破解成本 四. BCrypt算法加密解密…

零入门容器云网络-8:网络虚拟设备之tun设备介绍

已发表的技术专栏&#xff08;订阅即可观看所有专栏&#xff09; 0  grpc-go、protobuf、multus-cni 技术专栏 总入口 1  grpc-go 源码剖析与实战  文章目录 2  Protobuf介绍与实战 图文专栏  文章目录 3  multus-cni   文章目录(k8s多网络实现方案) 4  gr…