多线程的创建方式
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用匿名内部类的形式创建线程
4)使用lambda表达式创建线程
5)使用callable和Future创建线程
6)使用线程池例如用Executor框架
7)spring @Async异步注解
继承Thread类创建线程
public class Thread01 extends Thread {
/**
* 线程执行的代码, 就是在run方法中 执行完毕 线程死亡
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "<我是子线程>");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "<阻塞完毕!>");
}
public static void main(String[] args) {
//启动线程 调用start方法而不是run方法
System.out.println(Thread.currentThread().getName());
//调用start() 线程不会立即被cpu调度执行 就绪状态--等待cpu调度 线程从就绪到运行状态
new Thread01().start();
new Thread01().start();
System.out.println("主线程执行完毕");
}
}
实现Runnable接口创建线程
public class ThreadRunnable01 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "<我是子线程>");
}
public static void main(String[] args) {
//1 实现Runnable接口 启动线程
new Thread(new ThreadRunnable01()).start();
//2 使用匿名内部类创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "<我是子线程>");
}
}).start();
//3 lambda表达式创建线程
new Thread(() -> System.out.println(Thread.currentThread().getName() + "<我是子线程>"), "这个参数可以设置线程名称").start();
}
}
使用callable和Future创建线程
callable和Future 线程可以获取到返回结果, 底层基于LockSupport
import java.util.concurrent.Callable;
public class ThreadCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始执行..");
try {
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "返回1");
return 1;
}
}
测试类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.LockSupport;
public class Thread02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadCallable threadCallable = new ThreadCallable();
FutureTask<Integer> integerFutureTask = new FutureTask<>(threadCallable);
new Thread(integerFutureTask).start();
//我的主线程需要等待我们的子线程给我的返回结果
//调用get()方法获取返回结果, 底层会调用LockSupport.park()方法让我们的主线程阻塞, 等待子线程执行
// 等子线程执行完毕之后, 执行LockSupport.unpark() 唤醒我们的主线程, 这样我们的主线程就可以拿到返回结果了
Integer result = integerFutureTask.get();
System.out.println(Thread.currentThread().getName() + "," + result);
//juc 并发包
// LockSupport.park(); //阻塞
// LockSupport.unpark(); //唤醒
//类似于下面的代码
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是子线程开始");
LockSupport.park(); //让线程阻塞
System.out.println("我是子线程结束");
}
});
thread.start();
try {
Thread.sleep(3000);
} catch (Exception e) {
}
LockSupport.unpark(thread); //唤醒线程
}
}
使用线程池例如用Executor框架
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Thread03 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "我是子线程");
}
});
executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "我是子线程"));
}
}
spring @Async异步注解
直接在spring项目中,需要异步的方法上使用该注解就可以了(org.springframework.scheduling.annotation.Async),内部就会开辟一条线程独自去执行该方法
控制层
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@CrossOrigin
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
@Resource
private TestService testService;
@GetMapping("/test1")
public String test1() {
log.info("<log:1>");
testService.async();
log.info("<log:3>");
return "test1";
}
}
业务逻辑层
public interface TestService {
void async();
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class TestServiceImpl implements TestService {
@Override
@Async
public void async() {
try {
log.info("目标方法执行开始,阻塞3s");
Thread.sleep(3000);
log.info("<log:2>");
} catch (Exception e) {
}
}
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
手写@Async异步注解
1.aop拦截只要在我们的方法上有使用到@MyAsync(自定义的注解)就单独的开启一个异步线程去执行我们的目标方法
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAsync {
}
AOP拦截
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class ExtThreadAsyncAop {
/**
* 环绕通知
* 拦截方法
*
* @param joinPoint
*/
@Around(value = "@annotation(com.example.demo.async.MyAsync)")
public void around(ProceedingJoinPoint joinPoint) {
log.info(">环绕通知开始执行<");
new Thread(() -> {
try {
joinPoint.proceed(); //目标方法
} catch (Throwable e) {
throw new RuntimeException(e);
}
}).start();
log.info(">环绕通知结束执行<");
}
}
使用
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class TestServiceImpl implements TestService {
@Override
// @Async
@MyAsync
public void async() {
try {
log.info("目标方法执行开始,阻塞3s");
Thread.sleep(3000);
log.info("<log:2>");
} catch (Exception e) {
}
}
}
Synchronized锁的基本用法
1.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码快前要获得给定对象 的锁
2.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例 的锁
3.修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码前要获得当前类对象 的锁
案例代码
public class ThreadCount implements Runnable {
private static int count = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
}
// synchronized (this) {
// if (count > 1) {
// count--;
// System.out.println(Thread.currentThread().getName() + "," + count);
// }
// }
// cal();
cal2();
}
}
/**
* 如果将synchronized锁加在实例方法上, 则使用this锁
*/
private synchronized void cal() {
synchronized (this) { //写在实例方法上相当于写在这里面
}
if (count > 1) {
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
/**
* 如果将synchronized锁加在静态方法上, 类名.class
*/
private static synchronized void cal2() {
synchronized (ThreadCount.class) { //写在静态方法上相当于写在这里面
}
if (count > 1) {
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
new Thread(threadCount).start();
new Thread(threadCount).start();
}
}
线程如何实现同步
线程如何保证线程安全性问题
1.使用synchronized锁, JDK1.6开始 锁的升级过程 偏向锁 -> 轻量级锁 -> 重量级锁
2.使用Lock锁 (JUC并发包中), 需要自己实现锁的升级过程, 底层是基于aqs+cas实现
3.使用Threadlocal, 需要注意内存泄漏的问题
4.原子类CAS非阻塞式 (基于CAS可以纯手写一个Lock锁)
wait()、notify()
wait方法,阻塞锁对象, 必须在锁中使用,否则会报错
notify唤醒锁对象
public class Test {
private Object objectLock = new Object();
public static void main(String[] args) {
new Test().print();
}
public void print() {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + ">1<");
try {
/**
* this.wait() 释放锁的资源, 同时当前线程会阻塞
* this.wait()、notify() 需要放到 synchronized 同步代码块中使用
*/
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ">2<");
}
}
}).start();
try {
Thread.sleep(3000);
synchronized (objectLock) {
/**
* 主线程3s之后 唤醒子线程
*/
objectLock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
wait、notify生产者与消费者模型
public class Test {
/**
* 共享对象Res
*/
class Res {
public String username;
public char sex;
/**
* false 输入线程, 输入值
* true 输出线程, 输出值
*/
public boolean flag = false;
}
class inputThread extends Thread {
private Res res;
public inputThread(Res res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (res) {
if (res.flag) {
try {
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count == 0) {
res.username = "马牛逼";
res.sex = '男';
} else {
res.username = "小微";
res.sex = '女';
}
//输出线程 输出值
res.flag = true;
//唤醒输出线程
res.notify();
}
count = (count + 1) % 2;
}
}
}
class outputThread extends Thread {
private Res res;
public outputThread(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
if (!res.flag) {
//如果 res.flag = false 则 输出的线程 主动释放锁, 同时会阻塞该线程
try {
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(res.username + "," + res.sex);
//输出完毕, 交给我们的输入线程继续输入
res.flag = false;
//唤醒输入线程
res.notify();
}
}
}
}
public void print() {
Res res = new Res();
new inputThread(res).start();
new outputThread(res).start();
}
public static void main(String[] args) {
new Test().print();
}
}
join同步
waitset集合: 调用wait方法后的线程会放到该集合中
blockedThreads: 存放我们阻塞, 没有获取到锁的线程, 它的集合名称叫做entrylist
好比四个线程去抢一个锁的时候,第一个线程抢到了,其他三个线程通过内部各种方式都没有获取到的话,就会被放到blockedThreads中,如果线程一,一旦释放完锁(调用wait或者执行结束)之后,就会唤醒正在阻塞的其他三个线程,进入到一个获取锁的状态线程中一旦调用了wait方法,意味着这个线程,主动释放cpu执行权、释放锁,当前该线程变成了阻塞,会被放在waitset集合中,当调用notify的时候,唤醒该线程,意味着该线程又需要重新进入到一个获取锁的状态,重新去竞争锁的资源,相当于调用notify的时候从waitset集合移动到blockedThreads中,一旦需要的锁被释放的时候,所有没有获取到锁,正在等待的线程都会去竞争
代码
public class Test2 {
public static void main(String[] args) {
//t1 run方法执行完毕 唤醒---t2 正在等待
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程");
}, "t1");
//t2需要等待t1执行完毕
Thread t2 = new Thread(() -> {
try {
//t1.wait(); t2调用t1(this锁).wait() 主动释放this锁 同时t2线程变为阻塞状态
t1.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "线程");
}, "t2");
Thread t3 = new Thread(() -> {
try {
t2.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "线程");
}, "t3");
t1.start();
t2.start();
t3.start();
}
}
join原理
join底层原理是基于wait封装的, 唤醒的代码在 jvm Hotspot 源码中, 当jvm在关闭线程之前会检测线程阻塞在t1线程对象上的线程, 然后执行notifyAll(), 这样t2就被唤醒了
Join/Wait与sleep之间的区别
sleep(long)方法在睡眠时不释放对象锁
join(long)方法先执行另外的一个线程, 在等待的过程中释放对象锁, 底层是基于wait()
wait(long)方法在等待的过程中释放对象锁, 需要我们在 synchronized 中使用
多线程底层七种状态
初始化状态
就绪状态
运行状态
死亡状态
阻塞状态
超时等待
等待状态新建状态(new Thread()) 调用start()-> 就绪状态 (等待cpu调度执行)-> 运行状态(执行run方法) -> 终止状态(run方法执行完毕)
运行状态(执行run方法) -> 没有获取到锁,会重新进入获取到锁的状态 -> 就绪状态
运行状态(执行run方法) -> 等待超时状态(sleep(long),wait(long),join(long)) 到时间/调用唤醒的方法就会到下一个状态 -> 就绪状态
运行状态(执行run方法) -> 等待状态(wait(),join(),Locksupport.park()) -> 调用(notify(),notifyAll(),Locksupport.unpark()) -> 就绪状态
start():调用start()方法会使得该线程开始执行,正确启动线程的方式。
wait():调用wait()方法,进入等待状态,释放资源,让出CPU。需要在同步快中调用。
sleep():调用sleep()方法,进入超时等待,不释放资源,让出CPU
stop():调用sleep()方法,线程停止,线程不安全,不释放锁导致死锁,过时。
join():调用sleep()方法,线程是同步,它可以使得线程之间的并行执行变为串行执行。
yield():暂停当前正在执行的线程对象,并执行其他线程,让出CPU资源可能立刻获得资源执行, yield()的目的是让相同优先级的线程之间能适当的轮转执行
notify():在锁池随机唤醒一个线程。需要在同步块中调用。
notifyAll():唤醒锁池里所有的线程。需要在同步块中调用。
Sleep 主动释放cpu执行权 休眠一段时间
运行状态→限时等待状态
限时等待状态→就绪状态→运行状态Synchronized 没有获取到锁 当前线程变为阻塞状态
如果有线程释放了锁,唤醒正在阻塞没有获取到锁的线程wait() 运行---等待状态
notify() 等待状态--阻塞状态(没有获取到锁的线程 队列)
---就绪状态→运行状态
sleep()方法可以防止CPU飙高
守护线程和用户线程
java中线程分为两种类型: 用户线程和守护线程
通过Thread.setDaemon(false)设置为用户线程
通过Thread.setDaemon(true)设置为守护线程
如果不设置次属性, 默认为用户线程1.守护线程是依赖于用户线程, 用户线程退出了, 守护线程也就会退出, 典型的守护线程如垃圾回收线程
2.用户线程是独立存在的, 不会因为其他用户线程退出而退出
终止线程
stop()和interrupt()
Stop: 中止线程, 并且清楚监控器锁的信息, 但是可能导致线程安全问题, jdk不建议用
Destroy: JDK未实现该方法Interrupt(线程中止):
1.如果目标线程在调用Object class 的wait()、wait(long)或wait(long,int)方法、join()、join(long,int)或sleep(long,int)方法时被阻塞, 那么Interrupt会生效, 该线程的中断状态将会被清楚, 抛出InterruptedException异常
2.如果目标线程是被I/O或者NIO中的Channel所阻塞, 同样, I/O操作会被中断或者返回特殊异常值, 达到终止线程的目的
如果以上条件都不满足, 则会设置此线程的中断状态(标志位改为true)
代码
public class TestThread extends Thread {
@Override
public void run() {
while (true) {
try {
//interrupt()外部有人调用该方法,会变成true, 跳出循环, 结束线程
//标志位, 相当于一个标志, 也可以自己定义一个变量
if (this.isInterrupted()) break;
// System.out.println("1");
// Thread.sleep(1000000000);
// System.out.println("2");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
TestThread thread = new TestThread();
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
// thread.stop(); //这样可以终止 thread 线程, 线程直接从运行状态进入死亡状态, 但是不建议
System.out.println(">>中断<<");
//中断 阻塞或者正在运行的线程
//如果该线程正在阻塞状态, 会抛出异常, 属于正常现象
//该方法不会让线程停止, 只是改变该线程中的 isInterrupted 值
thread.interrupt(); // isInterrupted 这个值默认为false, 调用该方法会变成true
}
}
Lock锁
概述
在jdk1.5后新增的ReentrantLock类同样可达到此效果, 且在使用上比synchronized更加灵活
相关Api:
使用ReentrantLock实现同步
lock()方法:上锁
unlock()方法:释放锁
使用Condition实现等待/通知 类似于wait()和notify()及notifyAll()
Lock锁底层基于AQS实现, 需要自己封装实现自旋锁Synchronized --属于 JDK 关键字, 底层数据 C++虚拟机底层实现
Lock锁底层基于AQS实现 如果没有获取到锁 -- 变为重量级锁(直接就变为阻塞状态)
Synchronized 底层原理jdk6开始做了优化 -- 锁的升级过程(如果没有获取到锁, 会重试,重新获取锁,如果当前线程重试多次还获取不到锁的话, 才会变为重量级锁)
Lock锁 过程中, 获取了锁, 代码中就要释放锁Lock锁: 重量级锁, 手动挡, 获取锁和释放锁都要自己定义
Synchronized锁: 比较轻量级, 自动挡, 获取和释放锁不需要管, 有个优化过程new ConcurrentHashMap(); 1.7版本使用Lock锁保证线程安全问题, 1.8使用Synchronized
lock锁使用方式
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test5 {
private Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Test5 test = new Test5();
test.print1();
Thread.sleep(500);
test.print2();
}
public void print1() {
/**
* Lock 获取锁 和释放锁, 需要开发人员自己定义
*/
new Thread(() -> {
try {
lock.lock(); //获取锁
System.out.println(Thread.currentThread().getName() + "获取锁成功");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁, 不释放t2线程一直获取不到
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}, "t1").start();
}
public void print2() {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + ". 1");
lock.lock(); //获取锁
System.out.println(Thread.currentThread().getName() + ". 2");
} catch (Exception e) {
e.printStackTrace();
}
}, "t2").start();
}
}
condition用法
相当于synchronized的wait()、notify()
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test6 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Test6 test6 = new Test6();
test6.cal();
Thread.sleep(3000);
test6.signal();
}
public void cal() {
new Thread(() -> {
try {
/**
* await()主动释放锁, 同时当前线程变为阻塞状态
*/
lock.lock();//获取锁
System.out.println("1");
condition.await();
System.out.println("2");
} catch (Exception e) {
} finally {
lock.unlock(); //释放锁
}
}).start();
}
public void signal() {
lock.lock();
//相当于 this.notify(); 唤醒正在阻塞的线程
condition.signal();
lock.unlock();
}
}
多线程的yield()方法
1.多线程yield会让线程从运行状态进入到就绪状态, 然后调度执行其他线程
2.具体的实现依赖于底层操作系统的任务调度器
public class Test7 extends Thread {
public Test7(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i == 30) {
System.out.println(Thread.currentThread().getName() + ", 释放cpu执行权");
this.yield();
}
System.out.println(Thread.currentThread().getName() + ", " + i);
}
}
public static void main(String[] args) {
new Test7("t1").start();
new Test7("t2").start();
}
}
多线程优先级
1.在java语言中, 每个线程都有一个优先级, 当线程调控器有机会选择新的线程时, 线程的优先级越高越有可能先被选择执行, 线程的优先级可以设置1-10, 数字越大代表优先级越高
注意: Oracle为Linux提供的java虚拟机中, 线程的优先级将被忽略, 即所有线程具有相同的优先级
所以, 不要过度依赖优先级
2.线程的优先级用数字来表示, 默认范围是1到10, 即Thread.MIN_PRIORITY到Thread.MAX_PRIORTY, 一个线程的默认优先级是5, 即Thread.NORM_PRIORTY
3.如果cpu非常繁忙时, 优先级越高的线程获得更多的时间片, 但是cpu空闲时, 设置优先级几乎没有任何作用
public class Test8 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int count = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + ", " + count++);
}
}, "t1");
Thread t2 = new Thread(() -> {
int count = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + ", " + count++);
}
}, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
wait()为什么在Object中
因为wait()方法需要结合 synchronized(对象锁) 使用, 而对象锁可以是任意对象, 只有放在Object父类里, 才可以在任意对象锁中拿到wait和notify
纯手写callable与FutureTask
自定义Callable接口
public interface MyCallable<V> {
V call() throws Exception;
}
写Callable实现类
public class MyCallableImpl<V> implements MyCallable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + ", 子线程在执行耗时的代码");
Thread.sleep(3000);
//耗时代码执行完毕之后, 返回1
return 1;
}
}
自定义FutureTask
import java.util.concurrent.locks.LockSupport;
public class MyFutureTask<V> implements Runnable {
private MyCallable<V> myCallable;
private Object lock = new Object();
private V result;
private Thread cuThread;
public MyFutureTask(MyCallable<V> myCallable) {
this.myCallable = myCallable;
}
@Override
public void run() {
try {
//线程需要执行代码
result = myCallable.call();
} catch (Exception e) {
e.printStackTrace();
}
//如果子线程执行完毕 唤醒主线程 可以拿到返回结果
// synchronized (lock) {
// lock.notify();
// }
if (cuThread != null)
//唤醒 cuThread
LockSupport.unpark(cuThread);
}
public V get() {
//获取子线程异步执行完毕之后的返回结果
//主线程阻塞
// synchronized (lock) {
// try {
// lock.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
//主线程阻塞
cuThread = Thread.currentThread();
LockSupport.park();
return result;
}
}
测试
public class Test {
public static void main(String[] args) {
MyCallable<Integer> myCallable = new MyCallableImpl<>();
MyFutureTask<Integer> myFutureTask = new MyFutureTask<>(myCallable);
new Thread(myFutureTask).start();
Integer i = myFutureTask.get();
System.out.println(i);
}
}
纯手写日志框架设计原理
@Slf4j中
log.info() -> 缓存(队列) -> 将日志写入到磁盘中(单独线程从缓存队列中获取日志, 然后将日志写入磁盘中)
多线程操作
日志类
import com.mayikt.service.utils.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
@Component
public class LogManage {
/**
* 缓存日志内容
*/
private BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<String>();
private LogThread logThread;
private String filePath = "D:\\log\\my.log";
public LogManage() {
logThread = new LogThread();
logThread.start();
}
public void info(String log) {
// 将日志缓存起来
blockingDeque.offer(log);
}
class LogThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (Exception e) {
}
// 缓冲池 批量写入到磁盘中
String log = blockingDeque.poll();
if (StringUtils.isEmpty(log)) {
continue;
}
// 需要继续获取消息
StringBuilder sbLog = new StringBuilder(log);
for (int i = 0; i < 9; i++) {
String logTemp = blockingDeque.poll();
if (StringUtils.isEmpty(logTemp)) {
break;
}
sbLog.append(logTemp);
}
String logAppend = sbLog.toString();
if (!StringUtils.isEmpty(logAppend)) {
// 在将该日志写入到磁盘中
FileUtils.writeText(filePath, logAppend, true);
}
}
}
}
}
AOP切面
import com.alibaba.fastjson.JSON;
import com.my.service.manage.LogManage;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
@Aspect
@Component
@Slf4j
public class AopLog {
private static final String START_TIME = "request-start";
private SimpleDateFormat sdf4 = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
@Autowired
private LogManage logManage;
/**
* 切入点
*/
@Pointcut("execution(public * com.my.service.*Service.*(..))")
public void log() {
}
/**
* 前置操作
*
* @param point 切入点
*/
@Before("log()")
public void beforeLog(JoinPoint point) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
logManage.info("【请求 时间】:" + sdf4.format(new Date()));
logManage.info("【请求 URL】:" + request.getRequestURL());
logManage.info("【请求 IP】:" + request.getRemoteAddr());
logManage.info("【类名 Class】:" + point.getSignature().getDeclaringTypeName());
logManage.info("【方法名 Method】:" + point.getSignature().getName());
logManage.info("【请求参数 Args】:" +
JSON.toJSONString(point.getArgs())
);
// log.info("【请求 时间】:{}", System.currentTimeMillis());
// log.info("【请求 URL】:{}", request.getRequestURL());
// log.info("【请求 IP】:{}", request.getRemoteAddr());
// log.info("【类名 Class】:{},【方法名 Method】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName());
// log.info("【请求参数 Args】:{},", point.getArgs());
}
}