JUC并发编程之:简单概述(一)

Wesley13
• 阅读 705

JUC并发编程之:简单概述(一)

##内容概述:
·进程和线程、并发和并行、同步和异步概念
·如何查看和关闭进程
·Java线程常用的类和方法

一、概念:

##一、进程与线程
1·进程
·程序由指令和数据组成,但这些指令要运行,数据要读写,就必须降脂灵加载至CPU,数据加载至内存。
在指令运行过程中还需要用到磁盘、网络等设备。进程就是同来加载指令、管理内存、管理IO的

·当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程

·进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(如:记事本、画图、浏览器等),
也有的程序只能启动一个实例进程(例如网易云音乐、360安全卫士等)

[windows下一个exe就是一个进程]

2·线程
·一个进程内可以分为一到多个线程.

·一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行

·Java中,线程作为最小调度单位,进程作为资源分配的最小单位。在windows中进程是不活动的,只是作为
线程的容器

eg:
举个不恰当的例子,比如我们打开了360安全卫士,这就相当于开启了一个进程。
在360卫士中,开启清理垃圾相当于一个线程、扫描病毒是另一个线程



##二、并发与并行
1、并发:Concurrent
·单核CPU下,线程实际还是串行执行的,操作系统有一个组件叫做任务调度器,将CPU的时间片分给不同的
线程使用(windows下的时间片最小约为15ms),只是有序CPU在线程间的切换非常快,我们感觉是同时运行
的。总结为一句话:微观串行,宏观并发

·一般会将这种线程轮流使用CPU的做法称为并发Concurrent

[并发其实是串行,多个线程间的快速切换执行]

2、并行:Parallel
·多核CPU下,每个核core都可以调度运行线程,这时线程是可以并行的

eg:
·一个家庭主妇做饭、打扫卫生、喂奶,她一个人轮流交替做这些事,这时就是并发
·一个家庭主妇和母亲和丈夫,一个专做饭、一个专打扫、一个专喂奶,互不干扰,同时进行,这时就是并行


##三、同步和异步
从方法调用的角度讲
·需要等待这一步的结果返回,才能继续向下运行就是同步
·不需要等待这一步的返回结果,就能直接继续向下运行就是异步

同步在多线程中另外一层意思:让多个线程步调一致


##四、效率比较
·单核CPU下,多线程不能实际提高程序的运行效率,可能还会降低效率(多线程间的切换),只是为
了能够在不同的任务之间切换,不同线程轮流使用CPU,不至于一个线程总占用CPU,别的线程没
法干活

·多核CPU可以并行跑多个线程,但能否提高程序运行效率还要分情况

·IO操作不占用CPU,只是我们一般拷贝文件使用的是 阻塞IO,这时相当于线程虽然不用CPU,但
需要一直等待IO结束,没有充分利用线程。
因此才有非阻塞IO和异步IO的优化

二、查看和关闭进程

##一、windows
tasklist 查看进程
taskkill 关闭线程

##二、linux
ps -ef 查看所有进程
grep 命令用于查找文件里符合条件的字符串

##三、Java
jps 查看java进程

三、Java线程常用类和方法

1、创建和运行线程

1.1、直接使用Thread

//创建线程对象
Thread t1 = new Thread(){
    @Override
    public void run() {
        //要执行的任务
    }
};
//启动线程
t1.start();

1.2、使用Runnable配合Thread

runnable方法将【线程】和【任务】分开


/**
 * Thread代表线程
 * Runnable代表可运行的任务(线程要执行的代码)
 **/

//创建任务对象
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        //要执行的任务
    }
};
//创建线程对象
Thread t2 = new Thread(r1);
//启动线程
t2.start();

1.3、使用Callable和FutureTask创建

·Callable提供了一个call()方法,call方法相比run()方法要强大:
>call()方法可以有返回值
>call()方法可以声明抛出异常

·Future接口里定义了5个公共方法来控制它关联的Callable任务
>boolean cancel(boolean mayInterruptIfRunning)
>V get()
>V get(long timeout,TimeUnit unit)
>boolean isDone()
>boolean isCancelled()

·FutureTask实现类既实现了Future接口,还实现了Runnable接口
因此FutureTask可以作为Thread类的target


//创建任务对象 : FutureTask实现了RunnableTask
//RunnableTask实现了Runnable和Future
FutureTask<Integer> f1 = new FutureTask<>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        log.debug("callable...");
        return 100;
    }
});

//创建线程对象:futureTask实现了runnable接口
//所以可以当做Thread的target
Thread t3 = new Thread(f1);
//开启线程
t3.start();

//接收执行结果 : 主线程阻塞
Integer res = f1.get();
log.debug("结果:{}",res);

2、线程运行原理

##栈与栈帧
·我们都知道JVM运行时数据区是由堆、栈、方法区等组成的。
其中每个线程启动后,虚拟机就会为期分配一块栈内存

·每个栈由多个栈帧Frame组成,对应着每次方法调用时所占的内存
·每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
·栈是先进后出的

##线程上下文: Thread Context Switch
·因为以下原因CPU不再执行当前的线程,从而执行另一个线程代码
>线程的CPU时间片用完
>垃圾回收Stop The World
>有更高优先级的线程需要运行
>线程自己调用了sleep yield wait join park synchronized lock等方法

·当Thread Context Swich发生时,需要有操作系统保存当前线程的状态,并恢复另一个
线程的状态,Java中对应的概念就是程序计数器Program Counter Register,它的作用
是记住下一条JVM指令的执行地址,是线程私有的

·状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等

·Context Switch频繁发生会影响性能

3、常见方法

方法名

功能

start()

启动一个新线程,在新的线程运行run方法中的代码

run()

新线程启动后调用用的方法

join()

等待线程运行结束

join(long n)

等待线程运行结束,最多等待n毫秒

getId()

获取线程ID(id唯一)

getName()

获取线程名

setName(String)

修改线程名

getPriority()

获取线程优先级

setPriority(int )

修改线程优先级(1~10)

getState()

获取线程状态

isInterrupted()

判断线程是否被打断(不会清除打断标记)

interrupt()

打断线程

interrupted()

static , 判断当前线程是否被打断(会清除打断标记)

isAlive()

判断线程是否存活(还没运行完毕)

currentThread()

static, 获取当前正在执行的线程

sleep(long)

static, 让当前执行的线程休眠n秒,休眠时让出cpu时间片给其他线程

yield()

static, 提示线程调度器让出当前线程对CPU的使用

stop()

停止线程【过时】

suspend()

挂起(暂停)线程运行【过时】

resume()

恢复线程运行【过时】

注意:

·getState(),Java中线程状态用6个ENUM表示:
>NEW 未启动状态
>RUNNABLE 可运行状态
>BLOCKED 阻塞状态
>WAITING 等待状态
>TIMED_WAITING 具有指定时间的等待状态
>TERMINATED 已终止线程的线程状态或线程已完成执行


·interrupt()
如果被打断线程正在sleep wait join会导致被打断的线程抛出InterruotedException
并清除打断标记;如果打断正在运行的线程,则会设置打断标记;park的线程被打断也会设置
打断标记

3.1、start和run

@Slf4j
public class StartAndRun {

    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                try {
                    sleep(3000);
                    log.debug("thread running....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
//        t1.run();
        log.debug("main running");
    }
}

结果:

##t1.start()
[main] DEBUG com.lee.juc.StartAndRun - main running
[Thread-0] DEBUG com.lee.juc.StartAndRun - thread running....

##t1.run()
[main] DEBUG com.lee.juc.StartAndRun - thread running....
[main] DEBUG com.lee.juc.StartAndRun - main running

结果分析:

·使用start()方法具有异步执行的效果
·使用run()方法是同步执行的效果

·使用start()方法,是真的启动了相应的线程
·使用run()方法并没有真的启动线程,而是由一个叫main的主线程去调用的run()方法

3.2、sleep和yield

##一、sleep(任务调度器不会分给其时间片)

·调用sleep会让当前线程从running状态进入timed waiting状态

·正在水面的线程,其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出interruptException

·睡眠结束后线程未必会立刻得到执行(可能处于线程阻塞状态BLOCKED)

·建议使用TimeUtil的sleep代替Thread的sleep来获得更好的可读性
eg: TimeUtil.SECONDS.sleep(1)


##二、yield(任务调度器会分给其时间片)

·调用yield会让当前线程从running状态进入runnable就绪状态,然后调度执行其他线程

·具体的实现依赖于操作系统的任务调度


##三、线程优先级setPriority  1~10  数字越大优先级越高

·线程优先级会提示调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它

·如果CPU比较忙,那么优先级高的线程会获得更多的时间片,但CPU闲时,优先级几乎没有作用

3.3、join

@Slf4j
public class JoinThread {
    static int r = 0;

    public static void main(String[] args) {
        test1();
    }

    public static void test1(){
        log.debug("开始");
        Thread t1 = new Thread(()->{
            log.debug("进入t1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("wake up");
            r=10;
            log.debug("退出t1");
        },"t1");

        t1.start();
        //t1.join() 等待线程运行结束
        log.debug("r的值为:{}",r);
        log.debug("结束");
    }
}

结果:

[main] DEBUG com.lee.juc.JoinThread - 开始
[main] DEBUG com.lee.juc.JoinThread - r的值为:0
[main] DEBUG com.lee.juc.JoinThread - 结束
[t1] DEBUG com.lee.juc.JoinThread - 进入t1
[t1] DEBUG com.lee.juc.JoinThread - wake up
[t1] DEBUG com.lee.juc.JoinThread - 退出t1

结果分析:

·因为主线程和t1线程并行执行,t1线程需要1s水面才能算出r=10
·而主线程一开始就要打印r的结果,所以只能打印出r=0

##解决方案
·用sleep行不行?为什么
sleep可以但效率不高,因为无法确定t1线程什么时候同time_wating状态到runnable状
态,且被任务调度器调度执行

·用join,加载t1.start()之后即可
t1.join()会将main线程等待至t1线程技术后再接着向下运行

3.4、interrupt

3.4.1、打断sleep wait join的线程
@Slf4j
public class SleepInterrupt {

    public static void main(String[] args){

        Thread t1 = new Thread(()->{
            log.debug("sleep start....");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("sleep end....");
        },"t1");

        t1.start();
        try {
            Thread.sleep(1000);//等待t1进入睡眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("interrupt");
        t1.interrupt();
        log.debug("main 打断标记:{}"+t1.isInterrupted());
    }
}

结果:

[t1] DEBUG com.lee.juc.SleepInterrupt - sleep start....
[main] DEBUG com.lee.juc.SleepInterrupt - interrupt
[main] DEBUG com.lee.juc.SleepInterrupt - main 打断标记:{}true
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.lee.juc.SleepInterrupt.lambda$main$0(SleepInterrupt.java:13)
    at java.lang.Thread.run(Thread.java:748)
[t1] DEBUG com.lee.juc.SleepInterrupt - sleep end....

结果分析:

interrupt打断sleep wait join的线程
·会抛出InterruptedException异常,
·打断标记会被清除
3.4.2、打断正常运行的线程
@Slf4j
public class NormalThreadInterrupt {

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            log.debug("sleep start....");
            while(true){
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted){
                    log.debug("t1 线程被打断了,且打断标记为:{}",interrupted);
                    break;
                }
            }
        },"t1");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("interrupt");
        t1.interrupt();
        log.debug("main 打断标记:{}"+t1.isInterrupted());
    }
}

结果:

[t1] DEBUG com.lee.juc.NormalThreadInterrupt - sleep start....
[main] DEBUG com.lee.juc.NormalThreadInterrupt - interrupt
[main] DEBUG com.lee.juc.NormalThreadInterrupt - main 打断标记:{}true
[t1] DEBUG com.lee.juc.NormalThreadInterrupt - t1 线程被打断了,且打断标记为:true

结果分析:

·正常循环的线程被打断后 不会清除打断标记
·正常循环的线程被打断后 不会主动退出
3.4.3、设计模式之:两阶段终止模式
##两阶段终止模式 Two Phrase Termination:

·在一个线程 T1 中如何 优雅地 终止线程 T2,这个优雅指的是给 T2一个处理后续操作的机会

##注意:
isInterrupted() 判断打断标记(不会清除打断标记)
static interrupted() 判断打断标记(会清除打断标记)

代码:

@Slf4j
public class TwoPhraseTermination {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        t1.start();

        Thread.sleep(3000);
        t1.stop();
    }
}

@Slf4j
class MyThread{
    private Thread monitorThread;

    //启动线程
    public void start(){
        monitorThread = new Thread(()->{
            while(true){
                Thread currentThread = Thread.currentThread();
                if(currentThread.isInterrupted()){
                    log.debug("t1线程被终止后,处理后续业务");
                    break;
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    log.debug("t1线程 休眠 被异常中断,重置 打断标记");
                    currentThread.interrupt();
                }
                log.debug("t1线程正在处理业务");
            }
        },"t1");

        monitorThread.start();
    }

    //终止线程
    public void stop(){
        monitorThread.interrupt();
    }


}

3.4.4、打断park线程

##interrupt打断park线程,不会清空打断状态


@Slf4j
public class ParkInterrupt {

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            log.debug("park.......");
            LockSupport.park();
            log.debug("unpark.......");
            log.debug("打断标记为:{}",Thread.currentThread().isInterrupted());
        },"t1");

        t1.start();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
}

4、主线程与守护线程

·默认情况下,Java进程需要等待所有线程都运行结束,才会结束。

##一、守护线程:

·有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码
没有执行完,也会【强制结束】

·垃圾回收器线程就是一种守护线程

·tomcat职工的acceptor和poller线程也是守护线程,所以tomcat接收到
shutdown命令后,不会等待它们处理完当前请求


//守护线程,当所有非守护线程都运行结束后,守护线程直接强制结束
@Slf4j
public class DaemonThread {

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while(true){
                log.debug("daemon thread start");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("daemon thread stop");
            }
        },"t1");

        //设置t1为守护线程
        t1.setDaemon(true);
        t1.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("main thread stop");
    }
}

5、线程的五种状态(操作系统层面)

JUC并发编程之:简单概述(一)

##从操作系统层面描述:

·初始状态:创建了线程对象,但未与操作系统线程关联   NEW

·可运行状态:指该线程已被创建(且与操作系统线程关联),可以由CPU调度执行 Runnable

·运行状态:值获取了CPU的时间片运行中的状态 Running
 [当CPU时间片用完,会从运行状态转至可运行状态,导致线程的上下文切换]

·阻塞状态        BLOCKED
 [如果调用了阻塞API,如BIO读写文件,这是该线程实际不会用到CPU,会导致线程上下文切换,进入阻塞状态]
 [等BIO操作完毕,会有操作系统唤醒阻塞的线程,转至可运行状态]


·终止状态:表示线程已经执行完毕,声明周期已经结束,不会再转换为其他状态TERMINATED

6、线程的六种状态(Java API层面描述)

JUC并发编程之:简单概述(一)

##从JAVA API层面描述(Thread State):

·NEW:线程刚被创建,但是没有调用start()方法

·RUNNABLE:调用了start()方法之后
注意,JavaAPI层面的RUNNABLE状态涵盖了操作系统层面的 可运行状态、运行状态 和
阻塞状态(由于BIO导致的线程阻塞,在JAVA里无法区分,仍然认为是可运行)

·BLOCKED、WAITING、TIMED_WAITING都是JAVA API层面对阻塞状态的细分

·TERMINATED线程代码运行结束

代码展示:

@Slf4j
public class ThreadState {

    public static void main(String[] args) {
        //NEW
        Thread t1 = new Thread(()->{
            log.debug("running");
        },"t1");

        //RUNNING
        Thread t2 = new Thread(()->{
            while (true){

            }
        },"t2");
        t2.start();

        //TERMINATED
        Thread t3 = new Thread(()->{
            log.debug("running...");
        },"t3");
        t3.start();

        //TIMED_WATING
        Thread t4 = new Thread(()->{
            synchronized (ThreadState.class){ //t4比t6抢险占据了 锁
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t4");
        t4.start();

        //WAITING
        Thread t5 = new Thread(()->{
            try {
                t2.join();//等待t2的执行结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t5");
        t5.start();


        //BLOCKED
        Thread t6 = new Thread(()->{
            synchronized (ThreadState.class){ //t4比t6抢险占据了 锁
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t6");
        t6.start();


        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.debug("t1 state : {}",t1.getState());
        log.debug("t2 state : {}",t2.getState());
        log.debug("t3 state : {}",t3.getState());
        log.debug("t4 state : {}",t4.getState());
        log.debug("t5 state : {}",t5.getState());
        log.debug("t6 state : {}",t6.getState());


    }
}

结果:

[t3] DEBUG com.lee.juc.ThreadState - running...
[main] DEBUG com.lee.juc.ThreadState - t1 state : NEW
[main] DEBUG com.lee.juc.ThreadState - t2 state : RUNNABLE
[main] DEBUG com.lee.juc.ThreadState - t3 state : TERMINATED
[main] DEBUG com.lee.juc.ThreadState - t4 state : TIMED_WAITING
[main] DEBUG com.lee.juc.ThreadState - t5 state : WAITING
[main] DEBUG com.lee.juc.ThreadState - t6 state : BLOCKED

7、例:应用统筹

##思考:
泡茶的工序:洗水壶1s、拿水壶烧水15s、洗茶壶1s、洗茶杯1s、拿茶叶1s、泡茶1s
如何在最快且不浪费资源的情况下,泡好这壶茶

代码实现:

@Slf4j
public class SteepTea {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            try {
                log.debug("洗水壶...");
                Thread.sleep(1000);
                log.debug("拿水壶烧水...");
                Thread.sleep(15000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"老李");


        Thread t2 = new Thread(()->{
            try {
                log.debug("洗茶壶...");
                Thread.sleep(1000);
                log.debug("洗茶杯...");
                Thread.sleep(1000);
                log.debug("拿茶叶...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                t1.join();//等待水烧开
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("泡茶...");
        },"小李");

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

    }
}

代码结果:

16:17:58.618 [老李] DEBUG com.lee.juc.SteepTea - 洗水壶...
16:17:58.618 [小李] DEBUG com.lee.juc.SteepTea - 洗茶壶...
16:17:59.632 [老李] DEBUG com.lee.juc.SteepTea - 拿水壶烧水...
16:17:59.632 [小李] DEBUG com.lee.juc.SteepTea - 洗茶杯...
16:18:00.646 [小李] DEBUG com.lee.juc.SteepTea - 拿茶叶...
16:18:14.647 [小李] DEBUG com.lee.juc.SteepTea - 泡茶...

思考:

·上面的模拟是小李等老李的水烧开了,小李泡茶,如果反过来要实现老李等小李的茶叶拿过
来,老李泡茶呢?

·上面的两个线程其实是各执行各的,如果要模拟老李把水壶交给小李泡茶,或者模拟小李把茶
叶交给老李泡茶呢
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
并发编程
并发编程笔记本博客根据学习而做的笔记,链接如下一、基本概念1、进程与线程进程程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。进程
Wesley13 Wesley13
3年前
4、jstack查看线程栈信息
1、介绍利用jps、top、jstack命令找到进程中耗时最大的线程,以及线程状态等等,同时最后还可以显示出死锁的线程查找:FoundoneJavaleveldeadlock即可1、jps获得进程号!(https://oscimg.oschina.net/oscnet/da00a309fa6
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
3年前
Java多线程介绍
1\.线程概述1.1线程和进程进程是处于运行过程中的程序,并且具有一定的独立功能并发性:同一个时刻只能有一条指令执行,但多个进程指令被快速轮换执行并行:多条指令在多个处理器上同时执行线程是进程的执行单元1.2多
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这