一、锁对象 Lock接口
1、创建锁对象 ReentrantLock类
java
Lock lock=new ReentrantLock(true);
默认创建的是非公平锁 在创建锁对象时传入一个true参数 便会创建公平锁 先来后到
是重入锁 排他锁 加锁后不允许其它线程进入
2、加锁、解锁
(1)lock() unlock()
java
public class EasyThreadB {
Lock lock=new ReentrantLock(true);//创建锁对象
public void mehtod(){
lock.lock();//加锁
System.out.println(Thread.currentThread().getName()+"进入方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"结束方法");
lock.unlock();//解锁
}
public static void main(String[] args) {
Runnable run=new EasyThreadB()::mehtod;
Thread a=new Thread(run);
Thread b=new Thread(run);
a.start();
b.start();
}
}
(2)tryLock()
java
public class EasyThreadB {
Lock lock=new ReentrantLock(true);//创建锁对象
public void mehtod(){
// lock.tryLock()//尝试加锁 加锁成功 true 失败返回 false
if (lock.tryLock()){
System.out.println(Thread.currentThread().getName()+"进入方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"结束方法");
lock.unlock();//解锁
}else {
System.out.println("加锁未成功----------去执行别的代码");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
mehtod();
}
}
public static void main(String[] args) {
Runnable run=new EasyThreadB()::mehtod;
Thread a=new Thread(run);
Thread b=new Thread(run);
a.start();
b.start();
}
}
lock.tryLock() 尝试加锁 加锁成功 true 失败返回 false
3、读写锁 ReentrantReadWriteLock类
读锁(Read Lock):允许多个线程同时获得读锁,在没有写锁的情况下,多个线程可以并发地读取共享资源。
写锁(Write Lock):独占锁,一次只允许一个线程获取写锁进行写操作,当有线程持有写锁时,其他线程无法获取读锁或写锁。
java
public class EasyTreadC {
public static ReentrantReadWriteLock rrwl=new ReentrantReadWriteLock();
public static void method(){
System.out.println(Thread.currentThread().getName()+"进入方法");
Lock lock=rrwl.readLock();
lock.lock();
System.out.println(Thread.currentThread().getName()+"加锁成功---读锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"方法结束");
lock.unlock();
}
public static void methodWrite(){
System.out.println(Thread.currentThread().getName()+"进入方法");
Lock lock=rrwl.writeLock();
lock.lock();
System.out.println(Thread.currentThread().getName()+"加锁成功---写锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"方法结束");
lock.unlock();
}
public static void main(String[] args) {
Runnable run=EasyTreadC::method;
Runnable runWrite=EasyTreadC::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(run);
e.start();
Thread f=new Thread(runWrite);
f.start();
Thread g=new Thread(runWrite);
g.start();
Thread h=new Thread(runWrite);
h.start();
Thread i=new Thread(runWrite);
i.start();
System.out.println("main线程结束---------");
}
}
java
public class EasyThreadE {
public static void main(String[] args) {
EasyList list=new EasyList();
Runnable runSize=()->{list.size();};
Runnable runGet=()->{list.get(0);};
Runnable runAdd=()->{list.add(12);};
list.add(12);
Thread a=new Thread(runSize);
Thread b=new Thread(runGet);
Thread c=new Thread(runAdd);
a.start();b.start();c.start();
}
}
class EasyList{
private int[] values=new int[20];
private int size=0;
ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();
public int size(){
Lock readLock=rwLock.readLock();
readLock.lock();
System.out.println(Thread.currentThread().getName()+"Size开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"Size结束");
readLock.unlock();
return size;
}
public int get(int index){
Lock readLock=rwLock.readLock();
readLock.lock();
System.out.println(Thread.currentThread().getName()+"Get开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(index>=size){
throw new IndexOutOfBoundsException("index is "+index);
}
System.out.println(Thread.currentThread().getName()+"Get结束");
readLock.unlock();
return values[index];
}
public boolean add(int item){
Lock writeLock=rwLock.writeLock();
writeLock.lock();
System.out.println(Thread.currentThread().getName()+"Add开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(size>=values.length){
return false;
}
values[size++]=item;
System.out.println(Thread.currentThread().getName()+"Add结束");
writeLock.unlock();
return true;
}
}
4、 wait()与notify()、notifyAll()
notify():唤醒一条被该锁对象wait的线程 唤醒后线程在同步代码块外等待机会进入 进入后从 之wait代码后开始执行 notifyAll():唤醒全部被该对象wait的线程 wait():让执行到该代码的线程进入等待状态(等待池) 结束等待状态后进入就绪状态
java
public class EasyThreadD {
public static final Object OBJ=new Object();
public static void method(){
System.out.println(Thread.currentThread().getName()+"进入方法");
synchronized (OBJ) {
OBJ.notify();
OBJ.notifyAll();
System.out.println(Thread.currentThread().getName() + "进入同步代码块");
try {
try {
System.out.println(Thread.currentThread().getName() + "进入等待状态");
OBJ.wait();
System.out.println(Thread.currentThread().getName() + "重新运行");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "结束同步代码块");
OBJ.notify();
}
}
public static void main(String[] args) {
Runnable run=EasyThreadD::method;
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();
}
}
wait和sleep的区别
wait 是Object中定义的方法,可以让锁对象调用,让执行到该代码的线程进入到等待状态 sleep是Thread类中定义的静态方法,可以让执行到该行的线程进入等待状态 区别: 1 sleep需要传入一个毫秒数,到达时间后会自动唤醒 wait不能自动唤醒,必须调用notify/notifyAll方法唤醒 2 sleep方法保持锁状态进入等待状态 wait方法会解除锁的状态,其他线程可以进入 运行
二、线程池 ThreadPoolExecutor类
池==重用 完成线程创建和管理、销毁工作
1、线程池对象执行Runnable 和Callable
线程任务 Runnable Callable
Callable只能用submit()添加执行
ThreadPoolExecutor类的方法
添加执行线程任务 execute(Runnable run) submit(Callable call) submit(Runnable run)
submit()有返回值 返回值与call的泛型相同
关闭线程池对象 shutdown()
java
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());
//线程任务 Runnable Callable
Runnable run=EasyExecuters::method;
tpe.execute(run);
Callable<String> call=EasyExecuters::methodCall;
Future<String> f=tpe.submit(call);
// tpe.execute(call);//Callable只能用submit提交
// 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";
}
}
2、Future类
Future是 Java 中一个用于表示异步计算结果的接口。它主要用于处理那些可能需要较长时间运行的任务,使得主线程可以继续执行其他工作,而无需等待任务完成。
java
Future<T> f=tpe.submit(call);
方法:
cancel(boolean mayInterruptIfRunning): 尝试取消任务的执行。
isCancelled(): 判断任务是否被取消。
isDone(): 判断任务是否已经完成。
get(): 获取任务的结果,如果任务尚未完成则阻塞。
get(long timeout, TimeUnit unit): 在指定时间内获取任务的结果,如果超时则抛出
TimeoutException。
Future 的工作机制
当我们提交一个任务到线程池时,线程池会返回一个
Future
对象,通过这个对象可以控制任务的执行状态,并在任务完成后获取结果。
3、线程池7个参数
(1)核心线程数
(2)最大线程数
(3)保持存活时间
(4)保持存活时间的时间单位(TimeUnit.SECONDS)秒
(5)工作队列BlockingQueue接口
(6)线程工厂(Executors.defaultThreadFactory())
(7)回绝策略(new ThreadPoolExecutor.AbortPolicy())
4、四种回绝策略
AbortPolicy (默认)放弃该任务并会抛出一个异常RejectedExecutionException CallerRunsPolicy 调用者执行,让传递任务的线程执行此任务 DiscardOldestPolicy 放弃队列中时间最长的任务,将新任务加入队列,不会抛出异常 DiscardPolicy 直接放弃新的任务,不会抛出异常
5、 线程池的工作原理
任务放置在工作队列中 1>池中是否有空闲的线程,若有让该线程执行任务 2>如果池中没有空闲的线程,判断池中的线程数量有没有达到核心线程数 3>如果没有达到,创建新的线程执行任务,如果已经达到,优先在队列中存储,直到队列填满 4>工作队列填满后再添加新的任务,判断是否达到最大线程数,如果没有,创建新的线程执行任务 直到填满最大线程数 5>已经填满最大线程数,队列也已经填满,没有空闲的线程,就执行回绝策略 6>线程池中的线程达到(超过)核心线程数,超出的数量会根据存活时间,进行销毁,直到数量达到核 心线程数,如果线程数少于核心线程数,不会消亡
6、 java中内置的线程池对象
Executors.newCachedThreadPool(); 可以根据工作任务创建线程,如果没有新的线程就创建新的线程,队列中不放任务 线程存活时间60s Executors.newFixedThreadPool(10); 设定最大线程数量的线程池 Executors.newScheduledThreadPool(10); 提供定时运行的处理方案 Executors.newSingleThreadExecutor(); 创建一个具有单个线程的线程池 保障任务队列完全按照顺序执行
三、枚举类
默认继承Enum 但不能用extents声明,只能用enum声明
首行 必须枚举所有的实例
不可序列化 不可克隆
java
public enum EasyColor {
RED,YELLOW,GREEN,BLUE,PINK;
public void printColor(){
System.out.println(this.name());
System.out.println(this.ordinal());
}
}
测试类
java
class Test{
public static void main(String[] args) {
EasyColor.GREEN.printColor();
}
}
四、死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。当多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进,这种情况就是死锁。
1、死锁产生的四个必要条件
(1)互斥条件
进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
(2)不剥夺条件
进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
(3)请求和保持条件
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(4)循环等待条件
存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。
2、避免死锁
(1)破坏"请求和保持"条件
想办法,让进程不要那么贪心,自己已经有了资源就不要去竞争那些不可抢占的资源。比如,让进程在申请资源时,一次性申请所有需要用到的资源,不要一次一次来申请,当申请的资源有一些没空,那就让线程等待。不过这个方法比较浪费资源,进程可能经常处于饥饿状态。还有一种方法是,要求进程在申请资源前,要释放自己拥有的资源。
(2)破坏"不可抢占"条件
允许进程进行抢占,方法一:如果去抢资源,被拒绝,就释放自己的资源。方法二:操作系统允许抢,只要你优先级大,可以抢到。
(3)破坏"循环等待"条件
将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出
3、死锁解决方法
在有些情况下死锁是可以避免的。以下是避免死锁的技术:
(1)加锁顺序
线程按照一定的顺序加锁
(2)加锁时限
线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁
(3)死锁检测
步骤一:每个进程、每个资源制定唯一编号
步骤二:设定一张资源分配表,记录各进程与占用资源之间的关系
步骤三:设置一张进程等待表,记录各进程与要申请资源之间的关系