线程的创建和使用
Thread类
- 自定义线程类继承
Thread类
- 重写
run()
方法,编写线程执行体 - 创建线程对象,调用
start()
方法启用线程
常用方法:start\run(需重写)\currentThread\getName\setName\yield(争球)\join(球权转换)\stop(不建议使用)\sleep(long milltime)\isAlive\getPriority\setPriority(优先级是概率上的)
Runnable接口
- 创建一个实现了
Runnable
接口的类 - 实现类区实现
Runnable
中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到
Thread
类的构造器中,创建Thread类的对象 - 通过
Thread
类的对象调用start()
开发中优先选择实现Runnable接口方式
原因:1.实现的方式没有类的单继承的局限性 2.实现的方式更适合来处理多个线程有共享数据的情况
线程的声明周期、同步、通信
周期:新建、就绪、运行、阻塞、死亡
通过同步机制解决线程的安全问题:
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
//说明:1.操作共享数据的代码即为需要被同步的代码
//2.同步监视器(锁),任何一个对象都可以充当为锁,要求多个线程必须使用同一把锁
//3.在继承Thread方法中,同步监视器可以使用XXXXX.class(类也是对象),因为类只会加载一次所以是同一把锁
//4.在实现Runnable接口方法中,同步监视器可以使用this
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
public void run(){
while(true){
show();
}
}
private static synchronized void show(){
//需要被同步的代码
}
//1.同步方法仍然涉及到同步监视器,只是不需要显示声明
//2.非静态的同步方法,同步监视器是:this
// 静态的同步方法,同步监视器是:当前类本身
解决了安全问题,但是操作同步代码时只能有一个线程参与,其他线程等待相当于是一个单线程的过程,效率低
方式三:Lock锁(jdk5.0新增)
private ReentrantLock lock = new ReentranLock();//参数为true则为FIFO形式
@override
public void run(){
while(true){
//调用lock
try{
lock.lock();
//需要被同步的代码
}finally{
lock.unlock();
}
}
}
synchronized与lock的不同:
synchronized执行完相应的代码逻辑后自动的释放同步监视器,而lock需要手动的启动同步,同时结束同步也需要手动的实现
线程通信方式
涉及到的三个方法:
- wait():当前进程进入阻塞状态,并释放同步监视器
- notify():唤醒wait优先级最高的一个进程
- notifyAll():唤醒所有进程
这三个方法必须使用在同步代码块或同步方法中
这三个方法的调用者必须是同步代码块/同步方法的同步监视器
这三个方法是定义在java.lang.Object方法中
应用一:交替输出
public void run(){
//创建变量
while(true){
synchronized(this){
//唤醒操作
notify()/notifyAll();
//判断
if(xxx > xxx){
//需要执行的代码
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}else{
break;
}
}
}
}
sleep()和wait()的异同
相同点:一旦执行方法,就可以让当前进程进入阻塞态
不同点:
1)声明的位置不同,sleep声明在Thread类中,wait声明在Object类中
2)调用的范围不同,sleep随时调用,wait必须在同步代码块/同步方法中
3)关于释放释放同步监视器:若两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
JDK5.0新增线程创建方式
方式三:实现Callable接口
//1.创建一个实现Callable的实现类
class XxxThread implements Callable<Xxx>{
//2.实现call方法,将此线程需要执行的程序声明在call()中
@override
public Xxx call() thorws Exception{
//代码
}
return xxx;//可返回也可不返回
}
public class Xxx{
//3.创建Callable接口实现类的对象
XxxThread xxxthread = new XxxThread();
//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
FutureTask<Xxx> futuretask = new FutureTask<Xxx>(xxxthread);
//5.将FutureTask的对象作为参数传递到Thread的构造器中,创建Thread对象,并调用start()
Thread thread = new Thread(futuretask);
thread.start();
//6.获取Callable中call方法的返回值则使用get(),这是实现类重写的call()的返回值
try {
Object sum = futureTask.get();
System.out.println("call中返回的参数");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
使用Callable接口比Runnable接口强大的原因:
1.call()是可以有返回值的
2.call()可以抛出异常
3.callable支持范型
方式四:实现线程池
class Thread1 implements Runnable{}
class Thread2 implements Runnable{}
public class ThreadPool{
public static void main(String[] args){
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//可以设置线程属性
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
System.out.println(service.getClass());
service1.setCorePoolSize(15);
service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new Thread1());//适用于Runnable
service.execute(new Thread2());
service.submit(Callable callable);//适用于Callable
//3.关闭线程池
service.shutdown();
}
}
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数面试题:创建多线程有几种方式?四种!