Java基础第十九天总结——多线程2
目录:
一、线程的声明周期
二、线程的同步
三、线程的通信
四、JDK5.0新增线程创建方式
/*-------------------------分割线-----------------------*/
一、线程的声明周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Jaba语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下五中状态:
1.新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3.运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
5.死亡:线程完成了它的全部工作或线程被提前强制性的终止或出现异常导致结束
二、线程的同步
synchronized的使用方法
Java对于多线程的安全问题提供了专业解决方式:同步机制
1.同步代码块:
sychronized(对象){
//需要被同步的代码;
}
2.sychronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public sychronized void show(String name){
......
}
同步机制中的锁
同步锁机制:
synchronized的锁是什么?
> 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
> 同步方法的锁:静态方法(类名.class)、非静态方法(this)
> 同步代码块:自己指定,很多时候也是指定为this或类名.class
注意:
> 必须确保使用同一个资源的多个线程共用一把锁,保证共享资源的安全
> 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)
同步的范围
一、如何找问题,即代码是否存在线程安全?(重要)
1.明确哪些代码是多线程运行的代码
2.明确多个线程是否有共享数据
3.明确多线程运行代码中是否有多条语句操作共享数据
二、如何解决呢?(重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中。
三、切记:
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能
释放锁的操作
> 当前线程的同步方法、同步代码块执行结束
> 当前线程在同步代码块、同步方法中遇到break、return终止了改代码快、该方法的继续执行。
> 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
> 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁的操作
> 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yied()方法暂停当前线程的执行
> 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用supend()和resume()来控制线程
线程的死锁问题
死锁
互相占用对方所需要的同步资源、形成死锁。出现死锁不会有提示,只是所有线程都处于阻塞状态,无法继续。
解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
Lock(锁)
同步锁使用Lock对象充当
java.utilconcurrent.locks.Lock接口是孔志多个线程对共享资源进行访问的工具。线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的孔志中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
synchronized与Lock的对比
1.Lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchroized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
有限使用顺序:
Lock->同步代码块(进入方法体,分配了资源)-> 同步方法(在方法体外)
三、线程的通信
wait()与notify()和notifyAlll()
wait():另当前线程挂起并放弃cpu、同步资源并等待其他线程调用notify或notify方法换下,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒等待同步资源的线程中优先级最高者
notifyAll():唤醒等待资源的所有线程
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.lllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明
wait()方法
对象名.wait()
使当前线程进入等待状态,直到另一个线程对该对象发出notify或notifyAll为止
调用方法的必要条件:当前线程必须具有对该对象的监控全(加锁)
调用此方法后,当前线程将释放对象监控权,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行
notify()/notifyAll()
在当前线程中调用方法:对象名.notify()
功能:唤醒等待该对象监控权的一个/所有线程
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
四、JDK5.0新增线程创建方式
新增方式一:实现Callble接口
与使用Runnable相比,Callble功能更强大些
相比run方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
Futrue接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
FutrueTask是Futrue接口的唯一的实现类
FutrueTask同时实现了Runnable,Futrue接口。它既可以作为Runnable被线程执行,又可以作为Futrue得到Callable的返回值
新增方式二:使用线程池
背景:并发情况下的线程,经常创建销毁使用量特别大的资源,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时获取,使用完放回池中类似公共bus
好处:
提高响应速度
降低资源消耗
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
。。。