闽公网安备 35020302035485号
在学习和工作中,我们自己都需要做很多的事情,事情大多都是我们亲自一件一件去完成,每件事情都是自己动手,那在做事情的花费时间就是每件事情的耗时总和。但我们都梦想着有一天能当上领导,当有任务的时候,我们可以交给下属去做,我们直接获取结果就好,不用关注做事情的过程,这样就可以提高我们处理事情的效率。那这种情景在计算机中是如何模拟呢?
在介绍线程之前,我们先介绍一下计算机中的任务执行:首先任务需放在内存,当得到调度的时候就需要分配CPU时间,有了CPU支持,任务才可以执行。常见的执行方式有两种:当我们采用一种串行的方式去执行任务,即CPU处理好当下的任务,才会去处理下一个任务,这样的执行时间就是每个任务时间的总和。
当在性能要求较高的场景下,我们会采用一种并发的方式去执行任务,在每个线程执行任务时,会获得CPU时间片,在这一点时间内可以处理当前任务。在并发的方式中,每个任务都能通过一定的方式获取到一部分CPU时间。这样宏观下就可以看到多个任务在一起执行,而且并发也可以很好地利用CPU的性能。


/**
* 堆代码 duidaima.com
* 打印方法
*/
public static void doPrint(){
try{
Thread.sleep(2000); // 模拟该方法处理时花费的时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("--- doPrint ---");
}
方法2:模拟我们敲代码的方法/**
* 编写代码方法
*/
public static void doCode(){
try{
Thread.sleep(3000);// 模拟该方法处理时花费的时间 } catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("--- doCode ---");
}
情景一 所有的事情自己做/**
* 串行调用
* @param args
*/
public static void main(String[] args) {
long start = System.currentTimeMillis();
doCode();
doPrint();
System.out.println(System.currentTimeMillis() - start);
}
如代码所示,我们首先记录开始时间,然后串行去处理方法2和方法1。--- doCode --- --- doPrint --- 5005如结果所示,串行执行方法的时间花费是两个方法的总和,这就相当于敲代码和打印这两件事自己来做,这种串行也是我们平时常见的编程方式,虽然很方便,但是并没有很好地利用计算机的性能,可能会导致无意义的等待。
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
// 堆代码 duidaima.com
// 建立一个线程,找个人去打印
Thread threadA = new Thread(() -> {
try {
doPrint();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "threadA");
// 启动一个线程
threadA.start();
doCode();
// 阻塞等待线程结果
threadA.join();
// 打印花费的时间
System.out.println(System.currentTimeMillis() - start);
}
如上代码所示,使用Thread建立了一个线程threadA,让线程去处理打印方法,然后使用start()方法启动线程,最后使用join()等待线程threadA执行完毕。--- doPrint --- --- doCode --- 3032如上结果所示,使用线程后,时间是doCode多一点点,这样使用线程后main线程就只需处理一个方法,执行效率得到了极大提升。
// 找到两个人进行处理
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread threadA = new Thread(() -> {
try {
doPrint();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "threadA");
Thread threadB = new Thread(() -> {
try {
doCode();
} catch (Exception e) {
throw new RuntimeException(e);
}
}, "threadB");
// 启动线程
threadA.start();
threadB.start();
// 阻塞当前线程,等待两个线程运行结束
threadA.join();
threadB.join();
System.out.println(System.currentTimeMillis() - start);
}
如上所示,采用了两个线程去处理编码和打印两个方法。--- doPrint --- --- doCode --- 3047大家可能有疑惑怎么时间和情景二结果一致呢?这是因为在Main方法(线程)中我们需要等待两个线程的运行结果,在代码中我们使用到了join()方法,这个方法是一种同步阻塞的方法,即他会阻塞Main线程,等待threadA和threadB两个线程运行结束。又因为是用了两个线程,所以等待的时间就是较长方法的时间。
private final static int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors(); private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVAILABLE_PROCESSORS, AVAILABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(5), new ThreadPoolExecutor.CallerRunsPolicy());如上我们就建立了一个线程池POOL_EXECUTOR啦,其中AVAILABLE_PROCESSORS是为了获取计算机的核心数量;然后使用了ThreadPoolExector显示创建了一个线程池POOL_EXECUTOR。
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
POOL_EXECUTOR.execute(() -> {
try {
doPrint();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
POOL_EXECUTOR.execute(() -> {
try {
doCode();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
System.out.println(System.currentTimeMillis() - start);
// 挂起当前线程
Thread.currentThread().join();
}
运行结果:63 --- doPrint --- --- doCode ---如结果所示,使用了线程池,执行了我们的方法,并且main方法等待的时间大大缩小了,但是他的执行顺序是排到第一的。这其实是因为我们没有阻塞去等待其他的方法,所以在main线程中,先打印了时间,这也是并发的原因,线程的执行顺序是不能保证的。
//修改doPrint和doCode两个方法,添加返回值。
/**
* 打印方法
*/
public static String doPrint1(){
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("--- doPrint ---");
return "Print Task Done";
}
/**
* 编写代码方法
*/
public static String doCode1(){
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("--- doCode ---");
return "Code Task Done";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
Future<?> doPrint = POOL_EXECUTOR.submit(() -> {
try {
doPrint1();
} catch (Exception e) {
e.printStackTrace();
}
});
Future<?> doCode = POOL_EXECUTOR.submit(() -> {
try {
doCode1();
} catch (Exception e) {
e.printStackTrace();
}
});
// 阻塞线程
doPrint.get();
doCode.get();
System.out.println(System.currentTimeMillis() - start);
}
运行结果:--- doPrint --- --- doCode --- 3051如上所述,当线程使用execute()方法时,是不需要阻塞去获取结果的,这种可以使用到日志打印。使用submit()就可以使用get()方法去阻塞获取结果。