线程
有了线程然后才有多线程。多线程是对线程的升华。
进程 v.s 线程
- 进程是线程的容器,进程可以创建多个(甚至上百个)线程
- 一个 Java 程序(进程)至少有两个线程,一个主线程本身,另外一个 GC 线程
- 线程之间可以任意通讯,进程则一般不行。线程之间的共享父进程的内存
创建线程
Java 里面的线程,包含一个特定的“任务 Task”概念。所以有:
- 任务,任务是指具线程所要执行的具体内容,也就是所要执行的代码,需要明确的是,任务跟线程是没有太明确的关系的,任务是描述,而线程则是具体执行的工具
- 线程,线程只是负责执行委托给它的任务,不清楚具体的内容是什么,因为具体的内容是定义在任务之中
使用多线程的时候,一般是把操作逻辑与具体的执行者分开,这种实现方式看上去比较麻烦,但是实际上将实现逻辑与具体执行者分离是一种非常好的设计。
创建任务
两种任务:
- 没有具体返回值的任务,对应
Runnable
接口 - 有返回值的任务,对应
Callable<T>
接口
class CalcTask1 implements Runnable{
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100000; i++)
sum +=i ;
System.out.println(sum);
}
}
class CalcTask2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100000; i++)
sum +=i ;
return sum;
}
}
除了上面的两种还有一种是直接继承Thread
类,并且重写对应的run()
方法,不过会导致具体任务与对应的线程绑定在一起。
/**
* 通过继承Thread并且重写run方式来描述任务
*/
class CalcTask3 extends Thread{
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100000; i++)
sum +=i ;
System.out.println(sum);
}
}
如果不想绑定,可以把 Task 作为构造器参数传入到 Thread 中。
Runnable calcTask = new CalcTask1();// 创建对应的任务
// 将任务提交给线程
// 这里需要注意的是,如果是直接继承自Thread,则直接调用对应的线程对象的run方法即可
Thread executor = new Thread(calcTask);
executor.start();// 启动线程
初识线程池
一般而言我们比较少直接创建 Task 或 Thread,这些是低阶的 API。取而代之的是采用线程池的方式来管理线程,另外性能也会好很多(频繁地创建与回收线程是非常耗费资源的)。
ExecutorService service = Executors.newCachedThreadPool();// 获取一个ExecutorService实例
Runnable calcTask = new CalcTask1();// 创建对应的任务
service.submit(calcTask);// 将任务提交给ExecutorService
service.shutdown();// 关闭对应的 ExecutorService
线程的属性 Thread
线程本身也是一种对象,有许多属性,比如线程的ID、名字、优先级、状态等。
id
独一无二的 ID,只读不写。
class DetailTask implements Runnable{
@Override
public void run() {
System.out.println("Details : " + Thread.currentThread().getId());
// 其中Thread.currentThread()用于获取当前执行的线程
// 通过thread.getId()则可以获取对应的线程的ID
}
}
名称
推荐设置线程的名称。
class DetailTask implements Runnable{
private static int cnt = 0;
// 注意这里的代码是非线程安全的
@Override
public void run() {
Thread.currentThread().setName("Thread " + cnt++); // 设置线程名字
System.out.println("Details : " + Thread.currentThread().getName()); // 获取线程的名字
}
}
优先级
线程之间的调度采用的是基于时间片的轮转调度方式,其中在线程进行切换,重新调度时,调度的主要依据就是优先级,通常来说,优先级比较高的线程,被调度的可能性会比较大,也就是说,在单位时间内,优先级高的线程被调度的次数会大于优先级低的线程。设置优先级仅仅仅能提高线程被调度的概率,也就是仅能提高线程被调度的次数,但是并不能保证执行次数一定会高,所以,不能仅依赖优先级来实现如同步、合作之类的操作。
for (int i = 0; i < 10; i++){
Thread thread = new Thread(task);
System.out.println("Priority " + thread.getPriority());
if (i % 2 == 0){
thread.setPriority(Thread.MIN_PRIORITY); // 设置优先级为最低
}else {
thread.setPriority(Thread.MAX_PRIORITY); // 设置优先级为最高
}
thread.start();
}
优先级的设置必须在线程启动之前设置,也就是必须在thread.start()
方法执行之前进行设置,否则,会采用默认的优先级,言下之意即不能在 run 方法中进行优先级的设置.
状态
参考 JDK 源码:
/* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
守护线程
守护线程是一类特殊的线程,不能在守护线程中做一些逻辑比较复杂的操作,不能做一些比较重要的操作。
线程的异常
由于多线程的异步性特点,在多线程中处理异常与非多线程中有一些不同。在非多线程的编码过程中,我们会根据异常出现的情况进行处理,有的是直接处理即可,而有的异常,我们则会将其抛出,交由上层进行处理。而由于线程的异步性,是无法将在子线程出现的异常传递至父线程中,换句话说,子线程中出现的异常只能在子线程中进行处理,而不能将其交给父线程进行统一处理,所以,在多线程中出现的异常,有其独特的处理方式。
// 检查性异常
class ExceptionTask implements Runnable{
@Override
public void run() {
File file = new File("c:/data.txt");
try {
BufferedReader reader =new BufferedReader(new FileReader(file));
} catch (FileNotFoundException e) {
System.out.println("file not found");
return;
}
System.out.println("Thread finished");
}
}
// 非检查性异常
class ExceptionTask implements Runnable{
@Override
public void run() {
// 可能会出现异常
try {
int result = Integer.parseInt("123F");
System.out.println("Result is " + result);
}catch (NumberFormatException e){
System.out.println("not a number");
}
}
}
对于非检查性异常,Java 中还提供了另外一种处理方式
// 实现 Thread.UncaughtExceptionHandler,定制对应的异常处理器
class ExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
if (e instanceof NumberFormatException)
System.out.println("could not format number ");
}
}
public void test(){
Thread thread = new Thread(exceptionTask);
// 设置线程的异常处理器,需要注意的是,必须在线程启动之前进行设置
thread.setUncaughtExceptionHandler(new ExceptionHandler());
thread.start();
}
上面的操作方式是为某一特定的线程设置非检查性异常处理器,除此之外,还可以为所有的线程设置默认的异常处理器。
// 为所有线程设置默认的异常处理器
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
这三者的顺序分别为,try-catch、某一线程的异常处理器、线程的默认异常处理器,当出现异常时,JVM 会坚持按照这个顺序坚持对应的异常处理器,如果找到合适的处理器,则将对应的异常交给它。
停止线程
在某些情况下,除了线程执行完毕,正常结束外,我们还可能在线程执行过程中中断/结束该线程,根据线程的状态不同,结束某个线程也有不同的方式。
ThreadLocal
ThreadLocal 是 Java 提供的一种线程封闭的机制,它可以为每个线程提供独立的变量副本(空间换时间)。通过将共享变量存储在 ThreadLocal 中,可以避免多个线程之间的数据共享和竞争,从而保证线程安全。
private ThreadLocal<Integer> threadLocalCount = ThreadLocal.withInitial(() -> 0);
public void incrementCount() {
int count = threadLocalCount.get();
threadLocalCount.set(count + 1);
}
一个例子
要查询的数据量过多时,分多个线程查询,查完合并,提高效率。
static class ThredQuery implements Callable {
private List<Integer> parms;
private Object object;
public ThredQuery(List<Integer> parms) {
this.parms = parms;
}
@Override
public Object call() {
// 查询数据库
System.out.println("查询条件:--->" + parms);
// 模拟查询出的数据
// 处理的业务
object = "ID:" + parms + "查询的数据";
return object;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//返回结果
List<Object> result = new ArrayList<>();
//模拟需要查询的ID
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 要开的线程数量
int threadNum = 5;
// 拆分查的idlist
List<List<Integer>> smallerLists = Lists.partition(ids, 2);
//分配任务
List<Callable<List<ThredQuery>>> tasks = new ArrayList<>();
for (int i = 0; i < threadNum; i++) {
ThredQuery thredQuery = new ThredQuery(smallerLists.get(i));
tasks.add(thredQuery);
}
//执行
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
List<Future<List<ThredQuery>>> futures = executorService.invokeAll(tasks);
if (futures.size() > 0) {
for (Future<List<ThredQuery>> future : futures)
result.add(future.get());
}
System.out.println("查询出的数据:" + result.size());
for (Object o : result)
System.out.println(o.toString());
executorService.shutdown();
}