目录
1.线程的相关概念
(1)进程、线程
进程:是正在运行的应用程序,拥有独立的内存单元,是资源分配的基本单位;
线程:线程是进程的一条执行路径,多个线程共享内存资源,是cpu调度的基本单位;
具体来说,每个线程都有自己的栈空间,但共享一个堆内存;
(2)并行、并发
并行:多个CPU同时执行多个任务。
并发:一个CPU(采用时间片)同时执行多个任务。
(3)线程的生命周期
新建New 已经创建好线程,但还没有启动;
可运行Runnable 正在等待CPU时间片(Ready)或者正在运行(Running)
阻塞Blocking 等待获取一个排它锁,如果其他线程释放了锁就会结束此状态
无限期等待Waiting 等待其他线程显示地唤醒 Object.notify() notifyAll() 也可能是因为join进入等待的,那就要等被调用的线程执行完毕
限期等待Timed Waiting 一定时间后系统自动唤醒 Object.wait()或join()设置了时间参数的,或者sleep()
死亡terminated 线程完成任务自动结束,或产生异常而结束
2.多线程的实现方式
(1)继承Thread类
继承Thread类,重写run方法,方法内是线程要执行的操作;调用start方法启动线程
Thread t2 =new Thread(){
@Override
public void run() {
//线程要执行的操作
}
};
t2.start();
(2)实现Runnable接口
创建MyRunnable类实现Runnable接口,重写run方法,实例化对象,传给Thread构造器,创建线程;Runnable接口的实现类相当于一个任务,把任务对象传递给线程,由线程执行任务
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//要执行的操作
}
});
t2.start();
(3)实现Callable接口
创建MyCallable类实现Callable接口,重写call方法,实例化对象,传给FutureTask构造器,创建任务对象,并将任务对象传给Thread构造器,创建线程
Callable和Runnable的区别:Callable有返回值,返回值类型定义为泛型,返回值由FutureTask封装,通过调用FutureTask对象的get方法获得返回值
FutureTask<Integer> task = new FutureTask(new Callable<Integer>(){
@Override
public Integer call(){
//操作
return res;
}
});
Thread t1 = new Thread(task);
t1.start();
Integer res = task.get();
(4)创建线程池
线程池中提前创建了多个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。避免了频繁的创建和销毁线程。
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(new Task());//分配任务并执行任务
service.shutdown();
class Task implements Runnable{
@Override
public void run(){
//操作
}
}
3.解决线程安全问题
(1)同步代码块
把操作共享数据的代码都放在同步代码块内;锁是唯一的,所有线程共用一把锁;
synchronized(锁){
//操作数据
}
例:卖票
对共享数据ticket的操作包括if判断和ticket--;所以把整个if-else语句包在synchronized里面,不能把while循环包进去,否则一个线程拿到锁后会卖完所有票才释放。
class Ticket1 implements Runnable{
private int ticket=10;
@Override
public void run() {
while (true){
synchronized (this){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println("余票:"+ticket);
}else {
break;
}
}
}
}
}
(2)同步方法
把方法加上synchronized关键字修饰,默认的锁是this或者当前类
public synchronized void fun(){
}
例:卖票
class Ticket2 implements Runnable{
private int ticket = 10;
@Override
public void run() {
while (true){
boolean flag=print();
if(!flag){
break;
}
}
}
public synchronized boolean print(){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println("余票"+ticket);
return true;
}else{
return false;
}
}
}
(3)显示锁ReentrantLock
在操作共享数据的前后分别加锁、解锁
ReetrantLock l = new ReentrantLock();
l.lock();
//操作
l.unlock();
例:卖票
class Ticket3 implements Runnable{
private int ticket = 10;
private ReentrantLock lk = new ReentrantLock();
@Override
public void run() {
while (true){
lk.lock();
try {
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
System.out.println("余票"+ticket);
}else{
break;
}
}finally {
lk.unlock();//将解锁放到finally块中,否则可能因为break提前退出,导致锁无法释放
}
}
}
}
4.多线程间的通信
(1)生产者、消费者问题
若物品达到最大容量,生产者自动wait(),等待消费者取走物品后,被notify()唤醒,再生产
若当前没有物品可消费,消费者自动wait(),等待生产者产出物品后,被notify()唤醒,再消费
说明:wait常用于以下情境:
thread1: if(条件不满足) wait() 挂起当前线程,此时会释放锁
其他thread: do something …… 使得条件满足,notify() 唤醒线程1
注:sleep和wait的区别
sleep是Thread类的静态方法,wait是Object类的方法
sleep不会释放锁,wait会释放锁(如果没有释放锁,那么其它线程就无法进入对象
的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起
的线程,造成死锁)
(2)死锁
两个或多个线程,由于资源竞争或通信造成的一种阻塞现象,永远在互相等待,无法推进。
死锁的条件:互斥访问共享资源、保持和请求、不可剥夺、循环等待