线程锁对象
锁对象 lock
Lock lock = new ReentrantLock(true);
创建锁对象 默认非公平锁 加true才是公平锁
wait
notify
notifyAll
wait sleep区别
wait是Object中定义的方法,可以有锁对象调用,让执行到该代码的线程进入到等待状态
sleep是Thread类中定义的静态方法,可以让执行到该代码的线程进入到等待状态
区别:sleep需要传入一个毫秒数,到达时间后会自动唤醒 wait不能自动唤醒,必须调用notity/notityAll方法唤醒
sleep方法保持锁状态进入等待状态 wait方法会解除锁状态,其他线程可以进入运行
ReentrantReadWriteLock类
-
读锁
- 可以被多个线程同时获取,即支持共享锁。
- 当没有写锁的时候,读锁可以被任意数量的读线程获取。
- 当有写锁的时候,读锁会等待写锁释放后再获取。
- 读锁是不可重入的,即同一个线程无法获取多次读锁。
-
写锁
- 只能被一个线程获取,即独占锁。
- 写锁是可重入的,即同一个线程可以多次获取写锁。
- 当有写锁的时候,其他线程的读锁和写锁请求都会被阻塞。
- 当有读锁的时候,写锁的请求会被阻塞,直到所有读锁都被释放。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EasyThreadC {
public static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static ReentrantLock rl = new ReentrantLock();
public static void method(){
System.out.println(Thread.currentThread().getName()+"进入方法");
Lock lock= rwl.readLock();
lock.lock();
System.out.println(Thread.currentThread().getName()+"加锁成功----读锁");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lock.unlock();
System.out.println(Thread.currentThread().getName()+"方法结束");
}
public static void methodWrite(){
System.out.println(Thread.currentThread().getName()+"进入方法");
Lock lock= rwl.writeLock();
lock.lock();
System.out.println(Thread.currentThread().getName()+"加锁成功----写锁");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lock.unlock();
System.out.println(Thread.currentThread().getName()+"方法结束");
}
public static void main(String[] args) {
Runnable run = EasyThreadC::method;
Runnable runWrite = EasyThreadC::methodWrite;
Thread a = new Thread(run);
a.start();
Thread b = new Thread(run);
b.start();
Thread c = new Thread(run);
c.start();
Thread d = new Thread(run);
d.start();
Thread e = new Thread(runWrite);
e.start();
Thread f = new Thread(runWrite);
f.start();
Thread g = new Thread(runWrite);
g.start();
Thread h = new Thread(runWrite);
h.start();
System.out.println("main------end");
}
}
线程池
池==重用 完成线程创建和管理,销毁线程工作 线程任务 Runable Callable Runnable run=EasyExecuters::method; tpe.execute(run); Callable<String> call=EasyExecuters::methodCall; Future<String> f=tpe.submit(call); //tpe.submit(run); System.out.println(f.get());//会等待线程执行完毕 //关闭线程对象 tpe.shutdown();
public class EasyExecuters {
//线程池 池==重用
//完成线程创建和管理,销毁线程工作
public static void main(String[] args) throws ExecutionException, InterruptedException {
BlockingQueue qu = new ArrayBlockingQueue(12);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5,10,10,
TimeUnit.SECONDS,qu,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//线程任务 Runable Callable
Runnable run=EasyExecuters::method;
tpe.execute(run);
Callable<String> call=EasyExecuters::methodCall;
Future<String> f=tpe.submit(call);
//tpe.submit(run);
System.out.println(f.get());//会等待线程执行完毕
//关闭线程对象
tpe.shutdown();
}
public static void method(){
System.out.println(Thread.currentThread().getName()+"执行代码");
}
public static String methodCall() throws InterruptedException{
System.out.println(Thread.currentThread().getName()+"执行代码Call");
Thread.sleep(2000);
return "callResult";
}
}
线程池的七个参数
在Java中,线程池通常使用ThreadPoolExecutor
类来实现,它有7个主要的参数:
-
corePoolSize(核心线程数):
- 线程池中维持的最小线程数。
- 即使这些线程处于空闲状态,也不会被终止。
- 在没有任务执行时,核心线程也不会被回收。
-
maximumPoolSize(最大线程数):
- 线程池中允许的最大线程数。
- 当队列满了且已有的线程数小于最大线程数时,会创建新的线程来处理任务。
-
keepAliveTime(线程存活时间):
- 当线程池中的线程数超过
corePoolSize
时,多余的空闲线程在等待新任务到来时会终止。 keepAliveTime
指定了这些空闲线程的存活时间。
- 当线程池中的线程数超过
-
unit(时间单位):
- 与
keepAliveTime
参数配合使用,指定keepAliveTime
的时间单位。 - 通常使用
TimeUnit
枚举类的实例,如TimeUnit.SECONDS
、TimeUnit.MINUTES
等。
- 与
-
workQueue(任务队列):
- 用于保存等待执行的任务的阻塞队列。
- 当所有
corePoolSize
大小的线程都在忙碌时,新任务会被添加到这个队列中。
-
threadFactory(线程工厂):
- 用于创建新线程的工厂。
- 可以通过自定义线程工厂来设置线程的名称、优先级等属性。
-
handler(拒绝处理策略):
- 当任务无法被添加到工作队列中时,线程池会采取的饱和策略。
- 常见的策略有:
AbortPolicy
(抛出RejectedExecutionException异常)、CallerRunsPolicy
(在调用者线程中执行任务)、DiscardOldestPolicy
(丢弃最早的未处理任务)、DiscardPolicy
(静默地丢弃任务)。
线程池的四种回绝策略
AbortPolicy 放弃该任务并会抛出一个异常 CallerrunsPolicy 调用者执行,让传递任务的线程执行此任务 DiscardOldestPolicy 放弃队列中最长的任务,不会抛出异常 DiscardPolicy 直接放弃新的任务,不会抛出异常
四种内置线程池(缓存线程池,定时运行线程池,边界线程池,单例线程池)
Java中提供了4种内置的线程池:
1. 缓存线程池(CachedThreadPool):
- 使用`Executors.newCachedThreadPool()`创建。
- 线程池初始没有任何线程,当有任务来时会创建新线程执行,闲置60秒后会被回收。
- 适用于执行大量短期异步任务的场景。
2. 定时运行线程池(ScheduledThreadPool):
- 使用`Executors.newScheduledThreadPool()`创建。
- 线程池包含`corePoolSize`指定数量的线程,用于执行定时或周期性任务。
- 适用于需要延迟执行或定期执行的任务。
3. 边界线程池(FixedThreadPool):
- 使用`Executors.newFixedThreadPool()`创建。
- 线程池大小固定,线程数等于`corePoolSize`和`maximumPoolSize`。
- 适用于负载比较平稳的场景,可控制并发的线程数。
4. 单线程池(SingleThreadExecutor):
- 使用`Executors.newSingleThreadExecutor()`创建。
- 线程池中只有一个线程,任务会被顺序执行。
- 适用于需要保证任务顺序执行的场景,例如日志记录。
线程池的工作原理
任务放置在工作队列中 池中是否有空闲的线程,如果有就让该线程执行任务 如果没有空闲的线程,判断池中的线程数量有没有达到核心线程数 如果没有达到创捷新的线程执行任务,如果已经达到,优先在队列中存储 直到队列填满; 工作队列填满后在添加新的任务,判断是否达到最大线程数,如果没有创建新的线程执行任务 知道填满最大线程数 已经填满的最大线程数,队列也已经填满,没有空闲的线程,就执行回绝策略 线程池中的线程达到(超过)核心线程数,超出的数量会根据存活时间,进行销毁,直到数量达到核心线程数 如果线程的数量少于核心线程数,不会消亡
死锁(Deadlock)是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵持状态。当线程1持有资源A并请求资源B,而线程2正持有资源B并请求资源A,这样两个线程都在等待对方释放资源,就会形成死锁。
什么是死锁
形成死锁需要满足以下4个条件:
1. **互斥条件**:资源只能被一个线程使用,不能被其他线程共享。
2. **请求和保持条件**:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
3. **不可剥夺条件**:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺。
4. **循环等待条件**:存在一个线程-资源的循环等待链。
这4个条件缺一不可,只要有任何一个条件不成立,死锁就不会发生。
常见的避免死锁的方法有:
1. 破坏互斥条件:改用共享资源代替独占资源。
2. 破坏请求和保持条件:一次性申请所有需要的资源。
3. 破坏不可剥夺条件:占用资源的线程结束时主动释放资源。
4. 破坏循环等待条件:对资源加上全局排序,按序申请资源。