java基础09多线程
1.概念
1.1 并行 并发
并行:计算机系统中能同时执行两个或多个处理的一种计算方法
并发:指一个时间段中有几个程序都处于已启动运行到运行完毕之间,两个或者多个任务都请求运行,而处理器只能按受一个任务,就把这两个或者多个任务安排轮流进行
同步:上一个事物结束后,下一个事物才开始
异步:多个事物同时进行。不用等待其它程序是否有返回的结果
1.3 阻塞
阻塞:程序不能继续运行
2.线程
2.1进程 线程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个 进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作每个请求分配一个线程来处理。
比较:
-
进程是资源分配的最小单位,线程是程序执行的最小单位。
-
进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
-
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据。进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
-
多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了, 而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
-
一个进程有多个线程。
3. Java多线程编程
3.1 线程的状态

-
新建状态(New):使用new关键字创建线程对象,仅仅被分配了内存;该线程对象就处于新建状态。它保持这个状态直到程序 start()启动线程这个线程。
-
就绪状态(Ready):线程对象被创建后,等待它的start方法被调用,以获得CPU的使用权;
-
运行状态(Running):执行run方法,此时的线程的对象正占用CPU;处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
-
睡眠状态(Sleeping):调用sleep方法,线程被暂停,睡眠时间结束后,线程回到就绪状态,睡眠状态的线程不占用CPU;
-
死亡状态(Dead):run方法执行完毕后,线程进入死亡状态;
-
阻塞状态(Blocked):线程由于某些事件(如等待键盘输入)放弃CPU,暂停运行,直到线程重新进入就绪状态,才有机会转到运行状态
线程停止:在Thread类里面提供了stop()或者destory()等方法可以结束线程,但是不推荐使用,而且有的方法也已经过时了,在开发中,往往也不推荐使用过时的方法。
3.2 创建线程
3.2.1 继承Thread
| 构造 | 说明 |
|---|---|
| Thread() | 创建新线程 |
| Thread(String name) | 指定名称 |
| Thread(Runnable target) | 利用Runnable对象创建线程 |
| Thread(Runnable target,String name) | 指定名称 |
| 常用方法 | 说明 |
|---|---|
| static Thread currentThread() | 返回当前执行的线程对象的引用 |
| static void sleep(long millis) throws InterruptionException | 当前执行的线程休眠,指定毫秒数 |
| static void sleep(long millis, int nanos) throws InterruptionException | 休眠,指定毫秒和纳秒数 |
| static void yield() | 暂停当前线程,而去执行其他线程 |
| static boolean interrupted() | 判断线程是否中断 |
| static start() | 启动线程 |
| final String getName() | 返回线程名称 |
| final void setName() | 设置线程名称 |
| final void setPriority(int newPriority) | 设置线程优先级 |
| final int getPriority() | 返回线程优先级 |
继承Thread类,重写run方法
3.2.2 实现Runnable
实现Runnable接口的run方法
建立Thread对象,start() 启动线程
3.2.3 实现Callable
-
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
-
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
-
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
-
调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
3.2.4 比较
创建线程的主要三种方式:
-
继承Thread类,并重写run方法 没有返回值(不推荐,JAVA单根继承)。
-
实现Runnable接口,并实现run方法 没有返回值(推荐,Java多实现)。
-
实现Callable接口,并实现call方法 有返回值。
线程启动的方式:
-
new Thread子类().start();
-
new Thread(new Runnable实现类()).start();
-
new Thread(new FutureTask(new Callable实现类())).start();
线程创建时,如果不需要返回结果,使用Runnable方式.
如果需要返回结果,使用Callable方式.
采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
3.3 线程池
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快;另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
3.4 线程安全
3.4.1 自动加锁
synchronized (变量/对象/类) {代码块}
synchronized 方法
3.4.2 手动加锁
Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
ReentrantLock(可重入锁)类实现了Lock, 它拥有了与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的就是这个实现类了。可以显示加锁,释放锁。
private final Lock lock = new ReentrantLock();
lock.lock();//手动上锁
//手动释放锁 注意:有异常也需要释放 尽可能加上finally块
lock.unlock();
3.4.3 比较
-
Lock是显示锁(手动开启和关闭),synchronized是隐式锁,出了作用域就自动释放锁。
-
Lock只能在方法体内使用,synchronized既可以在方法体内(对象锁),也可以在方法上(类锁)。
-
使用Lock锁,JVM将花费更少的时间来调度线程,性能会更好,并且具有更好的可扩展性(有很多子类)。
-
优先使用顺序: Lock----> 同步代码块---->同步方法
3.5 死锁
死锁是指两个或两个以上的进程执行时由于竞争资源或者通信造成阻塞,称系统处于死锁状态或产生了死锁,这些永远在互相等待的进程称为死锁进程。
3.6 线程通信
3.6.1 wait、notify、notifyAll、join
-
wait :操作完释放锁 调用wait时释放线程占有的对象锁,必须等待被唤醒
-
notify:唤醒后争夺锁 唤醒在等待该对象同步锁的线程(只唤醒一个,具有随机性)
-
notifyAll :唤醒所有在等待该对象同步锁的线程,有顺序,按等待对象链
-
join:t.join()方法使主线程(即调用t.join()的线程)等待t线程执行完毕后再唤醒,相当于t抢先了主线程
注意:
-
调用了wait、notify、notifyAll 方法必须在同步状态,即已获取了对象锁,使用时候必须与 synchronized 关键字一起
-
调用 notifyAll 方法后,同一时刻只有一个线程执行,其他线程须等待该线程执行完毕释放对象锁

浙公网安备 33010602011771号