JUC(Java Util Concurrency)并发编程是指利用Java语言提供的java.util.concurrent包中的工具和类来实现并发程序
1.为什么要有并发编程呢?
我们知道多核cpu可以同时处理多条指令,而单核cpu遇到多个指令同时请求执行的时候只能处理一条指令。
而我们在java程序中也希望能同时进行多个任务的执行从而提高效率或完成其他要求
这里举一个最简单的java线程提高效率的例子:
使用2个线程同时计算
static long fib(int n) {
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) throws InterruptedException {
int n = 40; // 计算斐波那契数列的项数
long start = System.currentTimeMillis();
// 创建两个线程执行计算
Thread t1 = new Thread(() -> {
long sum = 0;
for (int i = 0; i <= n / 2; i++) {
sum += fib(i);
}
System.out.println("线程1计算完成");
});
Thread t2 = new Thread(() -> {
long sum = 0;
for (int i = n / 2 + 1; i <= n; i++) {
sum += fib(i);
}
System.out.println("线程2计算完成");
});
t1.start();
t2.start();
// 等待两个线程执行完毕
t1.join();
t2.join();
long end = System.currentTimeMillis();
System.out.println("总用时:" + (end - start) + "ms");
}
}
不使用:
static long fib(int n) {
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) throws InterruptedException {
int n = 40; // 计算斐波那契数列的项数
long start = System.currentTimeMillis();
// 计算斐波那契数列的前一半和后一半的和
long sum1 = 0;
for (int i = 0; i <= n / 2; i++) {
sum1 += fib(i);
}
long sum2 = 0;
for (int i = n / 2 + 1; i <= n; i++) {
sum2 += fib(i);
}
long end = System.currentTimeMillis();
// 计算总和
long totalSum = sum1 + sum2;
System.out.println("总用时:" + (end - start) + "ms");
}
可以看到效率是有一定提高的。那么使用这样的多线程不会出现问题吗?答案是肯定会出现问题的,比如临界资源问题等并发问题。
那么想要使用并且较好的使用多线程我们必须先来了解什么是线程,什么是并发。
2.进程与线程
2.1 什么是进程
- 进程可以被视为是程序的一个实例
- 程序是由指令和数据组成的,但这些指令要运行,数据要读写,就必须将指令加载到cpu,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备。进程就是用来加载指令、管理内存、管理io的
2.2 什么是线程
- 一个进程中有可以有多个线程
- 一个线程就是一个指令流,将指令流中的一条条指令按一定的顺序交给CPU执行
- Java中,线程作为最小调度单位,进程作为资源分配的最小单位。
现在我们大致了解了线程的概念了,那么并发又是什么呢?
3. 并行与并发
3.1 什么是并行
并行就是同一时间多个任务同时执行;例如3个厨师同时在做菜
3.2 什么是并发
并发就是同一时间有多个任务要执行,这些任务会交替进行;例如一个厨师在某一时刻要切菜,炒菜,上菜,摆盘
4. java中的线程
4.1 创建java线程
方法一 :直接使用Thread
我们希望创建一个线程执行某项事,需要重写Thread中的run方法,Thread实现了Runnable接口
这里采用匿名内部内的方法来实现
//创建线程对象
Thread t1 = new Thread(){
@Override
public void run(){
log.debug("t1");
}
}
// 设置名字
thread.setName("t1");
// 启动线程
thread.start();
方法二:Runnable结合Thread使用
上面也说了Thread是实现了Runnable中的run方法
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("r");
}
};
Thread thread = new Thread(runnable,"t1");
thread.start();
//任务和线程分开 更加灵活
方法三:FutureTask结合Thread使用 实现有返回值的线程
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("hello");
return 100;
}
});
new Thread(task,"t1").start();
log.debug("{}",task.get());
当任务有返回值的时候使用 FutureTask
-
FutureTask接受 Callable<V>的参数
-
FutureTask是一个泛型类它实现RunnableFuture<V>接口 而RunnableFuture又继承了Runnable和Future<V>类
-
从而可以返回值,并能被Thread作为参数接收