二、多线程
1、程序、进程、线程的基本概念
- 程序:是为完成特定任务、用某种语言编写的一组指令的集合,即是一段静态的代码,静态对象
- 进程:是正在运行的一个程序,是一个动态过程:有它自身的产生、存在和消亡的过程——生命周期,进程作为资源分配的单位
- 线程:进程可进一步细化为线程,是程序内部的一条执行路径,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)
2、使用多线程的优点以及不稳定因素
- 线程的优点:
- 提高应用程序的响应
- 提高计算机系统CPU的利用率
- 改善程序结构,将即长又杂的进程分为多个线程,独立运行,便于理解
- 线程的不稳定因素:
- 多个线程操作共享资源时可能会带来隐患
3、线程的生命周期
-
新建:当一个Thread类或子类的对象被声明并创建时,新生的线程对象处于新建状态
-
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行的条件只是没有分配到CPU资源
-
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
-
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
-
死亡:线程完成了它的全部工作或线程被提前强制地终止或出现异常导致结束
4、关于Thread类
- java语言的jvm允许实现多线程,通过java.lang.Thread类来实现
- 通过某个特定的Thread类对象的run() 方法来完成操作,run() 的方法体被称为线程体
- 通过Thread对象的start() 方法来启动线程,而不是run()
- 构造器:
- Thread():创建新的Thread对象
- Thread(String threadName):创建新的Thread对象,并指定Thread名称
- Thread(Runnable runnable):创建指定实现Runnable的线程对象
- Thread(Runnable runnable,String name):创建指定实现Runnable的线程对象,并指定线程名
- 方法:
- void start():启动线程,并执行对象的run()方法
- void run():线程在被调度室执行的操作
- String getName(): 返回线程名称
- void setName(String name): 设置线程名称
- static Thread currentThread():返回当前线程
- static void yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
- join():当某个程序执行流中调用其他线程的join()方法时,正在执行的线程将被阻塞,直到join()方法加入的join线程执行完成为止(低优先级的线程也可以获得执行)
- static void sleep(long millis):令当前活动的线程在指定时间内放弃对CPU的控制,使其他线程有机会被执行,时间到后重新排队,抛出InterruptedException异常
- boolean isAlive(): 判断线程是否还活着
- 其他:
- 属性:
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5
- 内部类:
- Thread.State是一个枚举类,用来保存线程的状态
- 优先级相关方法:
- getPriority() :返回线程优先值
- setPriority(int newPriority) :改变线程的优先级
- 属性:
5、java中线程有几种创建方式?分别是什么?
-
继承Thread类的方式,重写run()方法
/** * 创建线程方式一:继承Thread类 */ class ExtendsThread extends Thread{ @Override public void run() { //线程体 } }
-
实现Runnable接口,实现run()方法
/** * 创建线程方式二:实现Runnable接口 */ class ImplementsRunnable implements Runnable{ @Override public void run() { //线程体 } }
-
JDK 5.0新增方式:实现Callable接口,实现call()方法
/** * 创建线程方式三:实现Callable接口 */ class ImplementsCallable implements Callable<T>{ @Override public T call() throws Exception { //线程体 return null; } }
-
JDK 5.0新增方式:使用线程池
/** * 创建线程方式四:使用线程池 * 1.使用Executors工具类创建并返回不同类型的线程池 * 2.创建Runnable或Callable的实现类 * 3.调用ExecutorService对象的execute() 方法,并将实现Runnable接口的实现类对象传入进去 * 若使用的是Callable接口的实现类,则调用ExecutorService对象的submit()方法,把实现Callable接口的实现类对象传入进去 * 4.将线程池关闭,调用ExecutorService的shutdown()方法 */ @Test public void test4() throws ExecutionException, InterruptedException { ExecutorService service = Executors.newFixedThreadPool(10); //ExecutorService是一个接口,Executors创建线程池的类型实际是ThreadPoolExecutor类型 System.out.println(service.getClass()); //class java.util.concurrent.ThreadPoolExecutor ImplementsRunnable runnable = new ImplementsRunnable(); ImplementsCallable callable = new ImplementsCallable(); service.execute(runnable); Future<T> future = service.submit(callable); T t = future.get(); System.out.println(t); service.shutdown(); }
-
前面三种对应的@Test方法
/** * 创建线程方式一:继承Thread类 * 1.创建ExtendsThread继承Thread类 * 2.重写Thread中的run() 方法 * 3.创建了Thread子类对象,即创建了线程对象 * 4.调用线程对象start()方法:启动线程,即调用了run() 方法 */ @Test public void test1(){ Thread extendsThread = new ExtendsThread(); extendsThread.start(); } /** * 创建线程方式二:实现Runnable接口 * 1.创建ImplementsRunnable类实现Runnable接口 * 2.实现Runnable中的run() 方法 * 3.创建Runnable实现类的对象 * 4.将Runnable实现类的对象传入Thread的构造器,中创建Thread类的对象 * 5.调用线程对象start()方法:启动线程,即调用了run() 方法 */ @Test public void test2(){ ImplementsRunnable runnable = new ImplementsRunnable(); Thread thread = new Thread(runnable); thread.start(); } /** * 创建线程方式三:实现Callable接口 * 1.创建ImplementsCallable类实现Callable接口 * 2.实现Callable接口中的call() 方法 * 3.创建Callable实现类的对象 * 4.将Callable实现类的对象作为参数传入FutureTask的构造器中,创建FutureTask的对象 * 5.将FutureTask的对象作为参数传入Thread的构造器中,创建Thread类的对象 * 6.调用线程对象start()方法:启动线程,即调用了run() 方法 * 7.若想获得call() 方法的返回值,需要使用FutureTask的get()方法 */ @Test public void test3() throws ExecutionException, InterruptedException { ImplementsCallable callable = new ImplementsCallable(); FutureTask<T> futureTask = new FutureTask<>(callable); Thread thread = new Thread(futureTask); thread.start(); T t = futureTask.get(); System.out.println(t); }
6、四种创建线程的比较
- 实现Runnable方式比继承Thread方式的好处
- 避免了单继承的局限性
- 多个线程可以共享一个接口实现类,非常适合多个相同线程来处理同一份资源
- 实现Callable接口方式比实现Runnable接口的优点
- call() 方法比run() 方法更加强大,可以有返回值类型,可以抛出异常,可以支持泛型
- 使用线程池比不使用线程池的优点
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程的管理
- corePoolSize:核心线程池的大小
- maximumPoolSize : 最大线程数
- ……
7、线程的同步
-
问题:当多个线程操作共享数据时就会出现线程的同步问题,导致线程不安全
-
java中的解决办法:在操作共享数据时,一次只能一个线程进行操作
-
同步代码块:
synchronized(同步锁){ //需要被同步的代码 }
-
同步方法:
public synchronized void show(String name){//默认的同步锁是this //需要被同步的代码 } public static synchronized void show(){ //默认的锁依然是this,但是被static修饰,此时this指向的就是当前类 //需要被同步的代码 }
-
JDK 5.0
/** * java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 * ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全 * 的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。 */ //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); //2.给同步资源加锁,一般放在try中 lock.lock(); //3.给同步资源释放锁,一般方法finally中 lock.unlock();
-
synchronized 和 Lock的异同?
- 相同:都可以用来解决线程安全问题
- 不同:synchronized机制在执行完响应的同步代码后,自动释放同步监视器,Lock需要手动的启动同步(lock()),同时结束同步也需要手动实现(unlock())
8、线程的通信问题
-
wait() :当前线程就会进入阻塞状态,并释放同步监视器,sleep()并不会释放同步监视器
-
notify() : 就会唤醒一个被wait的线程,如果有多个线程被wait,就唤醒优先级最高的线程
-
notifyAll() : 会唤醒所有被wait的线程
说明:
- wait(),notify(),notifyAll() 三个方法必须使用在同步代码块或同步方法中
- wait(),notify(),notifyAll() 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现java.lang.IllegalMonitorStateException异常
- 这三个方法是在Object中的方法
-
sleep() 和 wait() 的异同?
- 相同:都可以让当前线程进入阻塞状态
- 不同:
- 方法声明的位置不同:sleep() 在Thread中声明,wait() 是在Object中声明的
- 调用的要求不同:sleep() 可以在任意需要的场景下调用,wait() 只能在同步代码块或者同步方法中调用
- 是否释放同步监视器: 若方法都声明在同步代码块或同步方法中 sleep() 不会调用同步监视器,wait() 会释放同步监视器
-
8、关于线程池
- 线程池的基本组成部分:
- 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 工作线程(WorkThread):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
- java中常用的线程:
- newFixedThreadPool : 该线程池是一种线程数量固定的线程池 Executors.newFixedThread(int count)
- newCachedThreadPool : Executors.newCachedThreadPool()
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
- java中常用的线程:
- newFixedThreadPool : 该线程池是一种线程数量固定的线程池 Executors.newFixedThread(int count)
- newCachedThreadPool : Executors.newCachedThreadPool()
- newSingleThreadExecutor : Executors.newSingleThreadExecutor()