锁对象 Lock
ReentrantLock(fair); fair值默认为false,是非公平锁;fair值为true,是公平锁
加锁 lock.lock();
尝试加锁 lock.tryLock();成功加锁返回true,加锁失败返回false
解锁 lock.unlock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class EasyThreadB {
//锁对象 Lock
//ReentrantLock可重入锁
Lock lock = new ReentrantLock(true);//创建锁对象 true公平锁
public void method(){
//lock.lock();//加锁
//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);
}
method();
}
}
public static void main(String[] args) {
Runnable run=new EasyThreadB()::method;
Thread a = new Thread(run);
Thread b = new Thread(run);
a.start();
b.start();
}
}
锁容器 ReentrantReadWriteLock读写锁
rrwl.readLock(); 读锁允许多个线程同时读取共享资源
rrwl.writeLock(); 写锁要求独占访问
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EasyThreadC {
//锁容器 ReentrantReadWriteLock
public static ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
public static ReentrantLock rl = new ReentrantLock();
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=EasyThreadC::method;
Runnable runWrite=EasyThreadC::methodWrite;
Thread thread=new Thread(run);
thread.start();
Thread thread1=new Thread(run);
thread1.start();
Thread thread2=new Thread(runWrite);
thread2.start();
Thread thread3=new Thread(runWrite);
thread3.start();
System.out.println("main线程结束-----");
}
}
wait方法
OBJ.wait(); 让执行到该代码的线程进入到等待状态(等待池)
OBJ.notify(); 唤醒一条被该锁对象wait的线程 OBJ.notifyAll(); 唤醒全部被锁对象wait的线程
wait和sleep的区别
wait是Object中定义的方法,可以有锁对象调用,让执行到该代码的线程进入到等待状态
sleep是Thread类中定义的静态方法,可以让执行到该行代码的线程进入等待状态
区别:1、sleep需要传入一个毫秒数,到达时间后自动唤醒
wait不能自动唤醒,必须调用notify/notifyAll方法唤醒
2、sleep方法保持锁状态进入等待状态
wait方法会解除锁状态,其他线程可以进入运行
public class EasyTheadD {
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 {
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 = EasyTheadD::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();
}
}
死锁
概念
Java中的死锁是指多个线程同时占用一些共享资源且彼此相互等待,从而导致所有的线程都被阻塞,不能继续执行程序的情况。这就好比在一个十字路口,没有交警也没有红绿灯指挥通行,所有的车辆都占据道路且互相等待对方让出路权,此时就很容易造成道路堵死,这其实就是道路的“死锁”。
死锁案例
public class DeadLock {
//定义2两个锁定的对象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1(){
//锁定对象1
synchronized (lock1){
System.out.println("Method 1:获取对象lock1的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//锁定对象2
synchronized (lock2){
System.out.println("Method 1:获取对象lock2的锁");
}
}
}
public void method2(){
//锁定对象1
synchronized (lock2){
System.out.println("Method 2:获取对象lock2的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//锁定对象2
synchronized (lock1){
System.out.println("Method 2:获取对象lock1的锁");
}
}
}
public static void main(String[] args) {
final DeadLock deadLock = new DeadLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
deadLock.method1();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
deadLock.method2();
}
});
//开启线程
t1.start();
t2.start();
}
}
在上面的案例中,定义了两个方法method1和method2,这两个方法分别占用了lock1和lock2两个锁,并且在执行过程中会互相等待对方的锁,从而形成了死锁。如果我们运行该程序,就会看到两个线程互相等待对方释放自己占用的锁,这最终会导致所有的线程都被阻塞。
产生条件
互斥条件:多个线程需同时访问一个共享资源,但每次只能有一个线程访问该资源;
请求和保持条件:一个线程在持有一个资源的同时,还想请求另一个资源;
不可剥夺条件:已经分配的资源不能被其他线程剥夺;
循环(环路)等待条件:多个线程形成了一个循环等待资源的链路,例如线程A等待线程B释放自己所占用的资源,线程B等待线程C释放自己所占用的资源,而线程C又等待线程A释放自己所占用的资源。
解决办法
避免使用多个锁;
尽可能减少同步代码块的长度;
尝试改变锁的获取顺序,避免线程之间形成循环等待;
使用定时锁,当等待时间超过一定的时间值后就自动释放锁。
案例优化
public class DeadLock {
//定义2两个锁定的对象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1(){
//锁定对象1
synchronized (lock1){
System.out.println("Method 1:获取对象lock1的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//锁定对象2
synchronized (lock2){
System.out.println("Method 1:获取对象lock2的锁");
}
}
}
public void method2(){
//锁定对象1
synchronized (lock1){
System.out.println("Method 2:获取对象lock2的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//锁定对象2
synchronized (lock2){
System.out.println("Method 2:获取对象lock1的锁");
}
}
}
public static void main(String[] args) {
final DeadLock deadLock = new DeadLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
deadLock.method1();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
deadLock.method2();
}
});
//开启线程
t1.start();
t2.start();
}
}
上面的这个案例与之前的案例代码几乎一样,但与之不同的是,本案例中的方法method1和method2,都是先占用lock1锁,再占用lock2锁,这样就避免了死锁的发生,因为这两个方法占用锁的顺序是一致的。所以我们在编写多线程代码时,需要特别注意线程死锁的问题,避免影响程序的正常执行。
线程池 为了让线程达到重用的效果
线程池完成线程的创建、管理和销毁工作
线程池七个参数
ThreadPoolExecutor(核心线程数,最大线程数 ,空闲线程存活时间 ,空闲线程存活时间单位 , 工作队列(阻塞队列),线程工厂 , 回绝策略)
四种回绝策略
AbortPolicy(默认) 放弃该任务并会抛出一个异常RejectedExecutionException
CallerRunsPolicy 调用者执行,让传递任务的线程执行
DiscardOldestPolicy 放弃队列中时间最长的任务,不会抛出异常
DiscardPolicy 直接放弃新的任务,不会抛出异常
线程池的工作原理
任务放置在工作队列中
1>判断池中是否有空闲的线程,如果有让该线程执行任务
2>如果池中没有空闲的线程,判断线程数量是否达到核心线程数
3>如果没有达到,创建新的线程执行任务,直到填满核心线程数,如果已经达到,优先在队列中储存,直到队列填满
4>工作队列填满后再添加新的任务,判断是否达到最大线程数;如果没有,创建新的线程执行任务,直到填满最大线程数
5>已经填满最大线程数,队列也已经填满。没有空闲的线程,就执行回绝策略
如果线程池中的线程达到(超过)核心线程数,超出的数量会根据存活时间,进行销毁,直到数量达到核心线程数。如果线程的数量少于核心线程数,不会销毁
java中内置的线程池对象
Executors.newCachedThreadPool();可以根据工作任务来创建线程,如果没有空闲的线程就创建新的线程 线程存活时间60秒
Executors.newFixedThreadPool(10);设定最大线程数量的线程池
Executors.newScheduledThreadPool(10);提供定时运行的处理方案
Executors.newSingleThreadExecutor();创建一个具有单个线程的线程池 保证任务队列完全按照顺序执行
线程任务Runnable和Callable
Runnable可以使用tpe.execute(run);和tpe.execute(run);调用
Callable只能使用tpe.submit(call);调用
f.cancel(true);中断线程
tpe.shutdown();关闭线程池对象
import java.util.concurrent.*;
public class EasyExecutors {
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 = EasyExecutors::method;
tpe.execute(run);
Callable<String> call= EasyExecutors::methodCall;
Future<String> f=tpe.submit(call);
//f.cancel(true);中断线程方式
//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";
}