文章目录
一、进程,线程
进程:进程说白了就是一个程序的一个实例,一个程序可以开多个实例,也可以只开一个实例。一个应用打开就是一个进程。有的程序限制了只能开一个实例,比如微信,QQ音乐等,而记事本,浏览器,代码编辑器可以开多个实例。也可以说进程是程序运行的过程。
线程:线程是任务调度的最小单元,进程就是线程的容器,一个进程里可以有多个线程。
进程是负责将指令分配给线程,或者负责将内存分配给线程等工作。
进程或线程的上下文切换:当一个进程没工作则需要将内存分配给别的进程,这就是进程的上下文切换,线程上下文切换也是一样。
二、并发,并行
并发:多个线程轮流使用cpu的时间片叫做并发。
并行:当cpu存在多个核心时,此时多个线程可以同时使用cpu的时间片,这就叫做并行
单核心cpu调度多线程在微观上执行程序是串行的,但在宏观上可以看成并行,但本质还是串行,为什么说在宏观上并行呢?因为cpu的最小的时间片可以为10ms左右,而我们人能分辨出的最小时间为0.1s左右,所以在宏观上单核心cpu调度多线程可以看作并行。
单核心cpu的多线程只有并发,而多核心的多线程既有并发又有并行。
三,同步,异步
在方法调用方角度来说,等待方法执行完返回结果的叫同步,不等待方法执行完返回结果的叫异步。
想要实现异步就必须通过多线程:
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run(){
log.debug("计算1到1亿的总和");
}
};
t1.setName("t1");
t1.start();
log.debug("计算1+1的总和");
}
}
执行结果:
22:25:35 [t1] c.Test1 - 计算1到1亿的总和
22:25:35 [main] c.Test1 - 计算1+1的总和
在上面的执行中计算1到1亿的总和的代码在计算1+1的总和的代码之前,但是计算1到1亿的总和所用时间显然比计算1+1的总和的时间长,所以我在想在执行计算1到1亿的总和的时候等待,我想往下执行,这就要用到多线程才能实现异步。
四,多线程
在cpu有多个核心的前提下的多线程相对于单线程可以提高效率,在cpu只有一个核心的前提下多线程相对于单线程会降低效率,因为会有上下文切换的时间。
五,多线程的实现方法
1.直接使用Thread对象
public static void main(String[] args) {
//这里只是创建了一个线程对象,并没有把该线程纳入任务调度器管理
Thread t1=new Thread(){
@Override
public void run(){
log.debug("计算1到1亿的总和");
}
};
t1.setName("t1");
//将该线程纳入任务调度器管理
t1.start();
log.debug("计算1+1的总和");
}
2.通过Runnable接口
public static void main(String[] args) {
Runnable runnable=()->{
log.debug("running");
};
Thread t2=new Thread(runnable);
t2.start();
log.debug("running");
}
3.通过FutureTask对象
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建任务对象
FutureTask<Integer> futureTask=new FutureTask<>(()->{
log.debug("running");
return 100;
});
Thread t3=new Thread(futureTask);
t3.setName("t3");
t3.start();//开启名为t3的线程,注意是异步的,不用阻塞主线程等待返回结果
//中间可以执行别的代码
Integer result=futureTask.get();//结果已经好了,直接拿出来用
log.debug("结果是:"+result);
}
六,多线程调度的先后顺序不由人为控制
多线程调度的先后顺序不由人为控制而是由cpu的任务调度器控制。
public static void main(String[] args) {
new Thread(()->{
while (true){
log.debug("running");
}
},"t1").start();
new Thread(()->{
while (true){
log.debug("running");
}
},"t2").start();
}
我们将上面的代码执行两次得到的结果(取前十个打印):
第一次:
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
14:45:52 [t2] c.Test4 - running
第二次:
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t1] c.Test4 - running
14:52:24 [t2] c.Test4 - running
14:52:24 [t2] c.Test4 - running
七,栈,栈帧
在jvm的内存结构中,栈指的是一个线程运行所需的内存空间,也可以叫栈内存,栈往往与线程挂钩。
每次调用一个方法就和生成一个栈帧。
八,线程常用方法
1.start与run
当创建一个Thread对象,此时这个线程对象并没有纳入任务调度器管理,这个对象必须在执行了start方法之后才会纳入任务调度器调度。
run方法就是重写的Runnable接口的run方法,当Thread对象调用run方法时并没有新开一个线程调用,单单只是这个对象本身调用。
2.sleep和yield
sleep是一个静态方法,只能通过Thread.sleep(休眠时间)调用,Thread.sleep(休眠时间)在哪个线程里面调用就让哪个线程休眠,Thread.sleep(休眠时间)的作用是让当前线程进入休眠,并不参与cpu时间片的竞争,但是sleep方法不会释放锁。
yield也是一个静态方法,只能通过Thread.yield()调用,Thread.yield()在哪个线程里面调用就让哪个放弃对时间片的竞争,注意这个方法时没有时间的,执行完这个方法就表示当前线程放弃对时间片的竞争,但是任务调度器还是可以吧cpu时间片给当前线程。这和setPriority(Thread.MIN_PRIORITY)给线程设置优先级的方法是一样的,yield和setPriority并不能决定任务调度器最后将cpu时间片分给哪个线程。
2.1应用
3.join
当某个线程调用了join方法,作用就是哪个线程调用了join方法,比如t1.join(),作用就是等待t1线程执行完t1.join()才结束,当在主线程调用了t1.join()时,主线程就会阻塞,等待t1.join()结束。