Java多线程基础

2023-11-07

原文链接:https://blog.csdn.net/weixin_43704599/article/details/107379994

 

1. 线程简介

1.1 多任务

image-20200714211333034

现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时间只做了一件事情

1.2 多线程

image-20200714211607891

原来是一条路,慢慢因为车太多了,道路堵塞,效率极低.为了提高使用的效率,能够充分利用道路,于是加了多个车道,从此再也不用担心道路堵塞了

1.2.1 普通方法调用和多线程

image-20200714211910849

1.3 程序-进程-线程

image-20200714212306447

一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等

1.3.1 Process与Thread

说起进程,就不得不说下程序,程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
而进程则是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意思了,线程是CPU调度和执行的单位.
 

 

注意:很多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器.如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉

 

1.4 核心概念
线程就是独立的执行路径
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程(main),和垃圾回收线程(gc)
main()称之为主线程,为程序的入口,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为的干预
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,例如cpu调度时间,并发控制开销
每个线程在自己的工作内存交互,内存控制不会造成数据不一致

2. 线程实现

2.1 三种创建方式

image-20200714213806270

2.1.1 Thread
package com.bigdata.demo01;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo01
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/14 21:42
 */
//创建线程方式一: 继承Thread类,重写run()方法,调用start开启线程
// 总结: 注意,线程开启不一定立即执行,由CPU调度执行    
public class TestThread1 extends Thread {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20 ; i++) {
            System.out.println("我在看代码--"+i);

        }
    }

    public static void main(String[] args) {
        //mian线程,主线程
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程--"+i);
        }
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start()方法开启线程
        testThread1.start();
    }
}

image-20200714214740961

2.1.2 网图下载

maven

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
 

网图下载

package com.bigdata.demo01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo01
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/14 21:53
 */
//练习Thread,首先多线程同步下载图片
public class TestThread2 extends Thread {
    private String url;
    private String name;

    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为: " + name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test1.png");
        TestThread2 t2 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test2.png");
        TestThread2 t3 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test3.png");
        t1.start();
        t2.start();
        t3.start();

    }
}

//下载器
class WebDownLoader {
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

2.2.3 实现Runnable

image-20200714223914180

推荐使用Runnable对象,因为Java单继承的局限性

package com.bigdata.demo01;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo01
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/14 22:14
 */
public class TestThread3 implements Runnable {

    public void run() {
        //run方法线程体
        for (int i = 0; i < 20 ; i++) {
            System.out.println("我在看代码--"+i);

        }
    }

    public static void main(String[] args) {
        //mian线程,主线程
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程--"+i);
        }
        //创建一个线程对象,通过线程对象来开启我们的线程
        Thread thread = new Thread(new TestThread3());
        thread.start();
    }
}

2.3.4 修改网图下载为Runnable实现方式
package com.bigdata.demo01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo01
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/14 21:53
 */
//练习Thread,首先多线程同步下载图片
public class TestThread2 implements Runnable {
    private String url;
    private String name;

    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    public void run() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为: " + name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test1.png");
        TestThread2 t2 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test2.png");
        TestThread2 t3 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test3.png");
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();

    }
}

//下载器
class WebDownLoader {
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

2.4.5 实现Callable接口

image-20200715112950961

package com.bigdata.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo02
 * Version: 1.0
 * 线程的创建方式三 实现Callable
 * Callable 接口 可以获取返回值 和 抛出异常
 * @author qingzhi.wu 2020/7/15 11:31
 */
public class TestCallable implements Callable {
    private String url;
    private String name;

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test1.png");
        TestCallable t2 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test2.png");
        TestCallable t3 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594745456785&di=35c95f24cfde5fa5d4486ce2e43e84d5&imgtype=0&src=http%3A%2F%2Ft8.baidu.com%2Fit%2Fu%3D2247852322%2C986532796%26fm%3D79%26app%3D86%26f%3DJPEG%3Fw%3D1280%26h%3D853","test3.png");

        //创建执行服务:
        ExecutorService ser = Executors.newFixedThreadPool(3);

        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        ser.shutdown();

    }

    public Boolean call() throws Exception {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("下载了文件名为: " + name);
        return true;
    }
}

//下载器
class WebDownLoader {
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }

}

2.4.6 龟兔赛跑-Race
首先来个赛道距离,然后要离终点越来越近
判断比赛是否结束
打印出胜利者
龟兔赛跑开始
故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
package com.bigdata.demo01;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo01
 * Version: 1.0
 * 模拟龟兔赛跑
 * @author qingzhi.wu 2020/7/15 10:31
 */
public class Race  implements Runnable{
    //胜利者
    private static String winner;
    
    public void run() {
        for (int i = 1; i < 101; i++) {

            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if (Thread.currentThread().getName().equals("乌龟")){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag = gameOver(i);
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() +"-->跑了" +i+"步");
        }
    }

    //判断是否完成比赛
    private boolean gameOver(int steps){
        if(winner != null){
            return true;
        }
        {
            if(steps >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}
 

 

 

2.4.7 总结
继承Thread类
子类继承Thread类具备多线程能力
启动线程: 子类对象.start()
不建议使用: 避免OOP单继承局限性
实现Runnable接口
实现接口Runnable具有多线程能力
启动线程: 传入目标对象+ Thread对象.start()
推荐使用: 避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
实现Callable接口
实现Callable接口,需要抛出异常
创建目标对象
创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(3);
提交执行: Future<Boolean> r1 = ser.submit(t1);
获取结果: boolean rs1 = r1.get();
关闭服务: ser.shutdown();
 

 

3. 初识并发
3.1 超卖问题
package com.bigdata.demo01;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo01
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/14 22:29
 */
//发现问题:多个线程操作同一个资源的时候,出现数据紊乱的问题
public class TestThread4 implements Runnable {
    private int ticketNums = 10;
    public void run() {
        while(true) {
            if (ticketNums <= 0){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第 "+ ticketNums-- + "票");
        }
    }

    public static void main(String[] args) {
        TestThread4 t4 = new TestThread4();
        new Thread(t4,"小明").start();
        new Thread(t4,"老师").start();
        new Thread(t4,"黄牛党").start();
    }

}

4. 静态代理模式


package com.bigdata.demo02;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo02
 * Version: 1.0
 *
 * 1. 静态代理
 * @author qingzhi.wu 2020/7/15 11:53
 */
public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany company = new WeddingCompany(new You());

        company.happyMarray();

        new Thread(()->System.out.println("我爱你"));
    }

}

interface Marry{
    void happyMarray();
}

class You implements Marry{

    public void happyMarray() {
        System.out.println("秦老师要结婚了,超开心");
    }
}

class WeddingCompany implements Marry{
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    public void happyMarray() {
        before();
        this.target.happyMarray(); //这个就是真实对象的结婚
        after();
    }

    private void after() {
        System.out.println("收尾款");
    }

    private void before() {
        System.out.println("布置现场");
    }

}

5. 线程状态


5.1 线程停止
package com.bigdata.demo03;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 13:56
 */
public class TestStop  implements Runnable{
    public   boolean isRunning = true;
    @Override
    public void run() {

        while (isRunning){
            System.out.println("我一直运行");
        }

    }

    public void stop(){
        this.isRunning = false;
    }

    public static void main(String[] args) throws InterruptedException {

        TestStop testStop = new TestStop();

        Thread t1 = new Thread(testStop);
        t1.start();
       // t1.sleep(500);
        for (int i = 0; i < 1000; i++) {
            System.out.println("main...."+i);
            if (i == 900){
                testStop.stop();
                System.out.println("线程该停止了.");
            }
        }


    }
}


5.2 线程休眠
package com.bigdata.demo03;

import com.bigdata.demo01.TestThread4;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 14:59
 */
public class TestSleep {
    private int ticketNums = 10;
    public void run() {
        while(true) {
            if (ticketNums <= 0){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第 "+ ticketNums-- + "票");
        }
    }

    public static void main(String[] args) {
        TestThread4 t4 = new TestThread4();
        new Thread(t4,"小明").start();
        new Thread(t4,"老师").start();
        new Thread(t4,"黄牛党").start();
    }
}


5.3 线程礼让
package com.bigdata.demo03;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 15:16
 */
public class TestYield {
    public static void main(String[] args) {
        MyYield m1 = new MyYield();
        MyYield m2 = new MyYield();
        new Thread(m1).start();
        new Thread(m2).start();

    }
}

class MyYield implements Runnable{


    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--> 正在执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "--> 正在停止");

    }
}

5.4 线程插队
package com.bigdata.demo03;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 15:22
 */
public class TestJoin {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();

        for (int i = 0; i < 900; i++) {
            System.out.println("main ... "+i);

            if (i == 100 ){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class MyThread implements Runnable{

    @Override
    public void run() {

        for (int i = 0; i < 1000; i++) {
            System.out.println("thread run....."+i);

        }
    }
}

5.5 线程的状态
package com.bigdata.demo03;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 15:53
 */
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread 正在运行");
            }
        });

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();

        state = thread.getState();

        System.out.println(state);

        while (!state.equals(Thread.State.TERMINATED)){
                Thread.sleep(100);
                state = thread.getState();
            System.out.println(state);
        }
        //System.out.println(thread.getState());


    }
}
 


5.6 线程的优先级
package com.bigdata.demo03;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 16:25
 */
public class TestPro {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getPriority());
        MyThread1 t1 = new MyThread1();
        MyThread1 t2 = new MyThread1();
        MyThread1 t3 = new MyThread1();
        MyThread1 t4 = new MyThread1();
        t1.setPriority(8);
        t1.start();

        t2.setPriority(10);
        t2.start();

        t3.setPriority(7);
        t3.start();

        t4.setPriority(5);
        t4.start();

    }
}
class MyThread1 extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getPriority());
    }
}

5.7 守护线程
package com.bigdata.demo03;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo03
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 16:37
 */
public class TestDaemon {
    public static void main(String[] args) {
        Thread god = new Thread(new God());
        god.setDaemon(true);
        Thread you = new Thread(new You());
        god.start();
        you.start();
    }
}

class God implements Runnable{

    @Override
    public void run() {
       while (true){
           System.out.println("上帝守护着你");
       }
    }
}

class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我活着");
        }
        System.out.println("dead");

    }
}

6. 线程同步
6.1 并发简介
并发: 同一个对象被多个线程同时操作

现实生活中,我们也会遇到同一个资源,多个人都想使用的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个的来


处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成队列,等待前面线程使用完毕,下一个线程再使用
6.2 队列和锁
类似于厕所,你得排队上厕所,然后我锁门,谁也进不来

由于同一进程的多个线程共享同一块存储空间,在带来方便的时候,也带了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待

使用后释放锁即可,存在以下问题:

一个线程持有锁会导致其他所有需要此锁的线程被挂起
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
6.3 三大不安全案例
不安全的集合

package com.bigdata.demo04;

import java.util.ArrayList;
import java.util.List;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 20:59
 */
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->
            {
                list.add(Thread.currentThread().getName());
            }
            ).start();


        }

        System.out.println(list.size());
    }
}

不安全的买票

package com.bigdata.demo04;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 * 不安全的买票
 * @author qingzhi.wu 2020/7/15 20:32
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket sta = new BuyTicket();

        new Thread(sta,"苦逼的我").start();
        new Thread(sta,"黄牛").start();
        new Thread(sta,"你").start();

    }
}

class BuyTicket implements Runnable{
    //票
    private int ticketNums = 10;
    boolean flag = true;

    //买票
    @Override
    public void run() {
        while (flag){
            buy();
        }
    }
    private void buy(){
        if(ticketNums <= 0){
            flag = false;
            return;
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "拿到"+ticketNums--);
    }

}


不安全的取钱

package com.bigdata.demo04;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 20:45
 */
public class TestUnsafeBank {

    public static void main(String[] args) {
        Account account = new Account(100,"结婚基金");
        Drawing you = new Drawing(account,50,"你");
        Drawing gridFriend = new Drawing(account,100,"女朋友");
        you.start();
        gridFriend.start();
    }
    //模拟取款
    static class Drawing extends Thread{
        Account account;//账户
        int drawingMoney;
        String name;

        public Drawing(Account account, int drawingMoney, String name) {

            this.account = account;
            this.drawingMoney = drawingMoney;
            this.name = name;
        }

        @Override
        public void run() {
            if (account.money -drawingMoney <0){
                System.out.println(Thread.currentThread().getName()+ "钱不够,取不了");
                return;
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money - drawingMoney;
           // nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name + "余额为:"+account.money);
            //System.out.println(this.getName() + "手里的钱: "+nowMoney);
        }
    }
    //银行:账户
    static class Account{
        int money;
        String name;

        public Account() {
        }

        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
}

6.4 同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包含两种用法:synchronized方法和synchronized块 public synchronized void mehtod(int args)
synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷 若将一个大的方法申明为synchronized将会影响效率

package com.bigdata.demo04;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 * 不安全的买票
 * @author qingzhi.wu 2020/7/15 20:32
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket sta = new BuyTicket();

        new Thread(sta,"苦逼的我").start();
        new Thread(sta,"黄牛").start();
        new Thread(sta,"你").start();

    }
}

class BuyTicket implements Runnable{
    //票
    private int ticketNums = 10;
    boolean flag = true;

    //买票
    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    private synchronized void buy(){
        if(ticketNums <= 0){
            flag = false;
            return;
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "拿到"+ticketNums--);
    }

}


6.5 同步块
同步块synchronized(Obj){}
Obj称之为同步监视器
Obj可以使任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
同步监视器的执行过程
第一个线程访问,锁住同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器没有锁,然后锁定并访问
package com.bigdata.demo04;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 20:45
 */
public class TestUnsafeBank {

    public static void main(String[] args) {
        Account account = new Account(100,"结婚基金");
        Drawing you = new Drawing(account,50,"你");
        Drawing gridFriend = new Drawing(account,100,"女朋友");
        you.start();
        gridFriend.start();
    }
    //模拟取款
    static class Drawing extends Thread{
        Account account;//账户
        int drawingMoney;
        String name;

        public Drawing(Account account, int drawingMoney, String name) {

            this.account = account;
            this.drawingMoney = drawingMoney;
            this.name = name;
        }

        @Override
        public void run() {
            synchronized (account){
                if (account.money -drawingMoney <0){
                    System.out.println(Thread.currentThread().getName()+ "钱不够,取不了");
                    return;
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.money = account.money - drawingMoney;
                // nowMoney = nowMoney + drawingMoney;

                System.out.println(account.name + "余额为:"+account.money);
                //System.out.println(this.getName() + "手里的钱: "+nowMoney);
            }

        }
    }
    //银行:账户
    static class Account{
        int money;
        String name;

        public Account() {
        }

        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
}

package com.bigdata.demo04;

import java.util.ArrayList;
import java.util.List;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 20:59
 */
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->
            {
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }
            ).start();


        }

        System.out.println(list.size());
    }
}


锁住变化的量

6.6 CopyOnWriteArrayList
package com.bigdata.demo04;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo04
 * Version: 1.0
 * 测试JUC 安全类型的集合
 * @author qingzhi.wu 2020/7/15 21:24
 */
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list =new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(list.size());
    }
}

6.7 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有两个以上对象的锁时,就可能发生死锁的问题

package com.bigdata.demo05;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo05
 * Version: 1.0
 * 死锁: 多个线程互相抱着对方需要的资源,然后形成僵持
 * @author qingzhi.wu 2020/7/15 21:34
 */
public class DeadLock {
    public static void main(String[] args) {
        Mackeup g1 = new Mackeup(0,"灰姑凉");
        Mackeup g2 = new Mackeup(1,"白雪公主");
        g1.start();
        g2.start();

    }
}

//口红
class Lipstick{

}
//镜子
class Mirror{

}
class Mackeup extends Thread{
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//使用化妆品的人

    public Mackeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        //化妆
        makeup()
    }
    private void makeup() throws InterruptedException {
        if (choice==0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(this.girlName +"获得口红的锁");
                Thread.sleep(1000);

                synchronized (mirror){
                    System.out.println(this.girlName +"获得镜子的锁");

                }
            }
        }else {
            synchronized (mirror){//获得口红的锁
                System.out.println(this.girlName +"获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlName +"获得口红的锁");
                }
            }
        }
    }
}

package com.bigdata.demo05;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo05
 * Version: 1.0
 * 死锁: 多个线程互相抱着对方需要的资源,然后形成僵持
 * @author qingzhi.wu 2020/7/15 21:34
 */
public class DeadLock {
    public static void main(String[] args) {
        Mackeup g1 = new Mackeup(0,"灰姑凉");
        Mackeup g2 = new Mackeup(1,"白雪公主");
        g1.start();
        g2.start();

    }
}

//口红
class Lipstick{

}
//镜子
class Mirror{

}
class Mackeup extends Thread{
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String girlName;//使用化妆品的人

    public Mackeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void makeup() throws InterruptedException {
        if (choice==0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(this.girlName +"获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror){
                System.out.println(this.girlName +"获得镜子的锁");

            }
        }else {
            synchronized (mirror){//获得口红的锁
                System.out.println(this.girlName +"获得镜子的锁");
                Thread.sleep(2000);

            }
            synchronized (lipstick){
                System.out.println(this.girlName +"获得口红的锁");
            }
        }
    }
}

产生死锁的四个必要条件:

互斥条件: 一个资源每次只能被一个进程使用
请求与保持条件: 一个进程因请求资源而阻塞时,对获得的资源保持不放
不剥夺条件: 进程已获得资源,在未使用完成之前,不能强行剥夺
循环等待条件: 若干进程之间形成一种头尾相连的循环等待资源的关系.
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

6.8 Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制—通过显示定义同步锁对对象来实现同步.同步锁使用Lock对象充当
Lock接口是控制多个线程对共享资源进行访问的工具.锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
package com.bigdata.demo05;

import java.util.concurrent.locks.ReentrantLock;

/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo05
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/15 21:55
 */
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements Runnable {
    int ticketNums = 10;

    //定义Lock 锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticketNums > 0) {
//                    try {
//                        Thread.sleep(1000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }


        }

    }
}

synchronized 与 Lock的对比

Lock是显示锁(手动开启和关闭锁,别忘记关闭锁) synchronized 是隐士锁,出了作用域自动释放
Lock只有代码块锁,synchronized 有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
优先使用顺序 Lock ->同步代码块 (已经进入了方法体,分配了相应的资源) -> 同步方法(在方法体之外)
7. 线程通信问题
7.1 生产者消费者模式
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间互为依赖,互为条件

对于生产者,没有生产产品之前要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
在生产者消费者问题中,仅有synchronized 是不够的
synchronized 可阻止并发更新同一个共享资源,实现了同步
synchronized 不能用来实现不同县城之间的消息传递(通信)
7.2 管程法
生产者: 负责生产数据的模块
消费者: 负责处理数据逇模块
缓冲区: 消费者不能直接使用生产者的数据,他们之间有个缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

package com.bigdata.demo06;


/**
 * Copyright (c) 2019 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo06
 * Version: 1.0
 * 测试: 生产者消费者模型 --> 利用缓冲区解决 管程法
 *
 * @author qingzhi.wu 2020/7/15 22:19
 */
public class TestPC {
    public static void main(String[] args) {
        SyncContainer container = new SyncContainer();
        new Productor(container).start();
        new Consumer(container).start();
    }

}

class Productor extends Thread {
    SyncContainer container;

    public Productor(SyncContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        int i = 0;
        while(true){
            System.out.println("生产了" + i++ + "只鸡");
            container.push(new Chicken(i));
        }


    }
}

class Consumer extends Thread {
    SyncContainer container;

    public Consumer(SyncContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (; ; ) {
            System.out.println("消费了-->" + container.pop().id);
        }
    }
}

class Chicken {
    public int id;

    public Chicken(int id) {
        this.id = id;
    }
}

class SyncContainer {
    //需要一个容器大小

    public Chicken[] chickens = new Chicken[10];
    //容器计数器
    int count = 0;


    //生产者放入产品
    public synchronized void push(Chicken chicken) {
        //如果容器满了
        if (count == chickens.length) {
          //  通知消费者消费,生产等待
            try {
                this.notify();
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
        }
        //如果没有满,我们就需要丢入产品

        chickens[count++] = chicken;
        this.notify();
    }

    //消费者消费产品
    public synchronized Chicken pop() {
        if (count == 0) {
            //通知生产者生产,消费者等待
            try {
                this.notify();
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        //Chicken chickens = null;
        count--;
        Chicken chicken = chickens[count];
        return chicken;
    }
}

7.2 信号灯法
package com.bigdata.demo06;

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo06
 * Version: 1.0
 * 信号灯法
 *
 * @author qingzhi.wu 2020/7/16 10:31
 */
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();


    }

}
//生产者 演员
class Player extends Thread{
    TV TV ;

    public Player(TV TV) {
        this.TV = TV;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i %2 == 0){
                this.TV.play("快乐大本营,播放中");
            }else {
                this.TV.play("抖音,记录美好生活");

            }
        }
    }
}
//消费者 观众
class Watcher extends Thread{
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
          this.tv.watch();
        }
    }
}

//产品   节目

class TV {
    String voice;
    boolean flag = true;

    //表演
    public synchronized void play(String voice){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:" +voice);

        //通知观众观看
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }

    public synchronized void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" +voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

8.0 线程池
经常创建和销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大

思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建消费,实现重复利用,类似生活中的公共交通工具

好处:

提高响应速度 减少了创建新线程的时间
降低资源消耗 重复利用线程池中线程,不需要每次都创建
便于线程管理
package com.bigdata.demo06;

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

/**
 * Copyright (c) 2020 bigdata ALL Rights Reserved
 * Project: learning
 * Package: com.bigdata.demo06
 * Version: 1.0
 *
 * @author qingzhi.wu 2020/7/16 11:49
 */
public class TestPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new Thread1());
        service.execute(new Thread1());

        service.execute(new Thread1());

        service.execute(new Thread1());

        service.shutdown();
    }
}

class Thread1 implements  Runnable{

    @Override
    public void run() {

            System.out.println(Thread.currentThread().getName() );

    }
}

原文链接:https://blog.csdn.net/weixin_43704599/article/details/107379994

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程基础 的相关文章

随机推荐

  • Java通过反射运用自定义注解案例

    Java和大数据系列 注 大家觉得博客好的话 别忘了点赞收藏呀 本人每周都会更新关于人工智能和大数据相关的内容 内容多为原创 Python Java Scala SQL 代码 CV NLP 推荐系统等 Spark Flink Kafka H
  • nvcc fatal : A single input file is required for a non-link phase when an outputfile is specified

    nvcc fatal A single input file is required for a non link phase when an outputfile is specified 错误原因 我想用VS编译colmap3 8 于是
  • Android无埋点数据收集SDK关键技术解析

    前言 本文基于网易乐得无埋点数据收集SDK 无埋点数据收集SDK用于向大数据平台提供全量 完整 准确的客户端数据 Android端无埋点数据收集SDK实现中涉及到比较关键的技术点有 用字节码插桩的方式实现Android端的AOP Hook
  • 自学python记录001-使用PyCharm创建项目

    启动PyCharm后如下所示 点击新建项目 选择项目存放路径 可以勾选创建main py文件 点击创建 创建完成后可以看到main py里面有一些提示的代码 比如说Shift F10可以运行项目 Ctrl F8可以直接添加断点 双击Shif
  • 强烈推荐的机器学习,深度学习课程以及python库

    本文知乎链接 强烈推荐的机器学习 深度学习课程以及python库 本着两条原则发一波车 1 不建议报辅导班 不是因为我们不应该为学习知识付费 而是因为有更好的资源 而这些资源恰好免费 报辅导班学习浪费钱倒是次要的 主要是时间有限 所以我们要
  • 程序分析技术理解(一)

    1 基本块 Basic Block 和流图 flow graph 将一段程序划分为基本块 Basic Block BB 每个基本块满足以下条件 a 控制流只能从基本块的第一个指令进入 b 除了基本块的最后一条指令 控制流在离开基本块前不会停
  • 移动端代码质量管理与安全检测评估

    在前面的文章中已经详细介绍过Jenkins Sonarqube的安装 配置及使用 对于Web端的代码质量管理通常相对容易 Jenkins配套Sonarqube很方便就能搞定 但是对于移动端来说 尤其iOS 集成和使用的复杂性会大幅提高 目前
  • PostgreSQL系列1:PostgreSQL 10.23 离线安装

    1 安装前准备 1 1创建用户 useradd postgres passwd postgres 1 2创建数据目录和日志目录 mkdir p data db pg data mkdir p data db pg logs chown R
  • 字符串分割(split),将字符串按照指定字符进行分割。split(String regex)和split(String regex, int limit)

    一 split String regex 字符串分割 将字符串按照指定字符进行分割 返回的是一个字符串数组 public String split String regex return split regex 0 原理 参数名称是rege
  • 【狂神说Java】HTML快速入门

    目录 1 初识HTML 2 网页基本信息 3 网页基本标签 4 图像标签 5 超链接标签应用 6 行内元素和块元素 7 列表标签 8 表格标签 9 媒体元素 10 页面结构分析 11 iframe内联框架 12 初始表单post和get提交
  • 两台linux文件拷贝

    scp就是secure copy 是用来进行远程文件拷贝的 数据传输使用ssh1 并且和ssh1使用相同的认证方式 提供相同的安全保证 与rcp不同的是 scp会要求你输入密码如果需要的话 最简单的应用如下 scp 本地用户名 IP地址 文
  • angular的ElementRef和Renderer2

    Angular ElementRef 简介 angular angular 2018 09 22 Angular 的口号是 一套框架 多种平台 同时适用手机与桌面 One framework Mobile desktop 即 Angular
  • opencv3.3.0在线读取网络图片图像资源

    说明 上一篇博客中描写了imread 的用法 请参见 http blog csdn net m0 37606112 article details 78524234 这一篇来描述cv2 videoCapture 的用法 照例打开opencv
  • vue插槽的基本使用

    1 什么是插槽 插槽 Slot 是 vue 为组件的封装者提供的能力 允许开发者在封装组件时 把不确定的 希望由用户指定的部分定义为插槽 2 体验插槽的基础用法 在封装组件时 可以通过元素定义插槽 从而为用户预留内容占位符 示例代码如下 如
  • 深度学习 - 模型的优化和过拟合问题

    优化函数 学习速率与反向传播算法 学习速率 learning rate 梯度就是表明损失函数相对参数的变化率 对梯度进行缩放的参数被称为学习速率 learning rate 学习速率是一种超参数或对模型的一种手工可配置的设置 需要为它指定正
  • 《Graph Neural Networks Foundations,Frontiers and Applications》第一部分第二章第2.3.1.1节翻译和解读

    书名 Graph Neural Networks Foundations Frontiers and Applications 图神经网络的基础 前沿和应用 出版社 Springer Berlin Heidelberg 作者 Lingfei
  • Python 函数的定义

    视频版教程 Python3零基础7天入门实战视频教程 函数 函数是执行特定任务的一段代码 程序通过将一段代码定义成函数 并为该函数指定一个函数名 这样即可在需要的时候多次调用这段代码 比如我们前面学到的range 函数 就是系统内置的函数
  • Appium自动化框架从0到1之 基类的封装

    这里只封装了4个基类 其他的大家可以自己丰富 直接上代码 baseView py coding utf 8 auth carl DJ time 2020 7 9 class BaseView object 封装一些基类 def init s
  • wazuh安装,单机部署3.13

    Wazuh涉及两个主要组件的安装 Wazuh服务器和Elastic Stack 此外 Wazuh agent需要部署到受监视的主机上 Wazuh server 运行Wazuh管理器和API 它从已部署的代理收集和分析数据 Elastic S
  • Java多线程基础

    原文链接 https blog csdn net weixin 43704599 article details 107379994 1 线程简介 1 1 多任务 现实中太多这样同时做多件事情的例子了 看起来是多个任务都在做 其实本质上我们