多线程
-
并发与并行
- 并发:指多个事件在同一时间段发生(事件交替执行)
- 并行:指多个事件在同一时刻发生(事件同时执行)
-
线程与进程
- 进程:指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程是程序的一次执行过程,是系统运行程序的基本单位
- 线程:是进程中的一个执行单位,负责当前进程中程序的执行,一个具有多个线程的进程,被称为多线程程序
-
线程调度:
-
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
-
抢占式调度:让优先级高的线程优先使用CPU,若线程的优先级相同,则会随机选择一个线程使用,Java的线程调度为抢占式调度
-
Java线程
-
主线程
执行主方法(main)的线程
-
创建线程类
- 创建Thread类的子类
java.lang.Thread
类是描述线程的类,可继承Thread类以实现多线程程序- 创建Thread类的子类
- 在Thread类的子类中重写run方法,设置线程任务
- 创建线程子类对象
- 调用Thread类的start方法,开启新线程,执行线程的run方法,完成线程任务
tips:在创建线程后,原线程与新建线程同时存在,分别执行任务,抢占式调度同一优先级下随机选择一个线程执行任务,两个线程并发执行
- 实现Runnable接口
- 创建一个Runnable接口的实现类
- 在实现类中重写Runnable接口的Run方法,设置线程任务
- 创建一个Runnable接口的实现类对象
- 创建Thread类对象,在构造方法中传递Runnable接口的实现类对象
- 调用Thread类的start方法,开启新的线程执行Runnable接口实现类的run方法
- 优点:
- 避免了单继承的局限性,即若继承Thread类开启线程就无法继承其他类
- 增强了程序的拓展性,降低了程序的耦合性,即实现Runnable接口的方式,将设置线程任务与开启新线程的任务进行了分离
- 优点:
-
匿名内部类实现线程创建
//继承方式实现 new Thread(){ @Override public void run(){ //线程任务 } }.start(); //Runnable接口实现 Runnable r = new Runnable(){ @Override public void run(){ //线程任务 } }; new Thread(r).start();
-
内存图解
- Thread类
- 常用方法:
public String getName()
:获取当前线程名称。public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。public void run()
:此线程要执行的任务在此处定义代码。public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
- 常用方法:
线程安全
多线程访问了共享的数据可能会发生线程安全问题,如对同一数据产生了不同的操作
线程同步
当我们使用多个线程访问同一资源,且多个线程中对资源有写的操作时,就容易出现线程安全问题,为解决多线程并发访问一个资源的安全性问题,Java中提供了线程同步机制来解决该问题。
-
同步代码块
通过使用
synchronized
关键字标记方法中的某一区块,表示只对该区块的资源实行互斥访问synchronized(锁对象){ //需要同步操作的代码块 }
-
同步代码块中的锁对象可以是任意的对象
-
必须保证多个线程使用的锁对象是同一个
-
通过锁对象即可将代码块锁定,只让一个线程在同步代码块中执行
-
同步代码块中的锁对象被称为同步锁,对象锁或对象监视器
-
原理:
每一个进程类似于一把钥匙,但同步代码块的锁只有一把,只有首先取得锁对象的进程才能进入同步代码块中,直到完成操作再归还锁对象,而其他未夺得锁对象的进程在该进程操作时自动进入阻塞状态,直到夺得锁对象的进程完成操作运行完同步代码块代码释放锁对象。
-
-
同步方法
-
通过使用
synchronized
关键字修饰的方法被称为同步方法public synchronized void method(){ //可能产生线程安全问题的代码 } //静态同步方法 public static synchronized void method(){ //可能产生线程安全问题的代码 }
-
使用步骤
- 抽取共享的数据代码到一个方法中
- 使用
synchronized
关键字修饰
-
普通同步方法的锁对象是调用该方法的线程实现类对象
-
静态同步方法的锁对象是本类的class属性,即class文件对象
-
-
Lock锁
-
java.util.concurrent.locks.lock
机制提供了比同步代码块与同步方法更广泛的锁定操作,更体现面向对象的思想 -
Lock锁也称同步锁,其加锁与释放锁的功能被方法化
public void lock();//加同步锁 public void unlock();//释放同步锁
-
使用步骤
-
成员位置创建一个ReentrantLock(Lock接口的实现类)对象
Lock l = new ReentrantLock();
-
在可能出现安全问题的代码前调用Lock接口的lock方法加同步锁
l.lock();
-
在可能出现安全问题的代码后调用Lock接口的unlock方法释放同步锁
tyr{ }catch(){ }finally{//无论程序是否出现异常均将同步锁释放 l.unlock(); }
-
-
线程状态
-
NEW:新建状态
线程刚被创建,但还未被启动
-
Runnable:可运行
线程在Java虚拟机中运行的状态
-
Blocked:锁阻塞
当一个线程未获取到线程锁时进入阻塞状态
-
Waiting:无限等待
一个线程等待另一个线程执行Object的notify或notifyAll方法唤醒时,处于无限等待状态
-
Timed Waiting:计时等待
调用含有超时参数的方法如:Thread.sleep、Object.wait,线程将进入计时等待状态,该状态将保持到超时期满或接收到唤醒通知
-
Teminated:被终止
因为run方法执行完毕而死亡或因没有捕获的异常终止了run方法而死亡
线程间通信
多个线程处理同一个资源,但各线程的线程任务却不相同,则各线程之间存在着线程通信的问题,避免多个线程在操作同一份数据时对同一共享变量的争夺,通过使用等待唤醒机制使得多线程之间能有效的利用资源
- 在同步代码块或同步函数中调用锁对象的wait与notify方法来实现线程的等待与唤醒
线程池
如果并发的线程数量很多,且每一个线程均执行一个时间很短的任务就结束了,这样频繁的创建线程会大大降低系统的效率,而在Java中通过线程池可以解决该问题,达到线程复用的效果,在执行完任务后并不销毁,而是执行其他的任务
-
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复调用。
-
Java.util.concurrent.Executors
:线程池的工厂类,用来生产线程池-
静态方法:
static ExecutorService newFixedThreadPool(int nThreads);
创建一个可重用固定线程数(int nThreads)的线程池,返回一个ExecutorService接口的实现类对象
-
java.util.concurrent.ExecutorService
:线程池接口用来从线程池中获取线程,调用start方法,执行线程任务
- submit(Runnable task)方法:提交一个Runnable任务用以执行
- shutdown()方法:关闭/销毁线程池
-
-
线程池的使用步骤
-
使用线程池的工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(n);
-
创建一个Runnable接口的实现类,重写run方法,设置线程任务
public clas RunnableImpl implements Runnable{ @Override public void run(){ //线程任务 } }
-
调用ExecutorService中的submit方法,传递线程任务,开启线程,执行run方法
es.submit(new RunnableImpl());
-
调用ExecutorService中的shutdown方法销毁线程池(不建议)
es.shutdown();
:线程池会一直开启,完成线程任务后会自动归还线程给线程池,销毁后,线程池无法继续使用
-
Lambda表达式
面向对象编程思想:程序的功能实现由创建对象具有的方法来完成
函数式编程思想:只要能获取到最终结果,实现的过程忽略
JDK1.8推出的一个重要特性
-
线程任务实现
-
匿名内部类:
new Thread(new Runnable(){ @Override public void run(){ //线程任务 } }).start();
-
Lambda表达式
new Thread(()->{ //线程任务 } ).start();
-
-
标准格式
(参数列表) -> {参数传递的重写方法体}
():接口中抽象方法的参数列表,无参为空,有参逗号分隔
->:表示传递参数
{}:重写接口的抽象方法的方法体