线程与进程
进程
三种定义:
-
是程序的依次执行
- 进程是程序机器数据在处理机上顺序执行时所发生的活动
-
进程是具有独立功能的程序在一个数据集合上运行的过程
进程的特征:
- 动态性 最基本的特征 由创建而产生,由调度而执行,由撤销而消亡
- 并发性 多个进程实体同时存在于内存中,且能够同一时间段内同时运行
- 独立性 进程实体是一个能够独立运行、独立获取资源和独立接受调度的基本单位,凡为建立PCB(进程控制块)的程序都不能够作为一个独立的单元参与运行
- 异步性 按照各自独立、不可预知的速度向前推进
线程
一个进程内存在多个线程,线程是最小的调度单位,进程是最小的资源分配单位
线程与进程之间的区别
- 进程基本上相互独立,而线程存在与进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间的通信比较复杂:
- 同一台计算机的进程通信成为IPC
- 不同计算机之间的进程通信需要通过网络、共同遵守HHTP等协议
- 线程通信相对比较简单,因为他们共享进程内的内存,一个例子是多个线程,可以访问同一个共享变量
- 线程更加轻量,线程上下文切换成本一般要比进程切换上下文的成本低
并行与并发
并行(parallel)
并行指的是两个或多个事件在同一时刻 同时发生,
举例: 5个人在7点59分59秒同时进教室门
并发(concurrent)
并发指的是两个或多个时间在同一时间段内同时发生
距离:5个人在7点58分到七点59分这个时间段内进教室门
多线程一定能提高效率嘛?
多线程不一定能提高效率,如果CPU是单核的,那么多线程的速度不如单线程,因为单线程CPU需要花费时间进行上下切换
Java线程
创建与启动
创建方法
-
使用Thread类直接进行创建线程
Thread t1 =new Thread(){ @Override public void run() { System.out.println( super.getName() + "hello word"); } };
-
使用Runnable 接口进行创建
Runnable runnable =new Runnable() { @Override public void run() { System.out.println("111"); } }; Thread t2 = new Thread(runnable);
Runnable可以是用lambda表达式创建,该接口存在 @FunctionalInterface注解,表明可以使用表达式进行一个创建,如下:
Runnable t3 = ()->{ System.out.println("123456"); };
Thread 与 Runnable之间的关系
我们查看源码,发现在Thread类中存在一个Runnable对象,在test2方法中,其调用了Thread的有参构造,在其有参构造中,最后调用的是其run方法
首先是Thread 的有参构造这个有参构造的参数类型是Runnable类其底层调用的是init()方法如图:
而init()方法将targe这个runnable类付给了自己内部的Runnable对象,
最后在其内部重写run方法:
而test1中由于targe是null,则自行重写,也就是我们重写的run方法
小tips
使用Runnable更容易与线程池等高级API结合起来,使任务类脱离了Thread继承体系,更加灵活
使用FutureTask 方法 配合Thread创建线程
FutureTask<Integer> fa =new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("fa");
return 100;
}
});
Thread test4 =new Thread(fa);
test4.start();
System.out.println(test4.getName()+ " ==" + fa.get());
FutureTask 实现了 RunnableFuture 接口,而RunnableFuture 继承了Runnable 和 Future 接口Future接口主要作用是为了获取线程的某些信息,在里面可以查看线程是否被取消线程线程是否正常执行等操作。
线程上下文切换
原因:
- 线程的CPU时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用sleep yield wait join park synchronized lock等方法
在进行上下文进行切换时,需要保存当前的线程状态同时还需要恢复另一个线程的状态这个过程需要使用程序计数器来进行记录,这个程序计数器是私有的
频繁的线程切换会影响其效率
run()与start()的区别
run内是需要执行的代码而start是真正的创建线程、启动线程,而run方法只是执行代码,如果直接调用run方法,不能实现多线程的效果且这个run方法最终是由主线程进行调用。
总结就是 start 是真正的创建线程、启动线程,run方法只是作为一个普通的方法进行调用且是被主线程调用。
start()方法启动线程时会自动调用run()方法,run()方法必须是public 访问权限的且不需要返回值
Sleep 与 yield方法比较
Sleep 方法
- 调用Sleep方法会让当前线程从Running状态进入到Time Wating 状态
- 其他线程可以使用interrupt 方法打断正在睡眠的线程,此时sleep方法会抛出 InterruptException异常
- 睡眠结束后线程未必会立刻执行
- 建议使用TimeUtil 的sleep方法代替Thread的sleep方法来获取更好的可读性
- 在while(ture) 循环中使用sleep 可以降低cpu的利用率方式cpu空转 这种场景适用于无需加锁的情况下
yield
- 调用yield方法会让当前线程从Running状态进入到Runnable状态,然后调度其他同优先级的线程这是如果没有同优先级的线程那么不能保证让当前线程暂停的效果
- 具体实现依赖于操作系统的任务调度器
线程优先级
-
设置优先级:
使用Thread中的setPriority()方法进行设置线程的优先级,查看源码可以知道,线程的优先级为1~10,最低为1,最高为10
设置有优先级代码:
thread.setPriority(优先级);
-
线程优先级会提示调度器优先调度该线程,但是仅仅是一个提示作用,调度器可以忽略优先级
-
如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但是cpu闲暇时,优先级基本上没有作用
-
查看优先级的效果:
代码:
Runnable r1 =()->{ int count = 0; for (; ;){ System.out.println("-->"+ count++); } }; Runnable r2 =()->{ int count = 0; for (; ;){ System.out.println(" -->"+ count++); } }; Thread t1 =new Thread(r1,"t1"); Thread t2 =new Thread(r2, "t2"); t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start();
运行后立即停止,查看效果:
可以看到优先级高的已经累加到72395了,但是优先级低的只是累加到63833说明优先级对调度是有一定影响的。
join
作用: 等待线程结束
使用方法:
在线程开始运行后直接调用他的join方法即可
案例:
static int a =0;
public static void main(String[] args) {
k();
}
public static void k (){
Runnable r =()->{
System.out.println("线程启动");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 10;
System.out.println("线程结束");
};
Thread r1 = new Thread(r, "r1");
r1.start();
System.out.println(a);
}
输出结果为
0
线程启动
线程结束
原因是新的线程和主线程不影响,主线程在输出a时 另一个线程 r1还在睡眠,所以输出结果为0
对此我们进行修改,让休眠结束后在进行一个输出
在==r1.start()==后加上r1.join() 后即可
再次运行,结果如下
线程启动
线程结束
10
且在线程启动输出后由明显的卡顿现象,着说明主线程在等待线程r1执行结束,等执行结束后a 已经从0变成了10
Join 中有参数
方法:
join(long time)
解释:
表示join 做多等待多久如果超过这个时间就不再进行等待,如果在这个时间内已经完成了,那么也不再进行等待
案例一: 超过了最大等待时间
static int a =0;
public static void main(String[] args) {
k();
}
public static void k (){
Runnable r =()->{
System.out.println("线程启动");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 10;
System.out.println("线程结束");
};
Thread r1 = new Thread(r, "r1");
r1.start();
try {
r1.join(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}
这里我们时最大等待时间时1s
输出结果为:
线程启动
0
线程结束
显然a=10 这部操作没有进行说明超过了最大等待时间就直接跳过
案例二: 在最大时间范围内结束
这次我们设置最大等待时间为3s,睡眠时间为1s,随后查看输出结果:
线程启动
线程结束
a=10,花费时间为:2046
发现其在差不多2s的时候就已经结束了,不会再等待到三秒
interrupt
作用:
打断线程
1. interrupt 打断sleep wait join 后会清除打断标记,但是会抛出异常
判断是否被存在打断标记: isInterrupted() 方法如果存在被打断的标记 就返回true
需要注意的是 还存在一个方法,是interrupted(),这个方法在返回标记后并将标记给清除
Runnable r = () -> {
System.out.println("线程启动");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 10;
System.out.println("线程结束");
};
Thread r1 = new Thread(r, "r1");
r1.start();
Thread.sleep(1000);
r1.interrupt();
System.out.println("是否被打断:" + r1.isInterrupted());
我们将先启动线程,并且让主线程sleep 1s防止一开始主线程直接进行并且在没有开始就直接打断sleep。
输出结果如下:
线程启动
是否被打断:false
线程结束
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at Threads.Test02.lambda$k$0(Test02.java:21)
at java.lang.Thread.run(Thread.java:748)
显然被打断的标记已经被清除了,,但是通过异常的方式告知我们他已经被打断了
2. 打断正常的程序
Thread r1 =new Thread( ()->{
while (true){
}
},"r1");
Thread.sleep(1000);
r1.start();
System.out.println("开始打断");
r1.interrupt();
System.out.println("r1线程是否存活?:"+r1.isAlive());
这里我们创建了一个线程这个线程内部时一个空循环,我们随后在进行打断,打断后发现线程还在运转,我们通过使用线程中的方法查看这个线程是否存活 ,isAlive()方法用于判断是否存活
,我们调用后发现线程仍然存活,那么线程如何终止?
我们可以在线程中查看是否存在被打断的标记,如果存在,那么就结束任务。
改进:
Thread r1 =new Thread( ()->{
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted){
break;
}
}
},"r1");
Thread.sleep(1000);
r1.start();
System.out.println("开始打断");
r1.interrupt();
System.out.println("r1线程是否存活?:"+r1.isAlive());
这里的currentThread()
方法时获取当前的线程,随后判断这个线程是否存在又被打断的标记,如果找到了,那么这个while就会被终止,随后这个线程就会被回收
改进:
Thread r1 =new Thread( ()->{
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted){
break;
}
}
},"r1");
Thread.sleep(1000);
r1.start();
System.out.println("开始打断");
r1.interrupt();
System.out.println("r1线程是否存活?:"+r1.isAlive());
这里的currentThread()
方法时获取当前的线程,随后判断这个线程是否存在又被打断的标记,如果找到了,那么这个while就会被终止,随后这个线程就会被回收