继承 Thread 类创建线程
步骤
定义继承 Thread 的子类,并重写 run() 方法
创建继承 Thread 的子类实例,即创建了线程对象
调用 start() 方法启动线程
示例代码
package com.dao.thead.core;
/**
* 继承 Thread 类创建线程
*
* @author 阿导
* @version 1.0
* @fileName com.dao.thead.core.MyThead.java
* @CopyRright (c) 2018-万物皆导
* @created 2018-04-11 13:32:00
*/
public class MyThread extends Thread {
/**
* 重写 run 方法
*
* @author 阿导
* @time 2018/4/11
* @CopyRight 万物皆导
* @param
* @return void
*/
@Override
public void run(){
System.out.println("这是继承 Thread 类创建线程。");
}
}
//启动继承 Thread 类的线程
new MyThread().start();
实现 Runnable 接口创建线程
步骤
定义实现 Runnable 的子类,并重写实现 run() 方法
创建实现 Runnable 的子类实例,并用这个实例作为 Thread 的 target 来创建 Thread 对象,这个 Thread 对象才是真正的线程对象
调用 start() 方法启动线程
示例代码
package com.dao.thead.core;
/**
* 实现 Runnable 接口创建线程
*
* @author 阿导
* @version 1.0
* @fileName com.dao.thead.core.MyRunnable.java
* @CopyRright (c) 2018-万物皆导
* @created 2018-04-11 13:32:00
*/
public class MyRunnable implements Runnable {
/**
* 实现 run 方法
*
* @author 阿导
* @time 2018/4/11
* @CopyRight 万物皆导
* @param
* @return void
*/
@Override
public void run() {
System.out.println("这里是实现 Runnable 接口创建线程。");
}
}
//启动实现 Runnable 接口的线程
new Thread(new MyRunnable()).start();
使用 Callable 和 Future 创建线程
步骤
定义实现 Callable 的子类,并实现 call() 方法
使用 FutureTask 类来包装实现 Callable 的子类,其包装的返回值(泛型)应和对应子类保持一致
将 FutureTask 对象作为 Thread 的 target 创建线程,并通过 start() 来启用线程
然后通过 get() 方法获取返回值,该阶段,线程会进入阻塞状态,直到所有线程结束。
示例代码
package com.dao.thead.core;
import java.util.concurrent.Callable;
/**
* 使用 Callable 和 Future 创建线程
*
* @author 阿导
* @version 1.0
* @fileName com.dao.thead.core.MyCallable.java
* @CopyRright (c) 2018-万物皆导
* @created 2018-04-11 13:33:00
*/
public class MyCallable implements Callable<String> {
/**
* 实现 call() 方法
* @author 阿导
* @time 2018/4/11
* @CopyRight 杭州微财科技有限公司
* @param
* @return java.lang.String
*/
@Override
public String call() throws Exception {
System.out.println("这是使用 Callable 和 Future 创建线程。");
return "万物皆导";
}
}
//使用 FutureTask 获取实现 Callable 的线程
FutureTask<String> task=new FutureTask<String>(new MyCallable());
//将任务放入线程执行,实质上还是以 Callable 对象来创建并启动线程
new Thread(task,"测试带返回值的线程").start();
//查看线程返回值,get() 方法会阻塞,直到子线程执行结束才返回
System.out.println("带回来的结果:"+task.get());
对比三种线程的实现方式
线程只是实现 Runnable 或实现 Callable 接口,还可以继承其他类。
这种方式下,多个线程可以共享一个 target 对象,非常适合多线程处理同一份资源的情形。
但是编程稍微复杂,如果需要访问当前线程,必须调用 Thread.currentThread() 方法。
继承 Thread 类的线程类不能再继承其他父类(Java 单继承决定)。
注:一般推荐采用实现接口的方式来创建多线程
案例来自于一些哥们和我分享的面试题,目前我这边听到最多的就是设计死锁和线程安全问题,下面我对这两个问题一一分析,不当之处,欢迎指正,我一贯认为写博客其实就是帮助自己成长的一种形式,也是给自己记录笔记,在各位大佬的指正下,我会成长的更快。
如何设置一个死锁程序?可能很多人一下子很蒙圈,若能理清什么是死锁,然后反向考虑,问题便很简单,一般来说产生死锁的原因就是资源申请不下来,线程一直处于阻塞等待状态,那么反向考虑只需要2个线程便能设计一个死锁程序,暂且设定两个线程 A、B ,并设置两个条件必须同时在一个线程拥有这两个条件才能执行结束,否则两个线程就处于阻塞状态,这样便设计出死锁程序了,话不多说,直接撸代码。
/**
* 死锁程序设计
*
* @author 阿导
* @CopyRight 万物皆导
* @Created 2019年03月25日 14:22:00
*/
public class DeadThread {
/**
* AB 执行的必要条件
*/
private static Object conditionAB = new Object();
/**
* BA 执行的必要条件
*/
private static Object conditionBA = new Object();
/**
* 主程序
* @param args
*/
public static void main(String[] args) {
// 线程 A
Thread threadA = new Thread(()->{
synchronized (conditionAB){
System.out.println("A 线程开始");
System.out.println("A 线程即将进入阻塞状态");
synchronized (conditionBA){
System.out.println("A 线程阻塞状态结束");
}
System.out.println("A 线程结束");
}
});
// 线程 B
Thread threadB = new Thread(()->{
synchronized (conditionBA){
System.out.println("B 线程开始");
System.out.println("B 线程即将进入阻塞状态");
synchronized (conditionAB){
System.out.println("B 线程阻塞状态结束");
}
System.out.println("B 线程结束");
}
});
threadA.start();
threadB.start();
}
}
前几天,过去和我共事的一个小哥们说他去面试了蚂蚁金服,他说面试官给他一个线程安全方面的问题,思路不是很清晰,因为阿导面试经验不是很丰富,对那些所谓的面试套路,面试题存储量是很少的,来杭州总共面试两次,都是同一个大佬,也没怎么太刁难我,所以很多面试题目我都表示很慌的,既然哥们有求于我,那怎么也得仗义相助一下,首先蚂蚁金服那位大佬的面试题如下:
学生成绩数据查询、修改、打印功能
【场景描述】
使用多线程方式,实现学生成绩数据查询、修改、打印功能。
线程A:从数据库中获取学生的成绩数据(stu_id, score1, score2, score3),stu_id代表学生ID,score1 - score3是代表三门课的成绩
线程B:会对这三门课的成绩进行修改
线程C:会对这三门课的成绩进行打印输出
【要求】
1.在线程B修改成绩的过程中,线程C不能对成绩进行打印,线程A不能读取数据
2.线程A从数据库读取数据的过程中,线程B不能修改成绩,线程C也不能打印成绩
3.线程C打印成绩的过程中,线程A不能读取数据,线程B不能修改数据。
4.主线程用于检测3个线程的状态,如果线程挂了,则重启线程。
学生成绩服务接口:
/**
* 查询学生成绩
* @param stuId 学生ID,由cmd控制台输入
* @return 学生成绩模型
**/
public StudentDO StudentService.queryStudent(String stuId);
/**
* 修改学生成绩
* @param student 学生模型,由cmd控制台输入
* @return 修改影响记录数
**/
public int StudentService.updateStudent(StudentDO student);
/**
* 打印学生成绩
* @param student 学生模型
**/
public void StudentService.printStudent(StudentDO student);
public class StudentManager {
public static void main(String[] args) {
// TODO 完成此处的代码
}
}
当哥们发过来面试题的时候,我这边因为在忙项目,并没仔细看题目,我以为是侧重点是线程竞争问题,等我这边事情忙完了,我仔细审题了一遍,发现这应该考虑的是线程安全问题。
其实实现方式有很多种,最为简单的无非就是加个锁,我这边以重入锁为例,大家也可以尝试其他的实现方式,比如通过 Semaphore 类控制访问线程的个数,话不多说,以下是我的代码示例。
package com.dao.example;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程安全问题
*
* @author 阿导
* @CopyRight 萬物皆導
* @Created 2019年03月25日 16:55:00
*/
public class ThreadSafe {
/**
* 重入锁锁定当前执行的对象,只是测试,正式环境慎用,防止导致死锁的情况
*/
private static Lock lock = new ReentrantLock();
/**
* 业务实现,只是示例,实际写代码应该单独写一个类去实现接口,建议平时开发注意依赖倒置原则
*/
private StudentService studentService = new StudentService() {
@Override
public StudentDO queryStudent(Long stuId) {
System.out.println(getThread() + "查询学生信息:" + stuId);
return new StudentDO();
}
@Override
public int updateStudent(StudentDO student) {
System.out.println(getThread() + "更新学生信息:" + student.getId());
return 0;
}
@Override
public void printStudent(StudentDO student) {
System.out.println(getThread() + "打印学生信息:" + student.getId());
}
};
private String getThread() {
return Thread.currentThread().getName()+"("+Thread.currentThread().getId()+")";
}
/**
* 打印学生成绩适配器
*/
private StudentServiceAdapter printAdapter = new PrintAdapter();
/**
* 更新学生成绩适配器
*/
private StudentServiceAdapter updateAdapter = new UpdateAdapter();
/**
* 查询学生信息适配器
*/
private StudentServiceAdapter queryAdapter = new QueryAdapter();
/**
* 主方法(用学生 ID 自加1 表示执行序次,若是次序一致则表示每一步都是互斥的)
*
* @param args
*/
public static void main(String[] args) {
ThreadSafe main = new ThreadSafe();
StudentDO studentDO = new StudentDO();
Long id = 0L;
studentDO.setId(id);
// 根据适配器选择不同的操作
Thread threadA = null;
Thread threadB = null;
Thread threadC = null;
// 主线程检测线程状态
while (true) {
threadA = keepThreadLive(threadA, main.studentService, studentDO, main.queryAdapter,"A");
threadB = keepThreadLive(threadB, main.studentService, studentDO, main.updateAdapter,"B");
threadC = keepThreadLive(threadC, main.studentService, studentDO, main.printAdapter,"C");
}
}
/**
* 确保线程是活的
*
* @param thread
* @param studentService
* @param studentDO
* @param serviceAdapter
* @return
* @author 阿导
* @time 2019/3/26 9:49
* @CopyRight 万物皆导
*/
private static Thread keepThreadLive(Thread thread, StudentService studentService, StudentDO studentDO, StudentServiceAdapter serviceAdapter,String name) {
if (thread == null) {
(thread = getThread(studentService, studentDO, serviceAdapter)).setName(name);
System.out.println("创建线程:"+thread.getName()+"("+thread.getId()+")学生信息:"+studentDO.getId());
thread.start();
} else if (!thread.isAlive()) {
(thread = getThread(studentService, studentDO, serviceAdapter)).setName(name);
System.out.println("重启线程:"+thread.getName()+"("+thread.getId()+")学生信息:"+studentDO.getId());
thread.start();
}
return thread;
}
/**
* 业务处理适配接口层
*
* @author 阿导
* @CopyRight 萬物皆導
* @created 2019/3/25 17:08
* @Modified_By 阿导 2019/3/25 17:08
*/
interface StudentServiceAdapter {
/**
* 适配处理
*
* @param studentService
* @param studentDO
* @return
* @author 阿导
* @time 2019/3/25 17:10
* @CopyRight 萬物皆導
*/
void doAdapter(StudentService studentService, StudentDO studentDO);
}
class PrintAdapter implements StudentServiceAdapter {
/**
* 适配处理:打印學生成績
*
* @param studentService
* @param studentDO
* @return
* @author 阿导
* @time 2019/3/25 17:10
* @CopyRight 萬物皆導
*/
@Override
public void doAdapter(StudentService studentService, StudentDO studentDO) {
studentService.printStudent(studentDO);
}
}
class UpdateAdapter implements StudentServiceAdapter {
/**
* 适配处理:更新學生信息
*
* @param studentService
* @param studentDO
* @return
* @author 阿导
* @time 2019/3/25 17:10
* @CopyRight 萬物皆導
*/
@Override
public void doAdapter(StudentService studentService, StudentDO studentDO) {
studentService.updateStudent(studentDO);
}
}
class QueryAdapter implements StudentServiceAdapter {
/**
* 适配处理:查詢學生信息
*
* @param studentService
* @param studentDO
* @return
* @author 阿导
* @time 2019/3/25 17:10
* @CopyRight 萬物皆導
*/
@Override
public void doAdapter(StudentService studentService, StudentDO studentDO) {
studentService.queryStudent(studentDO.getId());
}
}
/**
* 获取线程
*
* @param studentService
* @param studentDO
* @param serviceAdapter
* @return
* @author 阿导
* @time 2019/3/25 17:03
* @CopyRight 萬物皆導
*/
private static Thread getThread(StudentService studentService, StudentDO studentDO, StudentServiceAdapter serviceAdapter) {
return new Thread(() -> {
while (true) {
try {
lock.lock();
studentDO.setId(studentDO.getId() + 1L);
// 根据适配器去选择不同的操作
serviceAdapter.doAdapter(studentService, studentDO);
// 模拟业务处理
} finally {
lock.unlock();
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// e.printStackTrace();
}
}
}
});
}
}
/**
* 学生操作业务接口层
*
* @author 阿导
* @CopyRight 萬物皆導
* @created 2019/3/25 16:56
* @Modified_By 阿导 2019/3/25 16:56
*/
interface StudentService {
/**
* 查询学生成绩
*
* @param stuId 学生ID,由cmd控制台输入
* @return 学生成绩模型
**/
StudentDO queryStudent(Long stuId);
/**
* 修改学生成绩
*
* @param student 学生模型,由cmd控制台输入
* @return 修改影响记录数
**/
int updateStudent(StudentDO student);
/**
* 打印学生成绩
*
* @param student 学生模型
**/
void printStudent(StudentDO student);
}
/**
* 学生实体
*
* @author 阿导
* @CopyRight 萬物皆導
* @created 2019/3/25 16:56
* @Modified_By 阿导 2019/3/25 16:56
*/
class StudentDO {
/**
*
*/
private Long id;
private Integer score1;
private Integer score2;
private Integer score3;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getScore1() {
return score1;
}
public void setScore1(Integer score1) {
this.score1 = score1;
}
public Integer getScore2() {
return score2;
}
public void setScore2(Integer score2) {
this.score2 = score2;
}
public Integer getScore3() {
return score3;
}
public void setScore3(Integer score3) {
this.score3 = score3;
}
}
说到线程就不得不说三个辅助类,分别是 CountDownLatch 、CyclicBarrier 、Semaphore ,这个三个的区别点在哪呢?我的观点是时间点的问题,CountDownLatch 侧重于指定数量线程完成后才处理后续的流程,比如赛跑中我们只记录前3名,那么前三名到达终点便结束比赛;CyclicBarrier 侧重于指定数量线程同时启动,比如赛跑中为了保持公平,所有选手必须在听到发令枪后才能开始起跑;Semaphore 侧重于线程并发数量控制,比如只有3个封闭式赛道(只有跑完了才能换人跑),所以每次最多3个人在跑道上跑(有点不恰当,别介意),下面阿导使用代码一一诠释。
CountDownLatch
:因为是兄弟,所以才我们才等你一起到终点
未完待续
CyclicBarrier
:为了公平,我们等你一起开始我们的故事
未完待续
Semaphore
:因为资源有限,我们只能通过竞争去占用资源
未完待续
为什么要用线程池?
在程序运行过程中,线程资源是稀缺的,为了减少线程创建和销毁,使得线程得以重复利用,所以使用线程池。
防止滥用线程,通过线程池,可以有效的控制工作中的线程数量,防止服务器 CPU 或者内存消耗过多导致崩溃,造成重大损失。
线程池的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
threadFactory:线程工厂,用来创建线程
handler:超出线程范围和队列容量的任务的处理程序
线程实现原理
首先用户先提交线程任务
首先判断线程池核心线程是否都在执行任务,若没有(核心线程空闲或者还有核心线程没有被创建)则创建新的线程来执行任务,若核心线程都在执行任务,进入下一流程
然后判断线程池队列是否已经满了,若没有,则将新提交的任务放入线程队列中,若已满;进入下一流程
判断线程是否都处于工作状态,若没有,则新创建一个线程来执行任务,若已经满了,则交给饱和策略来处理这个任务
阿里云低价购买云服务,值得一看