多线程技术(下):Java多线程高手进阶:从线程状态到异步日志框架的深度剖析

多线程知识高级部分

多线程七种执行的状态

初始化状态

就绪状态

运行状态

死亡状态

阻塞状态

超时等待

等待状态

image-20240602170127766

img

start():调用start()方法会使得该线程开始执行,正确启动线程的方式。

wait():调用wait()方法,进入等待状态,释放资源,让出CPU。需要在同步快中调用。

sleep():调用sleep()方法,进入超时等待,不释放资源,让出CPU。

stop():调用sleep()方法,线程停止,线程不安全,不释放锁导致死锁,过时。

join():调用sleep()方法,线程是同步,它可以使得线程之间的并行执行变为串行执行。

yield():暂停当前正在执行的线程对象,并执行其他线程,让出CPU资源可能立刻获得资源执行。

yield()的目的是让相同优先级的线程之间能适当的轮转执行

notify():在锁池随机唤醒一个线程。需要在同步快中调用。

nnotifyAll():唤醒锁池里所有的线程。需要在同步快中调用。

Sleep 主动释放cpu执行权,休眠一段时间

运行状态→限时等待状态

限时等待状态→就绪状态→运行状态

Synchronized 没有获取到锁,当前线程变为阻塞状态,如果有线程释放了锁,唤醒正在阻塞没有获取到锁的线程从新进入到获取锁的状态

wait() 运行—等待状态

notify() 等待状态–阻塞状态(没有获取到锁的线程 队列)

—就绪状态→运行状态

守护线程与用户线程

java中线程分为两种类型:用户线程和守护线程。通过**Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)**设置为守护线程。如果不设置此属性,默认为用户线程

  1. 守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。

  2. 用户线程是独立存在的,不会因为其他用户线程退出而退出。

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ",我是子线程");
                } catch (Exception e) {

                }
            }
        });
        //thread 设置为守护线程 
        thread.setDaemon(true);
        thread.start();
        System.out.println("我是主线程,代码执行结束");
    }
}

多线程yield

主动释放cpu执行权

  1. 多线程yield 会让线程从运行状态进入到就绪状态,让后调度执行其他线程。

  2. 具体的实现依赖于底层操作系统的任务调度器

public class ThreaYield extends Thread {
    public ThreaYield(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 ThreaYield("01").start();
        new ThreaYield("02").start();
    }
}

多线程优先级

  1. 在java语言中,每个线程都有一个优先级,当线程调控器有机会选择新的线程时,线程的优先级越高越有可能先被选择执行,线程的优先级可以设置1-10,数字越大代表优先级越高

  2. 线程的优先级用数字来表示,默认范围是1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORTY一个线程的默认优先级是5,即Thread.NORM_PRIORTY

  3. 如果cpu非常繁忙时,优先级越高的线程获得更多的时间片,但是cpu空闲时,设置优先级几乎没有任何作用。

**注意:**Oracle为Linux提供的java虚拟机中,线程的优先级将被忽略,即所有线程具有相同的优先级。

所以,不要过度依赖优先级。

public class ThreaDemo04 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
                System.out.println(Thread.currentThread().getName() + "," + count++);
            }
        }, "t1线程:");
        Thread t2 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
                System.out.println(Thread.currentThread().getName() + "," + count++);
            }
        }, "t2线程:");
        //设置优先级最低
        t1.setPriority(Thread.MIN_PRIORITY);
        //设置优先级最高
        t1.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

sleep防止CPU占用100%

sleep(long millis) 线程睡眠 millis 毫秒

sleep(long millis, int nanos) 线程睡眠 millis 毫秒 + nanos 纳秒

使用sleep方法避免cpu空转,防止cpu占用100%

public static void main(String[] args) {
    new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

如何安全的停止一个线程

调用stop方法(过时)

Stop:中止线程,并且清除监控器锁的信息,但是可能导致 线程安全问题,JDK不建议用。

Destroy: JDK未实现该方法。

Interrupt(线程中止)

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操作会被中断或者返回特殊异常值。达到终止线程的目的。

如果以上条件都不满足,则要设置此线程的中断状态。

打断正在阻塞的线程

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
              e.printStackTrace();
        }
    }, "t1");
    t1.start();
    try {
        Thread.sleep(1000);
    } catch (Exception e) {

    }
    System.out.println("打断子线程");
    //调用interrupt 打断正在阻塞的线程
    t1.interrupt();
    System.out.println("获取打断标记:" + t1.isInterrupted());
}

打断正在运行的线程

public class ThreaDemo02 extends Thread {
    @Override
    public void run() {
        while (true) {
            // 如果终止了线程,则停止当前线程
            if (this.isInterrupted()) {
                break;
            }
        }
    }

    public static void main(String[] args) {
        ThreaDemo02 threaDemo02 = new ThreaDemo02();
        threaDemo02.start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
        }
        threaDemo02.interrupt();
    }
}

正确的线程中止-标志位

public class ThreaDemo03 extends Thread {
    private volatile boolean isFlag = true;

    @Override
    public void run() {
        while (isFlag) {

        }
    }

    public static void main(String[] args) {
        ThreaDemo03 threaDemo03 = new ThreaDemo03();
        threaDemo03.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        threaDemo03.isFlag = false;
    }
}

Lock锁的基本使用

在jdk1.5后新增的ReentrantLock类同样可达到此效果,且在使用上比synchronized更灵活

相关API:

使用ReentrantLock实现同步

lock()方法:上锁

unlock()方法:释放锁

使用Condition实现等待/通知 类似于 wait()和notify()及notifyAll()

Lock锁底层基于AQS实现,需要自己封装实现自旋锁。

Synchronized —属于JDK 关键字,底层是属于 C++ 虚拟机底层实现

Lock锁底层基于AQS实现-- 变为重量级

Synchronized 底层原理—有锁的升级过程

在使用Lock 过程中注意,获取锁用完之后记得释放锁

ReentrantLock用法

private Lock lock = new ReentrantLock();
lock.lock();//获取锁
lock.unlock(); //释放锁           
public class ThreaLock implements Runnable {
    private Lock lock = new ReentrantLock();
    private int count = 100;

    @Override
    public void run() {
        while (count > 1) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            cal();
        }
    }

    public void cal() {
        try {
            //获取锁
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "<>" + count--);
        } catch (Exception e) {
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ThreaLock threaLock1 = new ThreaLock();
        new Thread(threaLock1).start();
        new Thread(threaLock1).start();
    }
}

Condition用法

必须在获取锁的情况下使用

private Condition condition = lock.newCondition();
//使当前线程等待,直到发出信号或中断
condition.await();
//唤醒一个等待线程
condition.signal();
public class ThreaLockCondition {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public static void main(String[] args) {
        ThreaLockCondition threaLockCondition = new ThreaLockCondition();
        try {
            threaLockCondition.print();
            Thread.sleep(3000);
            //主线程三秒后唤醒子线程
            threaLockCondition.signal();
        } catch (Exception e) {
        }
    }

    public void print() {
        new Thread(() -> {
            try {

                lock.lock();//获取锁
                System.out.println(Thread.currentThread().getName() + ",1");
                //使当前线程等待,直到发出信号或中断
                condition.await();
                System.out.println(Thread.currentThread().getName() + ",2");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//释放锁
            }
        }).start();
    }

    public void signal() {
        try {
            lock.lock();//获取锁
            //唤醒一个等待线程
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }
}

多线程综合小案例

手写Callable与FutureTask模式

可以基于Callable+FutureTask可以实现异步线程执行 带返回结果

public interface TestCallable<V> {
    V call();
}
public class TestCallableImpl implements TestCallable<Integer> {
    @Override
    public Integer call() {
        System.out.println(Thread.currentThread().getName() + ",子线程执行开始");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + ",子线程执行结束");
        return 1;
    }
}

使用wait()与notify()

public class TestFutureTask<V> implements Runnable {
    private TestCallable<V> testCallable;
    private V result;
    private Object lock = new Object();

    public TestFutureTask(TestCallable<V> testCallable) {
        this.testCallable = testCallable;
    }

    /**
     * 调用get方法 当前线程就会阻塞。
     */
    public V get() {
        synchronized (lock) {
            try {
                System.out.println(Thread.currentThread().getName() + "<主线程阻塞等待>");
                lock.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    @Override
    public void run() {
        result = testCallable.call();
        // 如果 call方法执行完毕 则唤醒当前阻塞的线程
        if (result != null) {
            synchronized (lock) {
                lock.notify();
                System.out.println(Thread.currentThread().getName() + "<主线程唤醒继续执行>");
            }
        }
    }
}

使用LockSupport

public class TestFutureTask<V> implements Runnable {
    private TestCallable<V> testCallable;
    private V result;
    private Thread testThread;

    public TestFutureTask(TestCallable<V> testCallable) {
        this.testCallable = testCallable;
    }

    /**
     * 调用get方法 当前线程就会阻塞。
     */
    public V get() {
        //获取到当前主线程的信息
        testThread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "<主线程阻塞等待>");
        LockSupport.park();
        return result;
    }

    @Override
    public void run() {
        result = testCallable.call();
        // 如果 call方法执行完毕 则唤醒当前阻塞的线程
        LockSupport.unpark(testThread);
        System.out.println(Thread.currentThread().getName() + "<主线程唤醒继续执行>");

    }
}
public class TestThread {
    public static void main(String[] args) {
        TestCallableImpl testCallableImpl = new TestCallableImpl();
        TestFutureTask<Integer> testFutureTask = new TestFutureTask<>(testCallableImpl);
        new Thread(testFutureTask).start();
        Integer result = testFutureTask.get();
        System.out.println(Thread.currentThread().getName() +",子线程返回结果为:" + result);
    }
}

手写一个异步日志框架

@Component
public class LogManage {


    private static BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>();
    private static final String filePath = "d:/log/testlog.log";

    public LogManage() {
        new LogThread().start();
    }

    public static void addLog(String msg) {
        blockingDeque.add(msg);
    }

    class LogThread extends Thread {
        @Override
        public void run() {
            while (true) {
                String log = blockingDeque.poll();
                if (!StringUtils.isEmpty(log)) {
                    // 将该log写入到磁盘中
                    FileUtils.writeText(filePath, log, true);
                }
            }
        }
    }
}

aop采集日志

@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.mayikt.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()));
    }
}

相关文章:
《多线程技术(上):多线程概念与多线程的创建方式Java实践全解析》
《多线程技术(中):多线程编程:线程安全、同步与高效通信策略》

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值