Java 线程与多线程(二)

线程

有了线程然后才有多线程。多线程是对线程的升华。

进程 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();
  }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sp42a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值