1.什么是并发与并行
并行:指两个或多个事件,在同一时刻发生。
并发:指两个或多个事件,在同一时间段内发生
2.什么是线程与进程
进程
进程是正在运行的程序的实例。
进程是线程的容器,及一个进程中可以开启多个线程。
线程
线程是进程内部的一个独立执行单元
一个进程可以同时并发运行多个线程
多线程
多个线程并发执行。
3.线程创建
java中线程有四种创建方式:
1.继承Thread类
其中MyThread就是继承了Thread的类,并重写了run方法的,使用start启动了线程
2.实现Runnable接口
其中MyRunnable就是实现了Runnable接口的类,并重写了run方法的,使用start启动了线程
3.实现Callable
其中MyCallable就是实现了Callable接口的类,并重写了call方法的并指定返回值,使用start启动了线程
4.线程池-Executor
线程池线程类关系图如下:
使用了execute()方法执行了线程任务
小结
1.实现接口和继承Thread类比较
1.接口更适合多个相同的程序代码的线程去共享一个资源。
2.接口可以避免java中的单继承的局限性。
3.接口代码可以被多个线程共享,代码和线程独立。
4.线程池只能放入Runable或Callable接口的线程,不能直接放入继承Thread的类。。
2.Runnable和Callable接口比较
相同点:
都是接口;
都是用来编写多线程程序;
都需要调用Thread.start()启动线程;
不同点:
实现Callable接口的的线程可以返回执行结果;Runnable不能。
Callable接口的call()方法允许抛出异常;Runnable不能。
实现Callable接口的线程可以调用Future.cancle取消执行,可以动态监控线程的执行,Runnable不能。
注意点:
Callable接口支持返回执行接口,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取到接口;当不调用该方法时,主线程不会阻塞。
扩展:
java中程序每次至少启动2个线程,一是main线程,一个是垃圾收集线程。
4.线程生命周期
1.新建状态
new关键字创建了一个线程之后,该线程就处于新建状态
JVM为线程分配内存,初始化成员变量
2.就绪
当线程对象调用了start()方法之后,该线程处理就绪状态
JVM为线程创建方法栈和程序计算器,等待线程调度器调度
3.运行
就绪状态的线程获得CPU资源,开始运行run()方法,该线程进入运行状态
4.阻塞状态
发生如下状况时,线程进入阻塞状态
1.线程调用sleep()方法主动放弃所占用的处理器资源
2.线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
3.线程试图获得一个同步锁,但该同步锁正在被其他线程持有。
4.线程在等待某个通知(notify)
5.程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以尽量避免使用。
5.死亡
1.run()或call()方法执行完成,线程结束。
2.线程抛出为捕获异常
3.调用该线程的stop()方法来结束该线程,该方法容易导致死锁。
5.线程安全问题
什么是线程安全问题
多个线程同时运行实现了Runnable接口的类,程序每次运行结果都和单线程运行结果一致,其他变量的值和预期值一样,此为线程安全,反之则线程不安全。
问题分析
线程安全问题都是由全局变量及静态变量引起的。
若每个线程只对全局变量,静态变量只读不写,则这个变量是线程安全的。
若由多个线程同步执行写操作,一般都会考虑线程同步,否则就会出现线程安全问题。
线程安全问题根本原因
1.多个线程在操作共享的数据;
2.操作共享数据的线程代码有多条;
3.多个线程对共享数据有写操作;
线程安全问题的解决方案
线程同步
为了解决每个线程能够正常执行共享资源操作,java引入了7种线程同步机制。
1.同步代码块(synchronized)
例如:Object obj=new Object();
synchronized(obj){}
每个线程只有一个可以拿到obj,进入该方法体执行完后释放obj
2.同步方法(synchronized)
private stasic synchronized(当前对象.class) void cscscs(){};
private synchronized(new 当前对象) void cscscs(){};
3.同步锁(ReenreantLock)
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.
同步锁方法:
public void lock():加同步锁
public void unlock():释放同步锁
调用了lock方法需要在finally块里调用unlock方法不然会出现死锁
小结
4.特殊域变量(volatile)
5.局部变量(ThreadLocal)
6.阻塞队列(LinkedBlockingQueue)
7.原子变量(Atomic*)
6.线程死锁
所谓死锁就是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进
死锁必要条件
1.互斥条件
进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有;此时若有其他进程请求该资源,则请求进程只能等待.
2.不可剥夺条件
进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放
3.请求与保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
4.循环等待条件
存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求
死锁处理
预防死锁
通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的发生
破坏死锁必要条件
互斥条件是无法破坏的.
破坏"占有并等待"条件
1.一次性分配该进程所需要的全部资源,使其不需要再去申请其他资源
2.每个进程需要申请新的资源时,都需要释放它所占有的资源
破环"不可剥夺"条件
1.如果占有某些资源的进程进一步资源请求被拒绝,则该进程不许释放最初的资源,如果有不要,可以再次请求这些资源
2.如果一个进程请求另一个被占有的资源,则操作系统可以抢占另一个进程,要求它释放资源;只有在任意两个 进程的优先级不同的情况下,方法二才可以预防死锁
破坏"循环等待"条件
将系统中所有的资源统一编号,要求进程申请资源必须按照顺序提出.
避免死锁
在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生
避免死锁并不限制产生死锁条件的存在,应为即使存在也不一定发生
有序资源分配法:
1.必须为所有资源统一编号,
2.同类资源必须一次申请完,
3.不同资源必须按顺序申请
银行家算法
是一个避免死锁的著名算法
顺序加锁
限时加锁
检测死锁
运行程序在运行过程中发生死锁,但可以设置检测机构及时检测死锁的发生,并采取适当措施加以清除
解除死锁
当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来.
7.线程通信
多线程并发执行时,默认情况下cpu随机切换线程,有时需要按照我们的规律执行,就需要用到线程之间协调通信.
Thread类常用方法
构造方法 说明
Thread() 分配新的Thread对象
Thread(Runnable target) 分配新的Thread对象,target为run()方法被调用的对象。
Thread(Runnable target,String name) 分配新的Thread对象,target为run()方法被调用的对象。name为新线程名 称。
方法 说明
void run() 执行任务操作的方法
void start() 使该线程开始执行,java虚拟机调用该线程的run()方法。
void sleep(long millis) 指定毫秒数,让线程休眠(暂停执行)。
String getName() 返回线程的名字
int getPrority() 返回线程优先级
void setPrority(int newPrority) 更改线程优先级
static Thread currentThread() 返回当前正在执行的线程对象的应用
boolean isAlive() 测试线程是否处于活动状态
void join() 等待当前线程终止
void interrupt() 中断线程
void yield() 暂定当前正在执行的线程对象,并执行其线程,礼让
Java提供了如下3个方法实现线程之间的通信。
wait()方法:调用wait()方法会挂起当前线程,并释放共享资源的锁。
notify()方法:调用任意对象的notify()方法会在因调用该对象的wait()方法而阻塞的线程中随机选择一个线程 解除阻塞,但要等到获得锁后才可以真正执行。
notifyAll()方法:调用了notifyAll()方法会将因调用该对象的wait()方法而阻塞的所有线程一次性全部阻塞。
wait和sleep的区别
wait和notify的区别