文章目录
多线程知识高级部分
多线程七种执行的状态
初始化状态
就绪状态
运行状态
死亡状态
阻塞状态
超时等待
等待状态
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)**设置为守护线程。如果不设置此属性,默认为用户线程。
-
守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
-
用户线程是独立存在的,不会因为其他用户线程退出而退出。
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执行权
-
多线程yield 会让线程从运行状态进入到就绪状态,让后调度执行其他线程。
-
具体的实现依赖于底层操作系统的任务调度器
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();
}
}
多线程优先级
-
在java语言中,每个线程都有一个优先级,当线程调控器有机会选择新的线程时,线程的优先级越高越有可能先被选择执行,线程的优先级可以设置1-10,数字越大代表优先级越高
-
线程的优先级用数字来表示,默认范围是1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORTY一个线程的默认优先级是5,即Thread.NORM_PRIORTY
-
如果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 打断正在运行或者正在阻塞的线程。
-
如果目标线程在调用Object class的wait()、wait(long)或wait(long, int)方法、join()、join(long, int)或sleep(long, int)方法时被阻塞,那么Interrupt会生效,该线程的中断状态将被清除,抛出InterruptedException异常。
-
如果目标线程是被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实践全解析》
《多线程技术(中):多线程编程:线程安全、同步与高效通信策略》