JavaSE——多线程详细

news2024/11/19 9:29:26

目录

一、多线程

1.1 基本介绍

1.2 进程和线程的关系

1.3 多线程并发概念

二、实现线程的方式

2.1 继承Thread类

2.2 实现java.lang.Runnable接口

2.3 匿名类

2.4  实现Callable接口(JDK8新特性)

2.5  run和start的区别

2.6 线程声明周期

三、线程中易错及常用的方法

3.1 获取当前线程对象、修改线程的名字、获取线程对象的名字

3.2 线程 sleep方法

3.2.1 面试题

3.2.2 终止睡眠

3.2.3 强行终止线程

3.2.4 合理终止线程

 3.3 线程调度(了解)

3.3.1 获取线程优先级、设置线程优先级、合并线程

四、线程安全(重要)

4.1 对synchronized理解

4.1.1  哪些变量有线程安全问题?

4.1.2 扩大同步范围

4.1.3 synchronized出现在实例方法上

4.1.4 局部变量使用StringBuffer(线程安全)还是StringBuilder(不安全)

4.1.5 synchronized总结:3种用法

4.1.6 面试题1

4.1.7 面试题2

4.1.8 面试题3

4.1.9 面试题4

4.2 死锁

4.2.1 怎么解决死锁

五、守护线程

 5.1 实现守护线程

 5.2 定时器(比较重要,但是也很少用,因为框架支持定时任务)

六、生产者消费模式(wait和notify)

6.1 wait和notify作用

6.2 生产者消费者模式


一、多线程

1.1 基本介绍

 进程:一个应用程序,一个进程就是一个软件

 线程:一个进程中的执行场景/执行单元

 一个进程可以启动多个线程

对于java程序来说,当在DOS命令窗口中输入java HelloWorld回车之后,会先启动JVM,而JVM就是一个进程,JVM再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾,也就是说java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程

1.2 进程和线程的关系

 我们拿公司举例:

     阿里巴巴:进程

         马云:阿里巴巴的一个线程

         童文红:阿里巴巴的一个线程

     京东:进程

          强东:京东的线程

          妹妹:京东的线程

进程可以看做公司,线程可以看做公司中的某个员工

 十个线程十个栈,每个栈不会相互干扰,各自执行各自的,这就是多线程并发 

主栈空了,其他的栈可能还在执行

注意:进程A和进程B的内存独立不共享,即阿里巴巴和京东的资源不共享

           在Java语言中线程A和线程B的堆内存和方法区内存共享,但是栈内存独立

     

多线程机制的目的:提高程序的运行效率

1.3 多线程并发概念

   t1线程执行t1的,t2线程执行t2的,两者互不影响

  

   那单核的CPU(相当于一个大脑)能实现多线程并发么?

         对于多核肯定没问题的。单核的不能做到真正的多线程并发,但是能做到一个多线程并发的感觉。因为CPU的处理速度很快,多个线程之间频繁切换执行,给人一种多线程并发的错觉

二、实现线程的方式

2.1 继承Thread类

编写一个类,继承java.lang.Thread类,重写run方法

public class ThreadTest01 {
    public static void main(String[] args) {
//        这里的代码属于主线程

//        新建分支线程独享
        MyThread myThread = new MyThread();
//        启动线程,start()方法的作用就是启动一个分支线程,在JVM中开辟一个新的空间,这段代码瞬间就结束
//        只要新的空间开出来,代码就执行完毕。启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部
        myThread.start();
//        这里的代码还是主线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--》"+i);
        }
    }
}
public class MyThread extends Thread{

    @Override
    public void run() {
//       编写程序,这段程序运行在分支栈中
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程--》"+i);
        }
    }
}

输出结果有先有后,有多有少

 JVM图示

2.2 实现java.lang.Runnable接口

  下面这种用的多些,因为一个类可实现多个接口但是只能实现一个类

public class ThreadTest01 {
    public static void main(String[] args) {
//        这里的代码属于主线程
//      将可运行的对象封装成一个线程对象
        Thread t = new Thread(new MyThread());
        t.start();

//        这里的代码还是主线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--》"+i);
        }
    }
}
//仅仅是一个线程类,是一个可运行的类,此时还不是个线程
public class MyThread implements Runnable{

    @Override
    public void run() {
//       编写程序,这段程序运行在分支栈中
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程--》"+i);
        }
    }
}

2.3 匿名类

public class ThreadTest01 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("分支线程--》"+i);
                }
            }
        });
        t.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--》"+i);
        }
    }
}

 2.4  实现Callable接口(JDK8新特性)

这种方式实现的线程可以回去线程的返回值

 

2.5  run和start的区别

start()方法的作用就是启动一个分支线程,在JVM中开辟一个新的空间,这段代码瞬间就结束。只要新的空间开出来,代码就执行完毕。启动成功的线程会自动调用run方法(不需要我们主动调用),并且run方法在分支栈的栈底部

       run()方法只是一个方法,如果是t.run()调用的话并不会出现新的线程,仅仅是对象调用其方法,不会启动分线程

2.6 线程声明周期

面试挺重要的

 

三、线程中易错及常用的方法

3.1 获取当前线程对象、修改线程的名字、获取线程对象的名字

怎么获取当前线程对象?

  static Thread currentThread

public class ThreadTest01 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        Thread t1 = Thread.currentThread();
        System.out.println(t1);
        System.out.println(t.currentThread());
    }
}

为什么我们用t线程调用的时候输出的结果和t1输出的一个样呢?

     因为这个方法是静态方法,是在主线程中调用的,所以输出的线程是主线程

     简单地说  static Thread currentThread这段代码出现在哪里,就获取的哪个线程对象

  线程对象.setName("线程名字");

  线程对象.getName();

  线程没有设置名字的时候就是Thread-0,Thread-1.....

public class ThreadTest01 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        t.setName("ttttt");

        System.out.println(t.getName());

        t.start();
        
    }
}

3.2 线程 sleep方法

  此方法能使线程进入阻塞状态,可以做到间隔特定的时间执行特定的程序

  静态方法,参数是毫秒,让当前线程进入阻塞状态,放弃占有CPU时间片,让给其他线程使用

  简单的说,在哪个线程中使用,哪个线程就会进行休眠状态

public class ThreadTest01 {
    public static void main(String[] args) {
        System.out.println("休眠前");
        try {
            //休眠五秒
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("休眠后");
    }
}

    3.2.1 面试题

下面这段代码,不会让t线程进入休眠状态,反而是让当前线程进入休眠状态

让哪个线程进入休眠状态,取决于在哪个线程进行使用

  3.2.2 终止睡眠

 注意:run方法中的异常不能抛出,只能try,因为run方法在父类中没有抛出异常,子类不能比父类抛出更多的异常

 不是中断线程的执行,而是中断线程的睡眠

public class ThreadTest01 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        t.setName("t");
        t.start();

//      希望五秒后t线程醒来
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//      干扰,这段代码会让t线程中睡眠出现异常,然后进入catch语句块,然后整个try...catch结束了
        t.interrupt();
    }
}
//仅仅是一个线程类,是一个可运行的类,此时还不是个线程
public class MyThread implements Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(1000*500000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//       编写程序,这段程序运行在分支栈中
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程--》"+i);
        }
    }
}

干扰,这段代码会让t线程中睡眠出现异常,然后进入catch语句块,然后整个try...catch结束了
        t.interrupt();

3.2.3 强行终止线程

不建议使用,已经过时了

这个方法容易丢失数 据,这是一个很坏的结果,非常大的缺点

3.2.4 合理终止线程

public class ThreadTest01 {
    public static void main(String[] args) {
        MyThread r = new MyThread();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

//      希望五秒后t线程醒来
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//      干扰,这段代码会让t线程中睡眠出现异常,然后进入catch语句块,然后整个try...catch结束了
        r.run=false;
    }
}
//仅仅是一个线程类,是一个可运行的类,此时还不是个线程
public class MyThread implements Runnable{
//  打标记
   public boolean run = true;
    @Override
    public void run() {
        if(run){
            try {
                Thread.sleep(1000*500000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//       编写程序,这段程序运行在分支栈中
            for (int i = 0; i < 1000; i++) {
                System.out.println("分支线程--》"+i);
            }
        }else {
//            终止
            return;
        }

    }
}

3.3 线程调度(了解)

  • 抢占式调度模型

哪个线程的优先级别高,抢到CPU时间片的概率高一些。java便采用的是此模型

  • 均分式调度模型

平均分配CPU时间片,每个线程占有CPU时间片长度一样。平均分配,一切平等

3.3.1 获取线程优先级、设置线程优先级、合并线程

四、线程安全(重要)

多线程并发条件下数据的安全问题

什么时候会存在安全问题?

     多线程并发、有共享数据、共享数据有修改的行为

怎么解决线程安全问题?

    线程排队执行,不能并发,使用线程同步机制(牺牲一部分效率)

  • 同步编程模型

   两个线程各自执行各自的,谁也不等谁,其实就是多线程并发

  • 异步编程模型

  两个线程,t1执行的时候,必须等待t2线程执行,效率较低

4.1 对synchronized理解

下面这个不一定写this,只要是共享对象就行

对synchronized解决线程安全问题的理解:(仔细阅读)

       只要进入了synchronized就进入了线程同步模式,加入t1线程过来,遇到synchronized之后就会找后面括号里面的对象锁,每个java对象都有一把锁。此时synchronized便把这把锁给占有了,然后执行里面的代码。假如在这段代码的执行过程中,t2线程也遇到了这个synchronized,也会占用这个对象锁,但是很可惜,这把锁已经被t1线程给占用了,所以t2线程只能等待。当t1线程执行完这段代码后,t1线程会归还这把锁。归还之后,在等待的t2线程便不必等待,就拿到这个对象锁,再去执行代码。这样就达到了线程排队执行。这个共享对象一定要选好,这个共享对象一定是你需要排队执行的这些线程对象所共享的。

    类似厕所的茅坑,这个茅坑一个人用完了另一个人才能用。

    java语言中,任何一个独享都有一把锁,这把锁本质是一个标记,只是叫做锁。

    100个对象,100把锁。1个对象1个锁

4.1.1  哪些变量有线程安全问题?

     实例变量:堆中

     静态变量:方法区中

     局部变量: 栈中

     以上变量中,局部变量永远不会存在线程安全问题,因为不会共享。堆和方法区都只有一个,堆和方法区都是多线程共享的,所以可能存在线程安全问题。

   常量也没有线程安全问题,常量不可修改

4.1.2 扩大同步范围

      同步代码块越小,效率越大。

 

 4.1.3 synchronized出现在实例方法上

优点:代码写的少,简洁了。  如果共享的对象就是this,并且同步的代码块是整个方法体,就用这种方式。

 4.1.4 局部变量使用StringBuffer(线程安全)还是StringBuilder(不安全)

      因为局部变量没有线程安全问题,所以选择StringBuilder,效率高,不会走锁池

 4.1.5 synchronized总结:3种用法

   4.1.6 面试题1

问:doOther方法的执行需不需要等待doSome方法的结束?

 

 

t1线程和t2线程是同一个。此时doOther方法的执行并不需要等doSome方法的结束,执行doOther的时候没有锁

4.1.7 面试题2

问:doOther方法的执行需不需要等待doSome方法的结束?

此时是需要的。doSome一直占用着一把锁,doOther方法无法执行,锁被占用了

4.1.8 面试题3

问:doOther方法的执行需不需要等待doSome方法的结束?

不用要等待,因为对象mc1和对象mc2不是共享对象,所以是两把锁

4.1.9 面试题4

问:doOther方法的执行需不需要等待doSome方法的结束?

 需要等待,是类锁,出现在静态方法上,虽然new了两次但是同一个类。类锁只有一把

这种锁叫做排他锁,t1线程拿到后,其他线程拿不到

4.2 死锁

让程序停止不前,也不出什么异常但是就是不动了,很诡异,程序僵持住了。

这种错误很难调试。

 

死锁代码

 

 

 

4.2.1 怎么解决死锁

  synchronized在开发中不要嵌套使用

   我们在实际开发中只有在不得已的情况下才使用此关键字。此关键字会导致用户体验不好,系统用户的吞吐量降低,用户体验差。在不得已的情况下再选择线程同步机制。

第一种方案:尽量使用局部变量代替实例变量和静态变量

第二种方案: 如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应一个对象),对象不共享便没有线程安全问题

第三种方案:synchronized线程同步机制

五、守护线程

        垃圾回收器就是守护线程(后台线程,默默的在后面),主线程是用户线程,我们自己创建的线程也是用户线程

守护线程的特点:死循环,所有的用户线程只要结束,守护线程自动结束

 

守护线程一般用在系统数据自动备份,我们一般将定时器设置为守护线程

所有的用户线程结束,自动退出,也不需要数据备份

 5.1 实现守护线程

 

 

 5.2 定时器(比较重要,但是也很少用,因为框架支持定时任务)

间隔特定时间执行特定的程序。比如每天的数据备份、每天的流水分总计

public class Test {
    public static void main(String[] args) throws ParseException {
//        创建定时器对象
        Timer timer = new Timer();

//      指定定时任务 timer.schedule(定时任务,第一次执行时间,间隔多久);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2022-12-12 09:30:00");
//      间隔十秒
        timer.schedule(new LogTimerTask(),firstTime,1000*10);

    }
}
//编写定时任务类
class LogTimerTask extends TimerTask {

    @Override
    public void run() {
//       编写需要执行的任务
        System.out.println("执行任务");


    }
}

 

六、生产者消费模式(wait和notify)

wait和notify是Object类的方法。并不是线程对象调用的

6.1 wait和notify作用

wait方法和notify方法建立在线程同步的基础上

   wait方法,让正在对象是哪个获取地t线程进入等待状态,并且释放掉t线程之前占有的锁

 

6.2 生产者消费者模式

 

模拟生产者和消费者的代码

//生产线程负责生产,消费线程负责消费
public class Test {
    public static void main(String[] args) throws ParseException {
//      模拟仓库   模拟生产一个消费一个
        List list = new ArrayList<>();
//        生产者线程
        Thread t1 = new Thread(new Producer(list));
//        消费者线程
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start();
    }
}

class Producer implements  Runnable{
   private List list;
    @Override
    public void run() {
//  生产
        while (true){
//          加锁
            synchronized (list){
                if(list.size()>0){
//                  仓库满了表示生产够了,不再生产
                    try {
//                      当前线程进入等待状态,并释放锁。如果不释放这个锁的话,消费线程无法操作
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
//             程序运行到这里说明仓库是空的,可以生产
                Object object = new Object();
                list.add(object);
                System.out.println(Thread.currentThread().getName()+"---->"+object);
//              唤醒消费者消费
                list.notify();

            }

        }
    }

    public Producer(List list) {
        this.list = list;
    }
}
class Consumer implements  Runnable{
    private List list;
    @Override
    public void run() {
//        消费
        while (true){
            synchronized (list){
                if(list.size() ==0){
//                    仓库空了,等待
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
//               消费,运行到这里说明仓库中有剩余
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName()+"---->"+obj);
//              唤醒生产者生产  这个地方唤醒所有也没问题,因为唤醒不会释放锁
                list.notify();
            }
        }

    }

    public Consumer(List list) {
        this.list = list;
    }
}


交替效果

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

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

相关文章

双非本科怎么了,照样拿到阿里 offer! 分享阿里技术四面 + 交叉面 +HR 面难忘经历

说一下 java 类加载器的工作机制&#xff1f;类加载在哪个区域进行的&#xff1f; 说一下 java 的线程模型&#xff1f; violate 了解吗&#xff1f;它的原理是什么&#xff1f;violate 是线程安全的吗&#xff1f; 保证线程安全的解决方法有哪些&#xff1f;说一说读写锁吧…

前端高频手写面试题总结

实现字符串的repeat方法 输入字符串s&#xff0c;以及其重复的次数&#xff0c;输出重复的结果&#xff0c;例如输入abc&#xff0c;2&#xff0c;输出abcabc。 function repeat(s, n) {return (new Array(n 1)).join(s); }递归&#xff1a; function repeat(s, n) {return…

通过 JFR 与日志深入探索 JVM - TLAB 原理详解

什么是 TLAB&#xff1f; TLAB&#xff08;Thread Local Allocation Buffer&#xff09;线程本地分配缓存区&#xff0c;这是一个线程专用的内存分配区域。既然是一个内存分配区域&#xff0c;我们就先要搞清楚 Java 内存大概是如何分配的。 我们一般认为 Java 中 new 的对象…

模板模式

文章目录思考模板模式1.模板模式的本质2.何时选用模板模式3.优缺点4.模板方法的结构5.实现思考模板模式 模板模式其实就是抽离共用方法到抽象类中&#xff0c;然后再规定其具体实现步骤 1.模板模式的本质 模板方法模式的本质:固定算法骨架。 模板方法模式主要是通过制定模板&am…

系统集成企业需具备哪些证书?

IT信息化企业&#xff0c;系统集成企业需要做的资质证书有哪些&#xff1f;经常遇到有新成立的系统集成商问智达鑫业小编&#xff0c;该申请哪些企业资质&#xff0c;接下来了小编整理下目前市场上使用频率比较高的一些资质证书&#xff0c;大家可以参考下。 信息系统建设和服务…

A-Level考试常见问题综合解答

关于A Level的Q&A 问&#xff1a;参加A Level的考试与其他考试相比有什么优势吗&#xff1f; 答&#xff1a;A Level考试的门数相较其他国际课程更少&#xff0c;学生有更多的时间花费在每门课上取得更好的GPA和最终成绩。问&#xff1a;就读的学校就直接提供A Level课程&a…

jmeter断言

jmeter断言常用的有响应断言和json断言&#xff1b; 常用的响应断言&#xff1a; 1.字符串&#xff1a;如果响应中包含了指定的字符串&#xff0c;判断为成功&#xff0c;不支持正则表达式&#xff1b;如下图&#xff1a; 2.包括&#xff1a;如果响应中包含了指定的字符串&…

mac清空废纸篓怎么恢复?

众所周知&#xff0c;电脑只要在运行都会产生一些临时文件或者文档&#xff0c;而这些文件会存放在电脑的存储空间里&#xff0c;方便我们后续的使用。当Mac中存储的文件过多时&#xff0c;就会影响到我们的正常使用&#xff0c;只有通过清理电脑文件&#xff0c;来释放更多的存…

【JavaWeb开发-Servlet】拾起海中的漂流瓶超强版

目录 原版&#xff1a; 一、思路&#xff1a; 二、实现&#xff1a; 三、资源分享 四、部署服务器时记得修改文件路径 原版&#xff1a; 【JavaWeb开发-Servlet】拾起海中的漂流瓶增强版_代码骑士的博客-CSDN博客【代码】【JavaWeb开发-Servlet】拾起海中的漂流瓶增强版…

SMART PLC运动超驰功能编程应用(含V2.7版本固件下载)

什么是运动控制超驰功能,运动超驰功能如何开启,请参看下面的导图部分: 下面一步步教大家如何更新CPU固件版本。 S7-200 SMART PLC自定义脉冲控制功能块相关详细组态设置,请参看下面的博客。链接如下: S7-200 SMART PLC自定义脉冲轴控功能块AxisControl_FB(梯形图)_RXX…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:MapView MaptrimView

本文简述如何在Smobiler中使用MapView和MaptrimView。 Mapview MapView 地图插件&#xff0c;可用于显示指定地点地图&#xff0c;显示轨迹等。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入MapView和Button&#xff0c;MapView.Size设置&#xff08;300,300&#xf…

Spring Batch 批处理入门案例解析

引言 书接上篇 Spring Batch 批处理入门案例 &#xff0c;上篇带小伙伴们写了一个Spring Batch 入门案例&#xff0c;里面有哪些注意要点呢&#xff1f;本篇一起来分析分析~ 案例解析 整个入门案例核心点有5个&#xff0c;一一来讲解一下 EnableBatchProcessing 批处理启动…

AD8226组成的高精度放大电路之一

工业设备中常常需要用到高速、高精度的模拟前端方案,而其中控制系统中的信号电平通常为以下几类之一:单端电流(4 mA 至 20 mA)、单端差分电压(0 V 至 5V、0 V 至10 V、5 V、10 V)或者来自热电偶或称重传感器等传感器的小信号输入。大共模电压摆幅也非常典型,尤其是小信号…

Spring Cloud Zuul过滤器介绍及使用(传递数据、拦截请求和异常处理)

在教程《Zuul网关的介绍及使用》中一开始就介绍过&#xff0c;Zuul 可以实现很多高级的功能&#xff0c;比如限流、认证等。想要实现这些功能&#xff0c;必须要基于 Zuul 给我们提供的核心组件“过滤器”。下面我们一起来了解一下 Zuul 的过滤器。 过滤器类型 Zuul 中的过滤…

问题来了,拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗?

今天&#xff0c;聊一个有趣的问题&#xff1a;拔掉网线几秒&#xff0c;再插回去&#xff0c;原本的 TCP 连接还存在吗&#xff1f; 可能有的同学会说&#xff0c;网线都被拔掉了&#xff0c;那说明物理层被断开了&#xff0c;那在上层的传输层理应也会断开&#xff0c;所以原…

MarkDown 项目中如何引入开源MarkDown? 史上最简单教程

目录 一、少不了的东西 editor.md ① 下载链接 ② 将其引入到自己的项目中 引入依赖 二、代码部分 一些小细节 1. 编辑页 2. 展示页 一、少不了的东西 如果想要在一个页面中使用MarkDown &#xff0c;那么你首先就要引入MarkDown editor.md ① 下载链接 GitHub下…

Flutter和Rust如何优雅的交互

前言 文章的图片链接都是在github上&#xff0c;可能需要...你懂得&#xff1b;本文含有大量关键步骤配置图片&#xff0c;强烈建议在合适环境下阅读 Flutter直接调用C层还是蛮有魅力&#xff0c;想想你练习C&#xff0c;然后直接能用flutter在上层展示出效果&#xff0c;是不…

【中级ECharts技术】transform进行数据转换和dataZoom在项目中的使用(可视化非常的强劲)

transform 进行数据转换 数据转换是这样一个公式:outData=f(inputData)。F是转换方法,例如filter、sort、region、boxplot、cluster、aggregate(todo)等。有了数据转换功能,我们至少可以做到以下几点: 将数据分成多个部分,并在不同的饼图中显示它们。 执行一些数据统计…

C++ 注释

&#x1f4d2;博客主页&#xff1a; ​​开心档博客主页​​ &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐留言&#x1f4dd; &#x1f4cc;本文由开心档原创&#xff01; &#x1f4c6;51CTO首发时间&#xff1a;&#x1f334;2022年12月12日&#x1f334; ✉…

Httpd服务进阶知识-HTTP协议详解

一.WEB开发概述 1>.C/S编程 CS即客户端、服务器编程。 客户端、服务端之间需要使用Socket&#xff0c;约定协议、版本(往往使用的协议是TCP或者UDP)&#xff0c;指定地址和端口&#xff0c;就可以通信了。客户端、服务端传输数据&#xff0c;数据可以有一定的格式&#xff…