本系列会更新我在学习juc时的笔记和自己的一些思想记录。如有问题欢迎联系。
程序是静态的,进程是动态的
并发能力:同一时间应对多件事情的能力。
单核 cpu 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,一般会将这种 线程轮流使用 CPU 的做法称为并发, concurrent。
多核 cpu下,每个 核(core) 都可以调度运行线程,这时候线程可以是并行的。
引用 Rob Pike 的一段描述:
例子:
以调用方角度来讲,如果
1) 设计
多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了 5 秒钟,如果没有线程调度机制,这 5 秒 cpu 什么都做不了,其它代码都得暂停...
//同步
@Slf4j(topic = "c.Sync")
public class Sync {
public static void main(String[] args) {
FileReader.read(Constants.MP4_FULL_PATH);
log.debug("do other things ...");
}
}
//异步
@Slf4j(topic = "c.Async")
public class Async {
public static void main(String[] args) {
new Thread(() -> FileReader.read(Constants.MP4_FULL_PATH)).start();
log.debug("do other things ...");
}
}
2) 结论
充分利用多核 cpu 的优势,提高运行效率。想象下面的场景,执行 3 个计算,最后将计算结果汇总。
计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
注意
需要在多核 cpu 才能提高效率,单核仍然时是轮流执行
1) 设计
@Fork(1)
@BenchmarkMode(Mode.AverageTime)//测试模式,统计程序平均时间
@Warmup(iterations=3)//热身三次
@Measurement(iterations=5)//五轮测试取平均值
public class MyBenchmark {
static int[] ARRAY = new int[1000_000_00];
static {
Arrays.fill(ARRAY, 1);
}
@Benchmark
public int c() throws Exception {
int[] array = ARRAY;
FutureTask<Integer> t1 = new FutureTask<>(()->{
int sum = 0;
for(int i = 0; i < 250_000_00;i++) {
sum += array[0+i];
}
return sum;
});
FutureTask<Integer> t2 = new FutureTask<>(()->{
int sum = 0;
for(int i = 0; i < 250_000_00;i++) {
sum += array[250_000_00+i];
}
return sum;
});
FutureTask<Integer> t3 = new FutureTask<>(()->{
int sum = 0;
for(int i = 0; i < 250_000_00;i++) {
sum += array[500_000_00+i];
}
return sum;
});
FutureTask<Integer> t4 = new FutureTask<>(()->{
int sum = 0;
for(int i = 0; i < 250_000_00;i++) {
sum += array[750_000_00+i];
}
return sum;
});
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
new Thread(t4).start();
return t1.get() + t2.get() + t3.get()+ t4.get();
}
@Benchmark
public int d() throws Exception {
int[] array = ARRAY;
FutureTask<Integer> t1 = new FutureTask<>(()->{
int sum = 0;
for(int i = 0; i < 1000_000_00;i++) {
sum += array[0+i];
}
return sum;
});
new Thread(t1).start();
return t1.get();
}
}
在单核的情况下,多线程和单线程效率基本一致,多线程会有上下文切换的耗时。
在多核的情况下,多线程比单线程就会效率翻倍。
2) 结论
单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活。
多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的。
IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化
创建线程对象
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程,交给任务调度器,分配时间片,交给时间片去执行。
t.start();
例如:
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void test2() {
Thread t = new Thread(()->{ log.debug("running"); }, "t2");
t.start();
}
public static void test1() {
//匿名内部类的写法
Thread t = new Thread(){
@Override
public void run() {
log.debug("running");
}
};
//创建线程的时候可以给其指定名称
t.setName("t1");
t.start();
}
}
把【线程】和【任务】(要执行的代码)分开
Runnable源码,如果接口中有@FunctionalInterface注解,则可以被lambda简化。如果一个接口中有多个抽象接口,是没办法用lambda简化的。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnablecode> is used
* to create a thread, starting the thread causes the object's
* <code>runcode> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>runcode> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
@Slf4j(topic = "c.Test2")
public class Test2 {
public static void main(String[] args) {
//果接口中有@FunctionalInterface注解,则可以被lambda简化
Runnable r = () -> {log.debug("running");};
Thread t = new Thread(r, "t2");
t.start();
}
}
分析 Thread 的源码,理清它与 Runnable 的关系
小结
都是走的线程里的run方法。
FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
实现一个RunnableFuture的接口
public class FutureTask<V> implements RunnableFuture<V> {
RunnableFuture接口又继承了Runnable和Future
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
Future里有get方法返回任务执行结果
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result, if available.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
* @throws TimeoutException if the wait timed out
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
Callable可以配合FutureTask让任务执行完了,将结果传给其他线程
能抛出异常
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
实例代码:
// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
log.debug("hello");
return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);
输出:
19:22:27 [t3] c.ThreadStarter - hello
19:22:27 [main] c.ThreadStarter - 结果是:100
jconsole 远程监控配置
java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
Dcom.sun.management.jmxremote.authenticate=是否认证 java类
不需要就false,ip地址和连接端口在输入后记得把`去掉
如果要认证访问,还需要做如下步骤
ava Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
调用run
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug(Thread.currentThread().getName());
FileReader.read(Constants.MP4_FULL_PATH);
}
};
t1.run();
log.debug("do other things ...");
}
输出:
是主线程main来调用run方法,程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的
19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...
调用 start
t1.start();
输出:
19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms
程序在 t1 线程运行, FileReader.read() 方法调用是异步的
小结:
sleep