多线程
一,多线程的概念
进程:正在运行的程序
线程:线程是进程中的一条执行路径(执行单元)
多线程程序:可以开启多条线程的程序
多线程的好处:让程序中的多个任务可以并发执行
CPU执行权的分配方式:
分时调度:CPU平均分配CPU的执行权
抢占式调度:任务抢夺CPU的执行权(随机性),Java中的多线程就是采用这种方式的
注:只有主函数的Java程序也有两条线程:主函数所在的主线程、垃圾回收器所在的垃圾回收线程
二,创建线程的方式
方式一:继承Thread
步骤:
定义一个类继承Thread
重写Thread类的run方法
注:run方法中的功能就是多线程中要并发执行的任务
创建子类对象
调用Thread类中的start方法,来启动线程
注:
start方法的作用开启线程调用重写后的run方法
同一个线程对象只能开启一次,否则会发生
IllegalThreadStateException
不合法的线程状态异常只有程序中的所有线程全部执行完毕,JVM才会结束
方式二:实现Runnable
步骤:
定义一个类实现Runnable
重写run方法
创建实现类的对象
创建Thread类的对象,并将Runnable实现类的对象作为参数传入到构造函数中
调用Thread的start方法启动线程
以上两种方式都可以使用匿名内部类来实现:
new Thread(){ @Override public void run() { for(;;){ try { Thread.sleep(1); } catch (InterruptedException e) { } System.out.println("张三说:"+strs[r.nextInt(strs.length)]); } } }.start(); new Thread(new Runnable(){ @Override public void run() { for(;;){ try { Thread.sleep(1); } catch (InterruptedException e) { } System.out.println("李四说:"+strs[r.nextInt(strs.length)]); } } }).start();
三,线程安全问题
什么是线程安全的:
多个线程操作同一共享数据,最终的结果与单线程操作这一数据的结果一致,就称为线程安全
线程安全问题出现的原因:多个线程操作同一共享数据
解决办法:使用同步技术
同步的关键字:
synchronized
同步代码块
格式:将存在线程安全问题的代码包裹在同步代码块中
public void run(){ synchronized(对象){ 存在线程安全问题的代码 } }注:同步代码块中的对象称为锁对象,这个对象的类型是任意的,但是要保证这个对象是唯一的
同步函数
格式:将存在线程安全问题的代码封装在单独的方法中
public synchronized 返回类型 方法名(参数){ 存在线程安全问题的代码 }注:
在实现Runnable的方式中,使用同步函数也有锁对象,锁对象是this
在继承Thread的方式中,使用同步函数是无效的,因为每个线程对象都有各自的this作为锁,此时需要使用静态同步函数,静态同步函数中的锁对象是
类名.class
同步技术的原理:
所有线程共同抢夺CPU的执行权,谁抢到了谁就
判断锁
是否存在,如果锁存在则获取锁
并进入同步代码块中,如果锁不存在则处于阻塞状态其他没有抢到CPU执行权的线程会处于阻塞状态,等待抢夺到锁对象的线程执行完毕
抢夺到锁对象的线程在执行完同步中的所有代码后,会执行
释放锁
的动作。
四,线程池
概念:线程池的本质就是一个容器,在该容器中存放着若干个线程对象,当有任务需要使用线程时,直接从池中获取线程对象而不再额外创建,当任务执行完毕后,会将使用完的线程对象归还到池中。
线程池的好处:
提高了代码的复用性。每个线程对象在使用完毕后都会归还到池中,都可以复用。
提高了程序的响应速度,减少了线程的创建和销毁的次数
提高了对线程的管理
使用线程池要使用线程池的核心类
Executors
,它是线程池的工厂类,用于创建线程池
Executors
类中提供了生产线程池的静态方法newFixedThreadPool(int nThreads)
,用于创建一个固定数量的线程对象的线程池,这个方法会返回一个用于开启线程的对象ExecutorService
。
ExecutorService
中提供了submit(Runnable r)
,调用此方法会从池中获取线程对象并开启线程执行参数Runnable实现类中重写的run方法。
注:即使线程对象已经全部归还,线程池仍然处于开启状态,因为线程池在等待可能还有其他任务需要使用到池中的线程对象。我们可以通过调用
shutdown()
来手动关闭线程池,一旦线程池关闭了,就不能再从池中获取线程对象并使用,否则会发生RejectedExecutionException