明天你会感谢今天奋力拼搏的你。
ヾ(o◕∀◕)ノヾ
Java中6个状态定义:java.lang.Thread.State
Java里启动线程的方式归根结底只有一种:new Thread().start()
Runnable、Callablle->FutureTask只是Thread要执行的逻辑。
Runnable就没什么好说的了,基本上大家都会用,这里主要说说Callable.
Callable接口和Runnable接口相似,区别就是Callable需要实现call方法,而Runnable需要实现run方法;
Thread执行只支持Runnable,要用到Callable该怎么办?这里就要提到Java中的Executors工具类。
Callable和Runnable都可以通过Executors工具类执行,Executors工具类的详解后面再说,这里只说说对Callable的用法。
Callable可以通过标准的线程池执行类ThreadPoolExecutor调用,具体代码如下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建单一线程的执行器
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 执行Callable
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("执行线程逻辑>>>>");
Thread.sleep(2000);
return "执行完成";
}
});
// 获得Callable返回结果,Callable没执行完会阻塞在此
System.out.printf("执行结果:" + future.get());
executorService.shutdown(); // 最后关闭执行器
}
不安全方式
interrupt方法
正确的使用interrupt
public static void main(String[] args) throws InterruptedException {
Thread testThread = new Thread(){
@Override
public void run() {
//中断标志为false就进入执行
while (!Thread.currentThread().isInterrupted()){
try {
System.out.println("runing...");
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
//如果有代码中有sleep、wait、join
// 记得在InterruptedException中重新设置中断标志
//以达到让while循环关闭的目的
Thread.currentThread().interrupt();
}
}
}
};
testThread.start();
Thread.sleep(1000);
testThread.interrupt();
}
守护线程:是指在程序运行的时候在后台提供一种通用服务的线程,进程结束时,会杀死所有守护线程。
用户线程:非守护线程就是用户线程
进程结束:没有非守护线程还在运行时,进程结束
注意:
ThreadGroup的提出是为了方便线程的管理,通过它可以批量设定一组线程的属性,比如setDaemon,设置未处理异常的处理方法,设置统一的安全策略等等;也可以通过线程组方便的获得线程的一些信息,但因为ThreadGroup这个类本身不是线程安全的,所以在多线程环境下使用ThreadGroup实例,要注意线程安全。尽量不要使用,会带来线程安全问题。
主线程里添加了线程或者线程组一般和主线程在同一个组,即main group下。通过debug模式,也可以查看到线程组
多线程访问共享可变数据时,涉及到线程间数据同步的问题。
并不是所有时候都要用到共享数据,若数据都被封闭在各自的线程红,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭。
ThradLocal是一个线程级别的变量,每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
用法:
ThreadLocal<T> var = new ThreadLocal<T>();
会自动在每个线程上创建一个T的副本,副本之间彼此独立,互不影响。
可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法。
扩展:栈封闭
局部变量的固有属性之一就是封闭在线程中,因为他们都位于执行线程的栈中,其他线程无法访问这个栈。
JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。例如:wait/notify、park/unpark和被弃用的suspend和resume。
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
notify() 唤醒一个线程(谨慎使用),具体唤醒哪个线程,由CPU决定。
注意:
扩展:sleep和wait的区别
线程调用park则等待“许可”,unpark方法为指定线程提供“许可(permit)”
调用unpark之后再调用park线程会直接运行。
提前调用unpark不叠加,连续多次调用unpark后,第一次调用park后会拿到“许可”直接运行,后续调用会进入等待。
代码示例:
public static void main(String[] args) throws InterruptedException {
// 开启一个线程
Thread consumerThread = new Thread(() -> {
System.out.println("等待许可...");
LockSupport.park();
System.out.println("结束");
});
consumerThread.start();
Thread.sleep(2000L);
LockSupport.unpark(consumerThread);
System.out.println("拿到许可");
}
执行结果:
等待许可...
拿到许可
结束
在同步代码块中使用,容易出现死锁,因为park不会释放锁,代码示例:
public void test_DeadLock() throws InterruptedException {
Thread consumerThread = new Thread(() -> {
System.out.println("线程内的锁:" + this);
System.out.println("等待许可...");
synchronized (this) {
LockSupport.park();
}
System.out.println("结束");
});
consumerThread.start();
Thread.sleep(2000L);
System.out.println("主线程的锁:" + this);
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("拿到许可");
}
执行结果:死锁
扩展:
Java匿名内部类如果用new生成的内部再从中获得this和通过Lambda表达式中获得this,两种方式取得的对象是不一样的。
匿名类中的this是匿名对象本身,Lambda表达式中的this是调用Lambda表达式的对象,所以上面代码中两个this打印的都是外部类的地址。
如果把上面代码改成匿名内部类new的方式生成,代码示例如下:
public void test2_DeadLock() throws InterruptedException {
Thread consumerThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程内的锁:" + this);
System.out.println("等待许可...");
synchronized (this) {
LockSupport.park();
}
System.out.println("结束");
}
});
consumerThread.start();
Thread.sleep(2000L);
System.out.println("主线程的锁:" + this);
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("拿到许可");
}
执行结果:程序正常执行完成,因为两边获得的锁不一样,相当于没有锁住。
Thread.yield()是 Java 中的一个方法,用于提示线程调度器当前线程愿意放弃当前的 CPU 使用权,允许相同优先级的其他线程获得执行的机会。
作用:
提示调度器:当前线程通知线程调度器,它愿意让出自己的 CPU 时间片,即使它仍有剩余的执行时间。
提高线程调度的公平性:可以提高线程调度的公平性,避免某个线程长时间占用 CPU,导致其他线程饥饿。
协作式线程调度:线程之间需要协作的某些情况,可以用来实现线程之间的协作调度。
注意事项:
不保证一定会让出 CPU: yield方法并不保证线程一定会放弃 CPU,线程调度器可能会忽略这个提示,当前线程可能仍然继续执行。
不改变线程优先级:不会影响线程的优先级,它只是向线程调度器发出一个提示。
使用场景有限:在现代操作系统中,线程调度通常是由操作系统内核管理的,因此yield()的使用场景相对有限。在大多数情况下,线程调度器已经能够很好地管理线程的执行。
可能影响性能:频繁地调用此方法可能会导致性能下降,因为它会增加线程上下文切换的开销。
调用方式:Thread.currentThread().suspend();Thread.currentThread().resume();
作用:调用suspend挂起目标线程,通过resume可以恢复线程执行。
被弃用原因:
注意:代码中用 if 语句来判断,是否进入等待状态,这样的做法是错误的!
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
伪唤醒是指线程并非因为notify、notifyall、unpark等api调用而意外唤醒,是更底层原因导致的。
全部评论