今日重点:
线程锁对象 wait notify notifyAll wait和sleep区别
Lock ReentrantLock true 公平锁 方法lock tryLock unlock ReentrantReadWriteLock
线程池的定义 Callable 线程池对象执行Runnable 和Callable Future get
线程池的七个参数 线程池的四种回绝策略 四种内置线程池(缓存线程池,定时运行线程池,边界线程池,单例线程池) 线程池的工作原理
补充:继承Thread实现线程,每个线程数据是独立的;实现Runnable接口,传入同一个run,run是共享的,run里的数据也是共享的。
锁对象 Lock
创建锁对象
对于所有锁对象,初始化时默认是非公平锁,传入true是公平锁
Lock lock=new ReentrantLock();
ReentrantReadWriteLock rrwl=new ReentrantReadWriteLock(true);
ReentrantLock rtl=new ReentrantLock(true);
使用Lock
被lock(或tryLock)和unLock方法包裹的代码块相对于同步代码块
相比之下tryLock方法更好一些,该方法具有返回值,能反馈是否加锁成功,便于设置处理加锁成功和失败的情况。
lock.lock();//加锁 配合lock.unlock();//解锁,不解就会进入死锁
public void method() {
lock.lock();//加锁
System.out.println(Thread.currentThread().getName()+"进入方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"进入方法");
lock.unlock();//解锁,不解就会进入死锁
}
lock.tryLock()//尝试加锁,成功返回true失败返回false 配合 lock.unlock();
public void method() {
//lock.lock();//加锁
if(lock.tryLock()) {//尝试加锁,成功返回true失败返回false
System.out.println(Thread.currentThread().getName()+"进入方法");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"进入方法");
lock.unlock();//解锁,不解就会进入死锁
}else {
System.out.println("未成功加锁---执行其他任务等会回来执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
method();
}
锁容器ReentrantReadWriteLock
锁容器,里面有读锁和写锁 同一个锁容器的两个锁是互斥的,和定义在哪个方法或者代码块无关
注意:
1.没有线程加任何锁可以加写锁,加后没有线程可以再加任何锁
2.读锁是共享锁,加读锁其他线程也可以加读锁
加读锁演示:
package com.easy25;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EasyC {
//lock与unlock之间的代码块是同步代码块
public static ReentrantReadWriteLock rrwl=new ReentrantReadWriteLock();
public static ReentrantLock rtl=new ReentrantLock(true);
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"方法结束");
lock.unlock();
}
public static void main(String[] args) {
Runnable run=EasyC::method;
Runnable runWrite=EasyC::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();
System.out.println("main线程结束");
}
}
加写锁演示:
package com.easy25;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EasyC {
//锁容器,里面有读锁和写锁 同一个锁容器的两个锁是互斥的,和定义在哪个方法或者代码块无关
//没有线程加任何锁可以加写锁,加后没有线程可以再加任何锁
//读锁是共享锁,加读锁其他线程也可以加读锁
//传入boolean值,true是公平锁,false是非公平锁,默认非公平锁
//lock与unlock之间的代码块是同步代码块
public static ReentrantReadWriteLock rrwl=new ReentrantReadWriteLock();
public static ReentrantLock rtl=new ReentrantLock(true);
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
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) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"方法结束");
lock.unlock();
}
public static void main(String[] args) {
Runnable run=EasyC::method;
Runnable runWrite=EasyC::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 h=new Thread(runWrite);
h.start();
System.out.println("main线程结束");
}
}
锁对象的wait notify notifyAll方法
只有是锁对象时才能调用wait notify notifyAll方法,在那个地方是锁对象才能在那个地方用
OBJ.wait();
让执行到该行代码的线程进入等待状态,在锁对象的等待池等待
OBJ.notify();
唤醒一条被该锁对象wait的线程,使其进入就绪状态,在其获得锁对象后从上次运行的位置开始运行
OBJ.notifyAll();
唤醒所有被该锁对象wait的线程
代码演示:
public class EasyD {
public static final Object OBJ=new Object();
public static void method() {
System.out.println(Thread.currentThread().getName()+"进入方法");
synchronized (OBJ) {
OBJ.notify();//唤醒一条被该锁对象wait的线程,使其进入就绪状态,在其获得锁对象后从上次运行的位置开始运行
//OBJ.notifyAll();//唤醒所有被该锁对象wait的线程
System.out.println(Thread.currentThread().getName()+"进入同步代码块");
try {
System.out.println(Thread.currentThread().getName()+"进入等待状态");
OBJ.wait();//让执行到该行代码的线程进入等待状态,在锁对象的等待池等待
System.out.println(Thread.currentThread().getName()+"重新运行");
//只有某个线程执行notify方法才能唤醒
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"进入同步代码块");
OBJ.notify();//最后在唤醒,防止每次换出一个进去一个
}
}
public static void main(String[] args) {
Runnable run=EasyD::method;
Thread a=new Thread(run);
a.start();
Thread b=new Thread(run);
b.start();
Thread c=new Thread(run);
c.start();
}
}
线程池
池的目标就是重用 线程池的目的就是线程的重用
线程池的作用:完成线程创建、管理和销毁工作
使用要求:线程池对象使用完毕要调用shutdown方法关闭
线程池创建的七个参数
BlockingQueue<Runnable> q=new ArrayBlockingQueue(12);
//核心线程数、最大线程数、存活时间、时间单位、工作队列、工厂模式、默认处理
ThreadPoolExecutor tpe=new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, q,
Executors.privilegedThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
线程池对象执行Runnable 和Callable
public class EasyE {
public static void main(String[] args) throws InterruptedException, ExecutionException {
BlockingQueue<Runnable> q=new ArrayBlockingQueue(12);
//核心线程数、最大线程数、存活时间、时间单位、工作队列、工厂模式、默认处理
ThreadPoolExecutor tpe=new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, q,
Executors.privilegedThreadFactory(),newThreadPoolExecutor.AbortPolicy());
//线程的任务 Runnable Callable
Runnable run=EasyE::method;
//线程池接收处理任务
tpe.execute(run);
//可以加<String>
Callable call=EasyE::methodCall;
//Future接收 定义类型都要一致
Future<String> f=tpe.submit(call);
//tpe.submit(run);
//get方法会等待线程执行完毕 延迟效果
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";
}
}
Callable与Future
线程池不仅可以处理Runnable任务,还可以处理Callable任务
实现Callable要重写call方法,call方法具有泛型返回值,并且可以抛出异常
call方法返回值要用Future实例化对象的get方法获取
class p implements Callable{
@Override
public Object call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
完整演示:Future成员方法get方法会等待线程执行完毕再获取返回值 具有延迟效果
//可以加<String>
Callable<String> call=EasyE::methodCall;
//Future接收 定义类型都要一致
Future<String> f=tpe.submit(call);
//tpe.submit(run);
//get方法会等待线程执行完毕 延迟效果
System.out.println(f.get());
//关闭线程池对象
tpe.shutdown();
public static String methodCall() throws InterruptedException{
System.out.println(Thread.currentThread().getName()+"执行代码call");
Thread.sleep(2000);
return "callResult";
}
线程池重点
七个参数
核心线程数、最大线程数、存活时间、时间单位、工作队列、工厂模式、回绝策略
工作原理
任务放置在工作队列中
1.池中是否有空闲线程,如果有就让该线程执行任务,
2.如果没有空闲线程,判断池中的数量也没有达到核心线程数,如果没有达到核心线程数,会创建新的线程执行任务;
3.如果已经达到,优先在队列中存储直到队列填满;
4.工作队列填满时再加入新的任务,会判断是否达到最大线程数,如果没有就创建新的线程执行新的任务,直到达到最大线程数;
5.如果线程数已经达到最大线程数,队列也已经填满,没有空闲的线程,就执行回绝策略
线程池中的线程达到或超过核心线程数,超出的数量会根据存活时间进行销毁,直到数量达到核心线程数,如果线程的数量小于核心线程数,不会消亡。
四种回绝策略
AbortPolicy 默认 放弃该任务并抛出异常
CallerRunsPolicy 调用者执行 不抛出异常 让传递任务的线程执行此任务
DiscardOldestPolicy 放弃队列中存在时间最长的任务,不会抛出异常
DiscardPolicy 直接放弃新的任务,不会抛出异常
java中内置的四种线程池对象
缓存线程池
可以根据工作任务创建线程,如果没有空闲的线程就创建新的线程 线程存活时间60s
Executors.newCachedThreadPool();
边界线程池
设定最大线程数的线程池
Executors.newFixedThreadPool(10);
定时运行线程池
和fixed类型,并且提供定时运行的处理方案
Executors.newScheduledThreadPool(10);
单例线程池
创建一个具有单个线程的线程池(单例模式),保障任务队列完全按照顺序执行
Executors.newSingleThreadExecutor();
扩展
ArrayList与Vector性能比较,如何优化Vector
两者底层都是数组实现,由于 ArrayList
不是线程安全的,它在单线程环境下通常比 Vector
有更好的性能,尤其是在迭代和随机访问元素时;Vector
的同步机制增加了额外的开销,这在高并发环境下可能会影响性能。
优化方法:将synchronized关键字换成读锁和写锁,读操作时效率会大大提升
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EasyList{
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();
}
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;
}
}
什么是死锁,什么情况下会发生死锁现象
死锁是指两个或多个进程(或线程)在执行过程中,因争夺资源而造成的一种僵局。
1. 互斥条件: 一个资源每次只能被一个进程使用
2. 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3. 不可强行占有: 进程已获得的资源,在末使用完之前,不能强行剥夺。
4. 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
要避免死锁,可以采取以下一些策略:
避免占有和等待:要求进程在开始执行前一次性请求所有需要的资源。
资源有序分配:按照某种全局顺序来请求资源,确保所有进程都按照这个顺序来请求。
超时:允许进程请求资源时设置超时,如果超时则释放所有资源并重试。
资源剥夺:当检测到死锁时,可以强制剥夺某个进程的资源并分配给其他进程。
死锁检测:定期检测资源分配图是否存在循环等待,如果检测到死锁,采取措施解决。
在编程中,特别是在多线程环境中,可以通过合理设计同步机制和资源访问策略来避免死锁的发生。
wait和sleep区别
wait是Object内定义的方法,由锁对象调用,只能在其负责的同步方法或同步块中调用,让执行到该行代码的线程进入等待状态
sleep是Thread类中定义的静态方法,可以在任何地方调用,也可以让执行到该行的线程进入等待状态
区别:
1.功能上:sleep需要传入一个毫秒数,到达时间后会自动唤醒;wait不能自动唤醒,必须要其锁对象的 notify或 notifyAll方法唤醒;
2.sleep方法不会释放任何锁,wait方法会释放当前对象的锁,允许其他线程进入同步方法或同步块。