Java多线程知识

一、进程和线程

进程(Process):是正在运行的程序的实例。

线程(Thread):是操作系统能够进行运算调度和分配资源的最小单位。

一个进程中可以并发多个线程,每条线程并行执行不同的任务。

二、Java的线程

1. Java实现多线程的方法:

1.1 继承Thread类

public class BusinessWindow extends Thread{     //窗口编号     private final int windowNum;      /**      * @param windowNum 窗口编号      */     public BusinessWindow(int windowNum) {         this.windowNum = windowNum;     }     @Override     public void run() {         Integer ticketNum = 0;         while ((ticketNum = BusinessHall.getSeqNum()) != null) {             System.out.println("【" + windowNum + "】号窗口 - 处理业务号码" + ticketNum);         }     } }

执行线程:

BusinessWindow win1 = new BusinessWindow(1); win1.start();

通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪状态,等待系统调度(等待CPU分配执行时间),并没有立即运行,一旦得到cpu时间片,就开始执行run()方法,run()方法是这个线程的主体内容,Run方法运行结束,此线程随即终止。run()方法只是thread类中的一个普通方法调用,还是在主线程里执行。其程序执行路径还是只有一条,还是要顺序执行

1.2、 实现Runnable接口

public class BusinessWindowRunnable implements Runnable {     @Override     public void run() {         Integer ticketNum = 0;         while ((ticketNum = BusinessHall.getSeqNum()) != null) {             System.out.println("【" + Thread.currentThread().getName() + "】号窗口 - 处理业务号码" + ticketNum);         }     }  }

执行线程:

BusinessWindowRunnable winRunnable = new BusinessWindowRunnable(); t1.start();

2. 线程的方法、类、修饰符

wait()/wait(long timeout)

Object中的方法,调用此方法会使当前线程进组阻塞,直到有其他线程调用了Object的 notify 或者 notifyAll方法才能将其唤醒,或者祖师时间到达了 timeout而自动唤醒。wait方法必须在同步方法中使用

notify

用于唤醒wait方法阻塞的线程。必须在同步方法中使用。

sleep

与wait方法一样,都可以是当前线程进入阻塞状态。sleep是Thread特有的方法,但sleep不需要在同步方法中执行。sleep方法在休眠之后会主动退出阻塞,而wait方法则需要被其他线程中断后才能退出阻塞。

interrupt

打断当前线程的阻塞状态(wait,sleep,join等都会使当前线程进入阻塞状态)。线程内部存在这名为interrupt flag的标识, 如果一个线程调用了interrupt,flag将被设置。

isInterrupted

是Thread的一个成员方法,它主要判断当前线程是否被中断,仅仅是对interrupt标识的一个判断,并不会影响标识发生任何改变。

interrupted

是一个静态方法,虽然其他也用于判断当前线程是否被中断,但是他和成员方法isInterrupted有很大区别。调用该方法会直接擦除线程的interrupt标识。

join

join某个线程A,会使当前线程B进入等待,直到线程A结束生命周期,或者到达给定的时间,此期间线程B是出于阻塞状态。

ThreadLocal(线程局部变量)

为每个使用该变量的线程都提供一个该变量值的副本,每个线程都可以独立地改变此副本,而不会和其它线程的副本冲突。

volatile(修饰符)

多线程中用来修饰某个变量,这个变量只有一份,多个线程对它的读和写是唯一的,不会存在不一致的情况。

CountDownLatch

这个类能够使一个线程等待其他线程完成各自的工作后再执行。适用于对某个任务适用多线程拆分,每个线程执行一部分任务(子任务)。使其并行执行,挺高执行效率,减少整个任务执行时间。

四、线程安全

概念:线程安全就是当多线程访问资源(数据)时,为了防止出现数据不一致、错乱的情况(脏读)。采用对资源加锁的形式,使得同一时刻只有一个线程可以访问此资源,直到这个线程对资源的操作结束之后,其他线程才能对其访问。

常被提到的线程安全或不安全的类

JDK 1.5 增加了java.util.concurrent 包,这个包里有一系列能够让 Java 的并发编程变得更加简单的类。

Java多线程知识

五、容易出现的线程安全问题

SimpleDateFormat类

public class TimeUtils { 	 	/** 错误的使用方式,有线程安全问题 */ 	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 	/** 	 * 错误的使用方式,有线程安全问题 	 */ 	public static String formatNoFafe(Date date) { 		return sdf.format(date); 	} } 

解决方案:用jdk1.8的日期格式化类

public class TimeUtils { 	 	private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"); 	/** 	 * 使用1.8提供的日期时间API 	 */ 	public static String formatSafe(Date date) { 		LocalDateTime ldt = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); 		return ldt.format(dtf); 	} }

六、线程池

线程池,通俗的理解就是有一个池子,里面存放着已经创建好的线程,当有任务提交给线程池执行时,池子中的某个线程会主动执行此任务。任务执行结束后,线程池中的线程被自动回收,释放资源。如果池子中的线程数量不够应付数量众多的任务时,则需要自动扩充新的线程到池子中,但是扩充的数量是有限的。

线程池的优点:

降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
提高响应速度:任务到达时,无需等待线程创建即可立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

Java线程池(Executor)

Excutor是线程池的顶级接口,真正线程池的接口是ExcutorService

Executors.newCachedThreadPool():核心线程为0,最大线程数为Integer. MAX_VALU
Executors.newFixedThreadPool(nThreads):固定大小的线程池。
Executors.newSingleThreadExecutor():单个线程的线程池。
Executors.newScheduledThreadPool():指定核心线程数的定时线程池 

线程池相关参数
corePoolSize:线程池的核心线程数。是预创建线程数,即在没有任务到来之前就创建corePoolSize个线程。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:线程池所能容纳的最大线程数
keepAliveTime:控制线程闲置时的超时时长,超过则终止该线程。

线程池的拒绝策略

  线程池中,有三个重要的参数,决定影响了拒绝策略:

corePoolSize - 核心线程数,也即最小的线程数。

workQueue - 阻塞队列 。 

maximumPoolSize - 最大线程数

  当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。

 

线程池共包括4种拒绝策略

ThreadPoolExecutor.AbortPolicy:拒绝任务并抛出异常 
ThreadPoolExecutor.DiscardPolicy:拒绝任务但不做任何动作 
ThreadPoolExecutor.CallerRunsPolicy:拒绝任务,并在调用者的线程中直接执行该任务 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃任务队列中的第一个(最老的)任务,然后把这个任务加进队列。 

ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());

Spring boot中的线程池

1. 在启动类上加注解@EnableAhttps://gitee.com/funtaster/thread-sample

 

版权声明:玥玥 发表于 2021-03-17 3:32:49。
转载请注明:Java多线程知识 | 女黑客导航