进程是指一种正在运行的程序,有自己的地址空间.
并行: 在同一个时刻,有多个指令在多个处理器上同时执行---------多个CPU同时执行多个任务
并发: 在同一个时刻只能有一条指令执行,但多个进程指令被快速的轮换执行---------一个CPU(采用时间片)同时执行多个任务
线程是指进程内部的一个执行单元,它是程序中一个单一的顺序控制流程,又称轻量级进程
若在一个进程中同时运行了多个线程,用来完成不同的工作,则为多线程
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 | 线程可以看成轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同事运行多个任务(程序) | 在同一个应用程序有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源 |
包含关系 | 没有线程的进程看为单线程,如果一个进程内拥有多个线程,则执行过程不是一条线,二十多条线共同完成 | 线程是进程的一部分,线程有时候被称为轻量级进程 |
1. 继承Thread类
① 定义一个类,继承Thread类,重写run方法
② 创建线程的对象
③ 启动线程,调用线程的start()方法
2. 实现Runnable接口
① 定义一个类实现Runnable的接口,重写run()方法
② 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
③ 启动线程,调用线程的start()方法
3. 实现Callable接口
public class RandomCallable implements Callable{
@Override
public Integer call() throws Exception {
Thread.sleep(5000);
//throw new IOException();
return new Random().nextInt(10);
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建线程对象
Callable callable = new RandomCallable();
FutureTask task = new FutureTask(callable);
Thread thread = new Thread(task);
//启动线程
thread.start();
//获取返回值
System.out.println(task.isDone());
Integer num = task.get();//必须等线程执行完毕后,才能得到返回值,线程在此会阻塞
System.out.println(num);
System.out.println(task.isDone());//线程是否执行完毕
}
}
4. 使用线程池(如:Executor)
1. 原子性
原子性是指一个操作要么全部执行,要么全部都不执行
经典案例:银行转账问题:
A和B同时向C转账10万,C原本的账户有20万,如果转账操作不具有原子性,会出现A在向C转账,此时C应该有30万,但还未及时将30万写入到C账户的时候,B的转账请求就发过来了,发现C的账户还是20万,再将其写入C的账户,然后A的转账操作继续,这时C的最终余额为30万,而不是40万.
2. 可见性
可见性是指多个线程并发访问共享变量时,一个线程对共享变量的修改,其他线程能够立即看到.可见性是最容易被忽略和理解错误的一点.
3. 顺序性
顺序性是指程序执行的顺序按照代码的先后顺序执行.
1.保证原子性
① 锁和同步
使用锁可以保证同一时间只有一个线程能拿到锁,也就是保证同一时间只有一个线程能执行申请锁和释放锁之间的代码.
使用同步方法或同步代码块
非静态同步方法—锁住的是当前实例
静态同步方法—锁住的是该类的Class对象
静态代码块—锁住的是synchronized关键字后的对象
② CAS
Java中提供了对应的原子操作类来实现该操作,并保证原子性,其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。AtomicInteger使用方法如下。
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
new Thread(() -> {
for(int a = 0; a < iteration; a++) {
atomicInteger.incrementAndGet();
}
}).start();
}
2.保证可见性
使用volatile关键字保证可见性,当使用volatile修饰某个变量时,他会保证对该变量的修改会立即被更新到内存中,并且将其他缓存中对该变量的缓存设置成无效,因此其他线程需要读取该值时必须从主存中读取,从而得到最新的值.
3.保证顺序性
通过synchronized和锁保证顺序性
1. 死锁是什么?
死锁: 多个进程在运行过程中因争夺资源而造成的的一种僵局,当进程处于这种僵局状态时,若无外力作用,他们都将无法前进.
2. 产生死锁的原因
a:竞争资源
① 可剥夺资源-----进程在获得资源后,该资源可以被其他进程或系统剥夺,其中CPU和主存都属于可剥夺资源
② 不可剥夺资源—当系统把资源分配给某进程后,再不能强行收回,只能在晋城用完后自行释放,例如打印机,磁带机等.
b:进程间推进顺序非法
3. 产生死锁的条件
① 互斥使用 -----当资源被一个线程使用时,别的线程不能使用
② 不可抢占-----资源请求者不能强制从资源占有者的手中夺取资源,资源只能自己主动释放
③ 请求和保持-----当资源请求者在请求其他资源的同事,保持对原有的资源占有
④ 循环等待-----存在一个等待队列,p1占有p2的资源,p2占有p3的资源,p3占有p1的资源,这样就成了等待环路
4. 解决死锁的方式------只要打破死锁原因的一种就可以有效地预防死锁
1) 预防死锁
①以确定的顺序获得锁
②超时放弃
2) 避免死锁
银行家算法
银行家算法是避免死锁的一种重要方法,防止死锁的机构只能确保上述四个条件之一不出现,则系统就不会发生死锁。通过这个算法
可以用来解决生活中的实际问题,如银行贷款等。
程序实现思路银行家算法顾名思义是来源于银行的借贷业务,一定数量的本金要应多个客户的借贷周转,为了防止银行家资金无法周
转而倒闭,对每一笔贷款,必须考察其是否能限期归还。在操作系统中研究资源分配策略时也有类似问题,系统中有限的资源要供多个进
程使用,必须保证得到的资源的进程能在有限的时间内归还资源,以供其他进程使用资源。如果资源分配不得到就会发生进程循环等待资
源,则进程都无法继续执行下去的死锁现象。
把一个进程需要和已占有资源的情况记录在进程控制中,假定进程控制块PCB其中“状态”有就绪态、等待态和完成态。当进程在处于等待态时,表示系统不能满足该进程当前的资源申请。“资源需求总量”表示进程在整个执行过程中总共要申请的资源量。显然,每个进程的资源需求总量不能超过系统拥有的资源总数, 银行算法进行资源分配可以避免死锁。
3) 监测死锁(Jstack命令,Jsonsole工具)
① 为每个进程和每个资源指定一个唯一的号码
② 建立资源分配表和进程等待表
4) 解除死锁
剥夺资源:从其他进程剥夺足够数量的资源给死锁进程,以解除死锁
撤销进程:可直接撤销死锁进程或者撤销**代价(优先级,运行代价,进程的重要性和价值等)**最小的进程,知道有足够的的资源可用,死锁状态消除为止.
1.互斥同步
互斥同步----同步是指在并发访问数据时,保证共享数据同一时刻制备一个线程使用,互斥是手段,同步是目的.
2.非阻塞同步
线程的生命周期:新生状态,就绪状态,运行状态,阻塞状态,死亡状态
新生状态:用new关键字创建一个线程对象后,该线程对象处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态
**就绪状态:**处于就绪状态的线程已经具备了运行的条件,但是还没分配到CPU,处于"线程就绪队列",等待系统为其分配CPU.就绪状态并不是执行状态,当系统选定一定等待执行的Thread对象(线程)后,他就进行执行状态(此动作称为"CPU调度")
运行状态: CPU调度好之后,在运行状态的线程执行自己run()方法中的代码,直到等待某资源而阻塞或者完成任务而死亡.如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态.
阻塞状态: 是指暂停一个线程的执行以等待某个条件发生(资源就绪)
死亡状态: 是线程生命周期的最后阶段.
线程不能进入就绪状态的原因:
① 新建线程,调用start()方法,进入就绪状态
② 阻塞线程,阻塞解除,进入就绪状态
③ 运行线程:调用yield()方法,交出控制权,直接进入就绪状态,没有其他等待执行的线程,马上恢复执行
④ 运行线程;jvm将CPU资源从本线程切换到其他线程
导致阻塞的原因:
① millsecond()方法使当前线程休眠,进入阻塞状态,当指定时间到了之后,线程进入就绪状态,不会释放持有的对象锁,没有其他等待的线程,马上恢复执行
② 执行wait()方法,使当前线程进入阻塞状态,当使用nofity()方法唤醒这个线程后,它进入就绪状态,会释放持有的锁
③ 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。(在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行)
④ join()方法当某个线程等待另一个线程执行结束后,才能继续执行
线程死亡的原因:
① 一个是正常运行的线程完成了它run()方法内的全部工作
② 线程被强制终止,通过执行stop()或destory()方法来终止一个线程(这两个方法已经被废弃,不推荐使用),当一个线程进入死亡状态以后,就不能再回到其他状态
③ 线程抛出未捕获的异常
线程安全