多线程的概述
- 进程:
电脑中,一个正在运行
的应用程序就是一个进程,如QQ,微信等 - 线程:
一个进程中可以只有一个线程,也可以包含许多个线程 - 多线程:
就是一个正在运行的程序中的多个线程同时在执行
线程实现
以下四种皆可实现线程
- 继承Thread类
构造方法:
Thread(); // 创建一个线程
Thread(String name); // 创建一个名字为name的线程
常用方法:
start(); // 使线程开始执行
run(); // 是线程执行操作
sleep(long millis); // 使线程休眠millis毫秒(暂停millis毫秒)
创建线程的步骤:
1.创建一个类,使其继承Thread类
2.重写父类中的run()方法,原因是因为父类中的run方法什么也不执行,只返回一个null,没有用处,所以需要自己重写之后实现需求
3.创建子类对象
4.通过子类对象调用start()方法开启线程,当线程开启时,就会通知jvm去调用run方法
子类的方法:
setName(String name); // 给创建的线程赋予名字
getName(); // 获取当前线程的名字
- 实现Runnable接口
构造方法:
Thread(Runnable run); // 将创建的实现类对象传入构造方法中
Thread(Runnable run, String name); // 将创建的实现类对象传入构造方法中,并给线程起名字
创建线程的步骤:
1.创建一个类,使其实现Runnable接口
2.重写接口中的run()方法这里需要注意,Callable接口库中的run方法是由返回值的,可通过get方法获取到这个值,当然,get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
3.创建子类对象
4.通过Thread(Runnable);方法,创建线程对象
5.通过线程对象调用start()方法开启线程
- 实现Callable接口
创建线程的步骤:
1.创建一个类,实现Callable接口
2.重写接口中的run()方法
3.创建实现类:
Callable<E> oneCallable = new ImplementsCallable<E>();
4.创建FutureTask类对象,将创建的oneCallable对象传入:
FutureTask<String> futureTask = new FutureTask<String>(implementsCallable);
5.创建线程,将futureTask对象传入
Thread thread = new Thread(futureTask);
6.开启线程
- 从线程池中获取
1.创建一个类,实现Callable接口
2.重写接口中的run()方法
3.创建线程池
/* corePoolSize:池中所保存的线程数,包括空闲线程。
* maximumPoolSize:池中允许的最大线程数。
* keepAliveTime:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
* unit:参数的时间单位。
* workQueue:执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
*
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
4.创建实现类对象
实现类 对象名 = new 实现类(参数);
5.调用连接池中的submit(实现类对象):方法,将实现类对象传入方法中
threadPoolExecutor.submit(实现类对象);
6完成
多线程安全问题产生&解决方案
多线程安全问题
如果多个线程需要执行同一个任务,那么实现步骤如下
1.创建一个类,使其实现Runnable接口
2.重写run方法
3.创建子类对象
4.创建线程,将子类对象传入构造方法中这样,如果创建多个线程时,传入的参数都是同一个子类对象,那么就会执行同一个任务
5.开启线程
如果在线程执行过程中出现休眠,那么后面的线程可能就会读取重复的数据,出现线程安全问题
线程安全问题出现的原因
1.有多个线程
2.多个线程共享同一个数据
3.多个线程并发(同时)的访问同一个数据
多线程安全问题解决
- 对线程加锁
synchronized(同步锁)关键字:
用于修饰方法或代码块,被修饰的代码块或方法如果被线程访问则上锁,直到进入当前方法或代码块的线程完成操作,后面的线程才能访问 - 同步代码块
格式如下:
synchronized(锁对象){
//需要同步的代码
}
锁对象要被所有线程共享
- 同步和非同步
同步:安全性高,效率低
非同步:安全性低,效率高 - 同步方法
格式:
权限修饰符 synchronized 返回值类型 方法名() {
方法体;
}
线程的生命周期
创建线程池
通过Executors的静态方法来创建线程池(四种方法)。
newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程
newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制
newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行
newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
线程锁
同步锁synchronized
用于修饰方法或代码块,被修饰的代码块或方法如果被线程访问则上锁,直到进入当前方法或代码块的线程完成操作,后面的线程才能访问
Lock锁
重入锁(ReentrantLock)
写锁(ReetrantReadWriteLock.WriteLock)
读锁(ReetrantReadWriteLock.ReadLock)
增加Lock机制主要是因为内置锁存在一些功能上局限性。比如无法中断一个正在等待获取锁的线程,无法在等待一个锁的时候无限等待下去。内置锁必须在释放锁的代码块中释放,虽然简化了锁的使用,但是却造成了其他等待获取锁的线程必须依靠阻塞等待的方式获取锁,也就是说内置锁实际上是一种阻塞锁。而新增的Lock锁机制则是一种非阻塞锁