【JavaEE】线程案例-定时器-线程池 and 工厂模式

简介: JavaEE & 线程案例 & 定时器 & 线程池 and 工厂模式

JavaEE & 线程案例 & 定时器 & 线程池 and 工厂模式

1. 定时器

定时器,可以理解为闹钟

我们设立一个时间,时间一到,让一个线程跑起来~

而Java标准库提供了一个定时器类:

Timer ,from java.util


9aae6f31e99e8d7bdba1227f26731ac7.jpg

1.1 定时器Timer的使用

1.1.1 核心方法schedule


3e17335c41a3408ba7442dd3e63f1ce2.png

传入任务引用(TimerTask task)和 “定时”(long delay / ms)


07f6a10fefea41fa8896287dc8922c4c.png

由于TimerTask不是函数式接口,是普通的抽象类

所以只能用匿名内部类,而不能用lambda表达式

94819d8fd26248998d0def9c7550e497.png


写法

public static void main(String[] args) {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("好耶 ^ v ^");
            }
        }
    },1000);
    System.out.println("不好耶 T . T");
}


8f1f3568720a46dfafeac4a704a8a451.png

TimerTask实现了Runnable


不能传Runnable对象过去,这属于向下转型~

4d3587825e534dcd8a0207e185ced9cf.png


是Runnable的一个“封装”

所以,重写run方法,合情合理~

只不过不能用

而在Timer的schedule方法内部,则将这个线程保存起来,定时后执行~

6325bc9f04724e63be3c92b84f8cd347.gif

而这,有一个细节,就是执行完后,程序并没有结束,进程并没退出

原因是:


Timer内置了一个前台线程

阻止进程退出~

这并不是重点,其实就是timer在等待被安排下一个任务~

1.1.2 定时器管理多个线程

public class Test {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期一好耶 ^ v ^");
            }
        },1000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期二好耶 ^ v ^");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期三好耶 ^ v ^");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期四好耶 ^ v ^");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期五好耶 ^ v ^");
            }
        },5000);
        System.out.println("今天不好耶 T . T");
    }
}

那么就安排多个任务呗~

baed827cbae340ed8733c5ca2417e984.png


1964590a6de54acbb9a3d48fd89bf60c.gif


1.1.3 定时器的使用场景

应用场景特别多


尤其是网络编程

而这个任务等待,不应该是无期限的


超时:504 【gateway timeout】

定时器可以强制终止请求:浏览器内部都有一个定时器,发送请求后,定时器就开始定时;若在规定时间内,响应数据没有返回,就会强制终止请求


dd97f075f08f42588c9541b93aedc5d0.png

这个方法一般在任务的run方法中调用,确定是否及时


这种特殊语法不是我们能理解的,并且目前我们不需要用到这个用法~

1.2 自己实现一个定时器

想法一,根据任务们的时间

在添入的时候,就让他们启动并以对应的时间"睡下"

有点像睡眠排序法这个消遣的笑话~

显然这个方法是不科学的,线程到达一个量级,进程必然装不下

系统必然卡死崩掉

想法二,根据时间,到了时间自动启动~

将任务们按照时间长短排序

每次只看最早启动的任务就好

当然,等待时间是同步的~

每个任务都有在等

启动,再去看接下来的任务~

如果两个任务同时启动,顺序则不能确定~

是不是触动你的DNA了?


没错,搞一个堆就好了

每次可见堆顶元素~

而小根堆堆顶正是我们这里的最早启动的任务~

旧堆顶取走后,新堆顶又是剩余的最早启动的任务~

而定时器的核心数据结构就是:优先级队列 ===> 堆

而定时器可能被多线程使用,所以线程安全问题也要被保证

队列为空,队列为“满”的时候,对操作也要有限制(不应该有无限个任务)

这就需要我们的阻塞队列~

即,定时器底层就是一个阻塞优先级队列! ===> PriorityBlockingQueue


对于PriorityBlockingQueue,我这里并不会去模拟~

1.2.1 属性

class MyTask {
    public Runnable runnable;
    public long time;
}
public class MyTimer {
    private PriorityBlockingQueue<MyTask> tasks = new PriorityBlockingQueue<>();
}

阻塞优先级队列中的元素应该有如下两个信息:


MyTask

执行什么任务~

任务什么时候执行~

d59f83312c6b43469e447a1851b087f6.png


1.2.2 建立一个MyTask对象

runnable就是一个任务~

time是绝对时间,而不是定时时间

是”启动时间“的具体时间

到达这个时间,任务才能运行~

为1970.01.01那一天的00:00:00到构建对象时的此时此刻的毫秒数~

获取当前时间方法:System.currentTimeMillis()

class MyTask {
    public Runnable runnable;
    public long time;
    //绝对时间戳~
    //方便判断~
    //这个不是定时时间
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = delay + System.currentTimeMillis();
    }
}


1.2.3 schedule方法

public void schedule(Runnable runnable, long delay) {
    MyTask myTask = new MyTask(runnable, delay);
    tasks.put(myTask);
}


构造一个myTask对象插入到队列中~

1.2.4 构造方法初步设计

public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                MyTask myTask = tasks.take();
                long nowTime = System.currentTimeMillis();
                if(myTask.time <= nowTime) {
                    //启动
                }else {
                    //不能启动
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

定时器被构造出来后,应该就已经启动“母线程”

就应该尝试【take】了

只不过队列为空,要阻塞等待~

之后通过schedule安排任务~【put】

b3c5fc04c8dc459eb160451c07146d34.png


启动:

调用run方法

不能启动:

将任务返回队列

1.2.5 构造方法最终设计

在构造方法初步设计有两个很严重的BUG

可以停止观看去想一想~

优先级对于自定义类,需要我们给“比较规则”,“优先级规则”

“没有等待”以及“盲目等待”

对于1. 比较规则:


只需要让MyTask实现比较接口


当然也可以传比较器~(lambda表达式)

d3471a04c44e4f438a3bdaeb4c780e94.png



两种方式都OK~


左减右大于0


如果代表此对象大于该对象代表升序排列 ===> 小根堆


如果代表此对象小于该对象代表降序排列 ===> 大根堆


对于2. “没有等待”以及“盲目等待”


上述代码只会判断一次~

应该套上一个循环~

6ded12322b1e4521a4e87cee3d6bd6a7.png


wait等待,唤醒起来比较方便安全


sleep不是一个很好的选择~

因为新任务的插入,要进行唤醒

超过限定时间,自动醒来

wait需要有锁,这里我把循环体整个框起来了

我用的是“同步锁”

“盲目等待” 代表,这里放回去后,计算器又会判断是否可启动


这样就会导致一段时间内,这个任务反复被拿来拿去无数次~

相当于,上课时看表,一秒看一次,忙等

而计算机,1ms就可以看很多很多次~

那么我们只需要在schedule时唤醒一下,让他才判断一次就行了~


这防止新插入的任务更早而被忽略

大大减少判断次数!


0a64b6af80a64052b66ac59a278e6ed7.png


最终版:

public void schedule(Runnable runnable, long delay) {
    MyTask myTask = new MyTask(runnable, delay);
    tasks.put(myTask);
    synchronized (locker) {
        locker.notify();
    }
}
private Object locker = new Object();
public MyTimer() {
    Thread t = new Thread(() -> {
        while(true) {
            synchronized (locker) {
                try {
                    MyTask myTask = tasks.take();
                    long nowTime = System.currentTimeMillis();
                    if(myTask.time <= nowTime) {
                        myTask.runnable.run();
                    }else {
                        tasks.put(myTask);
                        locker.wait(myTask.time - nowTime);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    t.start();
}

cd61c8deb81b4e62ace4dbed09cab1cc.png


别忘了启动线程~

1.3 测试MyTimer

用MyTimer替换之前的Timer


TimeTask也可替换为Runnable,不过没关系,向上转型~


public static void main(String[] args) {
    MyTimer timer = new MyTimer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("星期一好耶 ^ v ^");
        }
    },1000);
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("星期二好耶 ^ v ^");
        }
    },2000);
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("星期三好耶 ^ v ^");
        }
    },3000);
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("星期四好耶 ^ v ^");
        }
    },4000);
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("星期五好耶 ^ v ^");
        }
    },5000);
    System.out.println("今天不好耶 T . T");
}


测试结果正常:


283f49c869604237acf5c63b6df785d1.gif

退出代码130,是按ctrl + f2

1.4 补充

你可能也发现了,代码之中并没有完全保证,一个线程一定会在规定的时间后执行

因为一个定时器,只能运行一个线程,没有并发性

只是和main线程并发~

所以,如果一个线程运行时间较长,会导致其后的任务“被迫延时”

而判断条件不是等于等于,也有这一方面原因

另一方面原因是,可能因为调度问题有误差~

23eae8fa97524c9d9439381516b55a9c.png


此时这个定时器,就只能起到,保证任务执行顺序的功能~

1.4.1 例子1

例如以下测试代码:

public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期一好耶 ^ v ^");
                try {
                    Thread.sleep(5000);
                    System.out.println("已过去五秒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期二好耶 ^ v ^");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期三好耶 ^ v ^");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期四好耶 ^ v ^");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("星期五好耶 ^ v ^");
            }
        },5000);
        System.out.println("今天不好耶 T . T");
    }
}


88147867e9ff42328d1874d3d9c462bc.png

第一个任务要花5秒,而还差1秒,第二个任务就应该启动~

而现象是这样的:


d66e2941f7f245af942faa40ab584eab.gif

后面的任务已经受严重延迟~

1.4.2 例子2

如果一个任务死循环了,会导致后面的任务无限延期


20c3295ae3cc4ab6a1b872a31170e5a1.png

就会导致下面这种情况:

02cfabf6f53d4f688074cefa1064f3a3.gif


注意:


这并不是我写的定时器有问题 ,Java标准库的定时器,就是这样子的, 一个定时器一个时间段里只能执行一个任务

现象跟MyTimer是一样的

就是这两个例子那样

一个任务时间太长,会导致下一个任务延迟

只起“区分先后”的作用

1.5 顺带一题

问:wait的同步锁的位置不同,结果会怎么样?


例如:


c3f5a9942ef946e1bdb6fdcfc7e048d9.png

这两种锁的框法不同,结果一样吗?

1.5.1 后者


3955f73c8da14b00aff03164d5fa7246.png

重点就在于,没有保证take与wait是原子的~

1.5.2 前者

保证原子性后:

a533e6cf6f68464abce3ec12cca9c0e7.png


2. 线程池

跟字符串常量池和数据库连接池一样

这个池的作用就提高效率,节省开销~

即使线程很轻量,但是积少成多就不能忽略~

只要再池子里去拿,就要比从系统申请要快~

提高效率还能提高轻量化线程“协程”,Java标准库还不支持


而线程池是一个重要的途径~


从线程池里拿线程,纯纯的用户态操作

而从系统上申请,就必须设计用户态和内核态之间的切换

真正的创建线程,是在内核态完成的

2.1 用户态和内核态

操作系统 = 内核 + 配套的应用程序

内核:各种系统管理和驱动,而内核就是为了支持应用程序的

这里不仅仅指核心~

因为进程管理这是他的工作之一

逻辑核心们也只是他的打工人~


cb75774df5d240c99a6e1ef04287803a.png

需要内核支持,才能运行的应用程序~


例如,println,打印到屏幕,需要通过硬件管理~

即,内核给那么多人服务,那么就不一定及时


举个栗子:


去银行打印资料,前台可以帮你打印

而前台在同时会去帮助其他人,给你打印好了还要好一会儿才给你~

af98c6b8d86c427499983d17f05c195e.png


你也可以去自助打印机打印

这样的时间消耗就只会缩短在 “打印需求” 内去消耗

05cc6345a7564cc5908b0ce2cdea97b3.png


也就是说,我们在申请线程时


内核态申请 ==> 内核要顾及进程管理和其他管理与驱动~


af460cea0c1e40d2bc0bd3c2b54884b4.png


用户态去拿 ==> 只需要在进程管理这个单项里去拿线程~


b638614c677c4e249d0d815497ec3b7a.png


当然,线程的诞生,还是要内核态申请


放进线程池,之后在线程池里用户态拿就好~

2.2 标准库线程池类ExecutorService

Java标准库实现了一个接口,ExecutorService,在进程中服务线程执行~


通过这个池的服务,不需要每次都申请~

但是这个接口不是通过new子类对象去实例化的,而是用一个静态方法去实例化~

225d3785854d4e89bebfa4258d8c69b0.png



而这里的Executors类就是“工厂类”

这个类就是为了构造“线程池”而存在的

这个类可以调用各种静态方法

而这些静态方法使用起来简单

并且可以构造各种满足我们特殊需要的对象

2.3 工厂模式

“工厂”


即“对象工厂”,可以工厂生产出不同的对象

有员工去帮你生产,使用简单

降低使用成本

相同原料可以有不同产品,避免参数列表相同导致无法触发重载

重要作用!

而工厂模式其实就是,把一个类/接口的构造方法,交给一个“工厂类”去定义


即,将构造方法打包成类

Executors工厂:

2d0a57f5a13c47e6b289ca21d3ceadfc.png



重点掌握


a9d610fa895c42a1823f0de05f613c87.png

你也可以自己“开个厂”


就比如说,一个【堆】,泛型类是我们的自定义类

而我们的自定义类要我们去规定比较方法

public class A {
    int a1;
    int a2;
    int a3;
    int a4;
    int a5;
    int a6;
}


假设我们A类有六个成员(都是int类型)

要求建立6个堆,每个堆以不同的比较规则去创建

每次创建都好麻烦,都要写个比较器~

只需要“开个比较器厂”,把这些构造方法包装起来就好~

以后构造的时候,通过不同的方法名调用对应的构造方法~

比较器Comparator

构造方法基本都没有参数列表的,那么就不能用重载去解决~

比较器的不同主要不是因为构造方法,而是compare被怎么重写有关~

compare方法重写也只能重写一个


960dc82320944a2fa241e63288ce85a2.png

2.3.1 开[A的构造厂]

public static A createA1(int a) {
        //匿名内部类优先捕获全局性质变量,这里在代码块内,a1就为全局性变量~
        return new A() {
            {
                this.a1 = a;
            }
        };
    }
    public static A createA2(int a) {
        return new A() {
            {
                this.a2 = a;
            }
        };
    }
    public static A createA3(int a) {
        return new A() {
            {
                this.a3 = a;
            }
        };
    }
    public static A createA4(int a) {
        return new A() {
            {
                this.a4 = a;
            }
        };
    }
    public static A createA5(int a) {
        return new A() {
            {
                this.a5 = a;
            }
        };
    }
    public static A createA6(int a) {
        return new A() {
            {
                this.a6 = a;
            }
        };
    }
}

e3de13e0d1884853a58604a6fd347f61.png

2.3.2 开[A的比较器厂]

class CreateComparatorA {
    public static Comparator<A> createA1() {
        return ((o1, o2) -> {
           return o1.a1 - o2.a1;
        });
    }
    public static Comparator<A> createA2() {
        return ((o1, o2) -> {
           return o1.a2 - o2.a2;
        });
    }
    public static Comparator<A> createA3() {
        return ((o1, o2) -> {
           return o1.a3 - o2.a3;
        });
    }
    public static Comparator<A> createA4() {
        return ((o1, o2) -> {
           return o1.a4 - o2.a4;
        });
    }
    public static Comparator<A> createA5() {
        return ((o1, o2) -> {
           return o1.a5 - o2.a5;
        });
    }
    public static Comparator<A> createA6() {
        return ((o1, o2) -> {
           return o1.a6 - o2.a6;
        });
    }
}


01158d8fecb647598d61e1111d55e176.png


2.3.3 测试

public class A {
    int a1;
    int a2;
    int a3;
    int a4;
    int a5;
    int a6;
    //参数列表相同无法特定构造特定成员~
    @Override
    public String toString() {
        return "A{" +
                "a1=" + a1 +
                ", a2=" + a2 +
                ", a3=" + a3 +
                ", a4=" + a4 +
                ", a5=" + a5 +
                ", a6=" + a6 +
                '}' + '\n';
    }
    public static void main(String[] args) {
        PriorityQueue<A> queue1 = new PriorityQueue<>(CreateComparatorA.createA1());
        PriorityQueue<A> queue2 = new PriorityQueue<>(CreateComparatorA.createA2());
        PriorityQueue<A> queue3 = new PriorityQueue<>(CreateComparatorA.createA3());
        PriorityQueue<A> queue4 = new PriorityQueue<>(CreateComparatorA.createA4());
        PriorityQueue<A> queue5 = new PriorityQueue<>(CreateComparatorA.createA5());
        PriorityQueue<A> queue6 = new PriorityQueue<>(CreateComparatorA.createA6());
        queue1.offer(createA.createA1(2));
        queue1.offer(createA.createA1(1));
        queue1.offer(createA.createA1(4));
        queue1.offer(createA.createA1(3));
        queue1.offer(createA.createA1(5));
        System.out.println(queue1);
    }
}


结果:

确实以a1为标准~


ddabcbdb87ea4054a2621e7040b70f65.png

当然,工厂当然不只可以生产构造方法:


还能生产那些我们需要的:重复参数列表的方法

例如生产A的toString()方法~

不额外说了~

2.4 ExecutorService的属性和方法

2.4.1 通过工厂类构造

a9d610fa895c42a1823f0de05f613c87.png


不给固定容量,按需创建线程池~

fca04e56e6d448a093d0d5e448c2e90d.png


跟定时器有关~

最重点的一个:


6d1a4ee9a8d6453b96682f9bdc95d281.png


提供固定容量的线程池构造方法~


c5bf5859419c4054a95174a0ce6808c2.png

2.4.2 submit方法

提交线程~

public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(10);
    pool.submit(() -> {
        System.out.println("好耶 ^ v ^ ");
    });
}


2.4.3 ThreadPoolExecutor类的属性

既然是构造ThreadPoolExecutor

那么它的属性就至关重要~

下面是Java的官方文档的内容:

a2cc2f8951b848b9b1dcdb4832d2d01e.png



这个类在【util应用应用工具】的【concurrent并发包】中~

简称【JUC】


9ae1d6b294bb4fc49a8586ac5cf7fc9d.png

通过这个构造方法让我们看到很多属性~

我也只讲解这几个属性~

也不讲这个方法咋实现的

corePoolSize 和 maximumPoolSize

corePoolSize为核心线程数

maximumPoolSize为最大线程数

核心线程,即不能随意停止的线程

最大线程数里【包含核心线程和“临时线程”】

这个“临时线程”就相当于公司里的实习生

关键时期来应急

这个核心线程就相当于正式员工

不能随意辞退

线程池会在任务少的空闲期,根据这些参数进行线程调整,把一些临时线程给销毁了~


keepAliveTime(long) 和 unit(TimeUnit)

keepAliveTime 为临时线程存活时间~


“实习生”并不是立即被辞退

而是跟这个参数有关

允许最多活多久~

unit ==> 时间单位


BlockingQueue< Runnable > workQueue

线程池要管理很多任务

通过阻塞队列来组织~

方便程序员控制线程数据交互

submit提交到这个阻塞队列里~

ThreadFactory threadFactory

线程工厂 ,跟工厂模式有关~

不细讲

RejectedExecutionHandler handler

线程池的拒绝执行应对策略~

池子满了,继续往里添加线程,如何应对?如何拒绝?

线程池满了是不依赖阻塞队列的

这个任务要不要干最好立马给出决策!

一般是空了依赖阻塞队列~

还有阻塞队列的线程安全性和解耦合性也很好

2.4.4 线程池的拒绝策略

755ab657f7ef4dea9fd8a02c81bd4ae2.png

直接抛异常 ---- 毁灭

哇哇大哭

直接拒绝 ---- 谁给我这个线程任务,谁自己去完成

达咩达咩

抛弃最老任务 ---- 把最先安排的任务 [队列头] 给删了,替换成新任务

做出牺牲

抛弃最新任务 ---- 我继续干原来的活,新的活谁都没干

原封不动

2.5 模拟实现线程池

public class MyThreadPool {
    private BlockingQueue<Runnable> pool = new LinkedBlockingQueue<>();
    public void submit(Runnable runnable) throws InterruptedException {
        pool.put(runnable);
    }
    //实现固定线程数的线程池
    //不是容量,是确确实实的线程数
    public MyThreadPool(int number) {
        for (int i = 0; i < number; i++) {
            Thread thread = new Thread(() -> {
                Runnable runnable = null;
                try {
                    while(true) {
                      runnable = pool.take();
                      runnable.run();  
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }
}


我们的简单实现,不涉及2.4.3的属性~


注意:这里的线程数,是工作人数,定量


而阻塞队列里的线程数,则是这些人做的”任务“


14503d0020c84f199a63202451084ac7.png

测试:

public static void main(String[] args) throws InterruptedException {
    MyThreadPool myThreadPool = new MyThreadPool(10);
    for (int i = 1; i <= 1000; i++) {
        int id = i; //线程id,变量捕获~
        myThreadPool.submit(() -> {
            System.out.println("好耶^ v ^ " + id);
        });
    }


提供固定的工作人员 * 10

源源不断塞1000个任务~

工作人员疯抢~


54773438c3cd46a58285b7f43c3bb5ed.gif


数据顺序无序很正常

线程调度无序嘛~

程序还未结束

这是因为十个工作人员“吸血鬼”还等着任务呢~

线程池中如何体现,“用户态拿”:


线程池中有固定数量的线程,而这些线程是一开始一次性申请的

之后我们无需为了一个或者多个任务再去额外申请一个Thread对象了

只需要提交任务给线程池,让线程池里的“空线程”去帮我们干事

反复用这些空线程去做任务~

只需要提交就行,不需要从线程池里取线程

我们可能没有拿线程这个操作~

但是这样就相当于我们拿了线程做了任务~

只不过这个任务几乎全自动地被线程池帮忙在一个线程里执行了

而原本执行一个任务就要我们需要自己申请个新的Thread对象

35105dab9b2941d5a46ad2144eac354e.png

注意:


线程 不等于 任务

任务必须依托线程才能执行

——

2.6 线程池的固定线程数的确定(理论)

至于线程池固定线程数,设置为多少合适?


最好最科学的方式就是去测试!

cpu密集型,主要做一些计算工作,要在cpu上运行~


IO密集型,主要等待一些IO操作(读写硬盘/读写网卡),不怎么吃cpu


如果你的线程全是使用cpu的,那就得设置线程数少于核心数~

如果全是使用IO的,那就可以设置很多很多线程,远超核心数

而实际情况不会这么极端,所以这个线程数一定是要看实际情况的


所以就要测试!

通过一些数据去看看哪个固定线程数是OK的

例如执行时间…检测资源使用状态~

控制变量法~


相关文章
|
17天前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
5天前
|
Java 数据库
【Java多线程】对线程池的理解并模拟实现线程池
【Java多线程】对线程池的理解并模拟实现线程池
14 1
|
17天前
|
Java 程序员 数据库
Java线程池让使用线程变得更加高效
使用一个线程需要经过创建、运行、销毁三大步骤,如果业务系统每个线程都要经历这个过程,那会带来过多不必要的资源消耗。线程池就是为了解决这个问题而生,需要时就从池中拿取,使用完毕就放回去,池化思想通过复用对象大大提高了系统的性能。线程池、数据库连接池、对象池等都采用了池化技术,下面我们就来学习下线程池的核心知识、面试重点~
61 5
Java线程池让使用线程变得更加高效
|
4天前
|
Java
线程安全问题-卖票案例实现
线程安全问题-卖票案例实现
8 0
|
5天前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
11 0
|
7天前
|
消息中间件 监控 前端开发
面试官:核心线程数为0时,线程池如何执行?
线程池是 Java 中用于提升程序执行效率的主要手段,也是并发编程中的核心实现技术,并且它也被广泛的应用在日常项目的开发之中。那问题来了,如果把线程池中的核心线程数设置为 0 时,线程池是如何执行的? 要回答这个问题,我们首先要了解在正常情况下,线程池的执行流程,也就是说当有一个任务来了之后,线程池是如何运行的? ## 1.线程池的执行流程 正常情况下(核心线程数不为 0 的情况下)线程池的执行流程如下: 1. **判断核心线程数**:先判断当前工作线程数是否大于核心线程数,如果结果为 false,则新建线程并执行任务。 2. **判断任务队列**:如果大于核心线程数,则判断任务队列是否
17 1
面试官:核心线程数为0时,线程池如何执行?
|
15天前
|
消息中间件 缓存 Java
【多线程学习】深入探究定时器的重点和应用场景
【多线程学习】深入探究定时器的重点和应用场景
|
15天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
|
15天前
|
消息中间件 监控 安全
【JAVAEE学习】探究Java中多线程的使用和重点及考点
【JAVAEE学习】探究Java中多线程的使用和重点及考点
|
5天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
17 1