目录
1.线程的创建
使用场景:在一个页面同时下载多个图片;服务器同时相应多个请求...
创建:1.创建一个类使用Runnable接口,将所要执行的内容放在run()方法中。2.创建Runnable对象,将它放在一个线程对象Thread中。3.运行该线程对象。
由于Runnable接口只有一个方法,所以可以使用lambda表达式。
@Test
void easyThread(){
Runnable task1=()->{
int i;
for (i=0;i<100;i++){
logger.info("task1");
}
logger.info(String.valueOf(i));
};
Runnable task2=()->{
int i;
for (i=0;i<100;i++) {
logger.info("task2");
}
logger.info(String.valueOf(i));
};
new Thread(task1).start();
new Thread(task2).start();
}
//2个任务交替执行
2.中断线程
中断使用t.interrupt()。获取当前线程用Thread.currentThread()。
判断状态:1.判断特定线程的状态,t.isInterrupted() ; 2.判断当前线程的状态,Thread.interrupted(), 这一判断会将中断状态重置为false
其他方法可在官方文档查询:JDK 19 Documentation - Home
System.out.println(Thread.currentThread());
Thread.currentThread().interrupt();
System.out.println(Thread.interrupted());//true
System.out.println(Thread.interrupted());//false
System.out.println(Thread.currentThread().isInterrupted());//false
3.同步
3.1锁
由于各线程可以公用资源,当多个线程需要对同一变量改动时极易产生错乱的风险。
ReentrantLock是重入锁,一个被锁保护的代码可以调用另一个使用相同锁的代码,避免创建更多新锁。
锁必须在finally语句中释放,避免因为异常而跳转。
(这里的lock相当于mutex)
var lock=new ReentrantLock();
Runnable task1=()->{
lock.lock();
try {
//
}finally {
lock.unlock();
}
};
3.2条件对象
满足某个条件或获得某种资源后才可以进行,此时可以使用Condition。(相当于semaphore)
ReentrantLock lock=new ReentrantLock();
Condition s=lock.newCondition();
Runnable producer=()->{
lock.lock();
try{
while (empty.get() <1) {
s.await();
}
...
s.signalAll()
} finally{lock.unlock();}
}
Condition必须在lock()之后只用;lock用于互斥,Condition用于同步
3.3对象锁synchronized
每个对象都有一个锁,隐式锁,代替lock,简化代码。放在方法的返回值的前面,表示这个方法不能同时被两个线程调用。条件是 wait(); notifyAll();
public synchronized void addI(int i) throws InterruptedException {
while (this.i+i>10)
wait();
this.i +=i;
notifyAll();
}
3.4字段锁volatile
当需要在get和set方法前加synchronized时,可直接在字段前加volatile代替
private volatile int i;
等效于
public synchronized int getI() {
return i;
}
public synchronized void setI(int i) {
this.i = i;
}
3.5原子性
将变量声明为原子类,它们进行数值变化时不会被打断。这些类使用高效的机器指令,执行速度更快。
public static AtomicInteger n=new AtomicInteger(6);
public static int n2=n.incrementAndGet();
3.6死锁
程序挂起时,Ctrl+\可以查看所有线程,找到哪里阻塞。
3.7线程局部变量ThreadLocal
有时需要避免共享变量,为每个线程设计单独的对象,但这样又很低效,可以使用ThreadLocal
public class Main{
public static final ThreadLocal<SimpleDateFormat> format=ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd"));
//...
Runnable task2=()->{
String d1=format.get().format(new Date());
//...
}
}
4.线程安全
4.1阻塞队列
BlockingQueue,操作方法类似list,是condition的改进版,对队列操作及其有用。
public void operation() throws InterruptedException {
String str1=queue.take(); //没有将等待
queue.put("aa"); //放不下将等待
String str2= queue.poll(30, TimeUnit.MILLISECONDS);//有限等待
queue.offer("bb",30,TimeUnit.MILLISECONDS);
}
5.线程池
当有大量线程时能以某种策略自动管理这些线程。
主要实现方式:
@Test
void ThreadPool(){
ExecutorService executorService= Executors.newCachedThreadPool(); //选择一种线程池
Runnable task1=()->{ //构造任务
System.out.println("task1");
};
executorService.submit(task1); //加入线程池
executorService.shutdown(); //关闭
}
线程池类型
CachedThreadPool
特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
应用场景:执行大量、耗时少的任务。
FixedThreadPool
特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
应用场景:控制线程最大并发数。
SingleThreadExecutor
特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等
ScheduledThreadPool
特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
应用场景:执行定时或周期性的任务。
参考
线程池的总结来源于Java 多线程:彻底搞懂线程池,讲的比较清晰