join 底层原理
package thread;
public class ThreadJoin {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "running.."), "t1");
Thread t2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "running.."), "t2");
Thread t3 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "running.."), "t3");
t1.start();
t2.start();
t3.start();
}
}
思考上面代码执行结果是多少?
有序,还是乱序?
t1running..
t3running..
t2running..
因为执行顺序是靠cpu调度的,所以结果不确定性。
那么如何顺序执行呢
join() 方法的作用,就是等待这个线程的结束,谁调用它,阻塞谁。
package thread;
public class ThreadJoin {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " running..");
}, "t1");
// t2 等t1
Thread t2 = new Thread(() -> {
// 等待t1执行完毕
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " running..");
}, "t2");
// t3 等t2
Thread t3 = new Thread(() -> {
// 等待t2执行完毕
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " running..");
}, "t3");
t1.start();
t2.start();
t3.start();
}
}
执行结果
t1 running..
t2 running..
t3 running..
那么这个join如何做到的?
我查看其java实现
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
通过简单分析后我们不难发现,join底层是wait
实现的。
那么上面就解释通了:
join 底层原理是基于wait封装的,唤醒代码在 jvm中。
当jvm在关闭线程前会检测阻塞线程,然后执行notifyAll
那么如何知道notify不是java实现的呢?
阅读jvm源码
void Threads::remove(JavaThread* p, bool is_daemon) {
// Extra scope needed for Thread_lock, so we can check
// that we do not remove thread without safepoint code notice
{ MonitorLocker ml(Threads_lock);
// BarrierSet state must be destroyed after the last thread transition
// before the thread terminates. Thread transitions result in calls to
// StackWatermarkSet::on_safepoint(), which performs GC processing,
// requiring the GC state to be alive.
BarrierSet::barrier_set()->on_thread_detach(p);
assert(ThreadsSMRSupport::get_java_thread_list()->includes(p), "p must be present");
// Maintain fast thread list
ThreadsSMRSupport::remove_thread(p);
_number_of_threads--;
if (!is_daemon) {
_number_of_non_daemon_threads--;
// Only one thread left, do a notify on the Threads_lock so a thread waiting
// on destroy_vm will wake up.
if (number_of_non_daemon_threads() == 1) {
ml.notify_all();
}
}
ThreadService::remove_thread(p, is_daemon);
// Make sure that safepoint code disregard this thread. This is needed since
// the thread might mess around with locks after this point. This can cause it
// to do callbacks into the safepoint code. However, the safepoint code is not aware
// of this thread since it is removed from the queue.
p->set_terminated(JavaThread::_thread_terminated);
// Notify threads waiting in EscapeBarriers
EscapeBarrier::thread_removed(p);
} // unlock Threads_lock
// Reduce the ObjectMonitor ceiling for the exiting thread.
ObjectSynchronizer::dec_in_use_list_ceiling();
// Since Events::log uses a lock, we grab it outside the Threads_lock
Events::log(p, "Thread exited: " INTPTR_FORMAT, p2i(p));
}
// NOTE: see comment of notify()
void ObjectSynchronizer::notifyall(Handle obj, TRAPS) {
JavaThread* current = THREAD;
markWord mark = obj->mark();
if (mark.has_locker() && current->is_lock_owned((address)mark.locker())) {
// Not inflated so there can't be any waiters to notify.
return;
}
// The ObjectMonitor* can't be async deflated until ownership is
// dropped by the calling thread.
ObjectMonitor* monitor = inflate(current, obj(), inflate_cause_notify);
monitor->notifyAll(CHECK);
}
验证猜想
package thread;
public class ThreadInterrupt {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
synchronized (object) {
System.out.println("1");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2");
}
});
thread.start();
Thread.sleep(3_000);
thread.interrupt();
}
}
1
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at thread.ThreadInterrupt.lambda$main$0(ThreadInterrupt.java:10)
at java.lang.Thread.run(Thread.java:748)
2
Process finished with exit code 0
当执行interrupt
时线程被打断,执行了jvm的Threads::remove
方法,当线程关闭时,jvm会主动执行notifyAll
来保证事情在可控范围之内。
线程的七种状态
话不多说看jvm
// Java Thread Status for JVMTI and M&M use.
// This thread status info is saved in threadStatus field of
// java.lang.Thread java class.
enum class JavaThreadStatus : int {
NEW = 0,
RUNNABLE = JVMTI_THREAD_STATE_ALIVE + // runnable / running
JVMTI_THREAD_STATE_RUNNABLE,
SLEEPING = JVMTI_THREAD_STATE_ALIVE + // Thread.sleep()
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
JVMTI_THREAD_STATE_SLEEPING,
IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE + // Object.wait()
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
IN_OBJECT_WAIT_TIMED = JVMTI_THREAD_STATE_ALIVE + // Object.wait(long)
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
PARKED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park()
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
JVMTI_THREAD_STATE_PARKED,
PARKED_TIMED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park(long)
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
JVMTI_THREAD_STATE_PARKED,
BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE + // (re-)entering a synchronization block
JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,
TERMINATED = JVMTI_THREAD_STATE_TERMINATED
};
#endif // SHARE_CLASSFILE_JAVATHREADSTATUS_HPP
从上面源码可以看线程一共有七种状态
- 新生态:
创建线程
- 就绪态:
执行start方法
- 运行态:
线程开始执行
- 阻塞态:
没有获得锁
- 等待:
等待线程
- 等待超时:
等待线程超时
- 消亡:
线程正常/异常 结束
- 没有获得锁的线程会等待
- 持有锁的线程释放锁时,会唤醒正在阻塞的线程
wait
会主动释放锁,线程进入阻塞状态。- 使用
LockSupport
也可以让线程进入唤醒/阻塞状态。- yeild 主动放弃cpu调度执行权,线程进入就绪态。
如何防止sleep导致cpu飙升?
因为线程运行在cpu之上
package thread;
public class ThreadSleep {
public static void main(String[] args) {
// 当前电脑 6核 开启 8个线程
for (int i = 0; i < 8; i++) {
new Thread(() -> {
while (true) {
}
}).start();
}
}
}
然后我们通过jconsole查看cpu占用
可以看到cpu占用还是挺高的。
如何解决?
添加一个sleep语句试试
package thread;
public class ThreadSleep {
public static void main(String[] args) {
// 当前电脑 6核 开启 8个线程
for (int i = 0; i < 8; i++) {
new Thread(() -> {
while (true) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
继续通过jconsole查看cpu占用
差别还是蛮大的
为什么会出现如此大的差距呢?
当我们加sleep方法会减缓cpu的调度压力,所以在日常生活中尽量少写死循环代码,如要非要写,那么记得添加sleep方法。
用户线程和守护线程
在java中有两种线程:分别是用户线程
和守护线程
。
守护线程依赖于,用户线程,用户线程退出了守护线程也会退出(燕子 燕子 没你我怎么活啊
),最典型的守护线程:垃圾回收线程.
用户线程是独立存在的,不会因为其他用户线程退出而退出。
package thread;
public class ThreadDaemon {
public static void main(String[] args) {
new Thread(()->{while (true){}},"sub thread").start();
System.out.println("main thread run over");
}
}
main thread run over
问题:为什么主线程结束了子线程还在运行?
答:因为子线程是用户线程,不会被其他线程退出影响
如果我要修改代码逻辑为:主线程运行完毕子线程随之结束,如何做?
子线程修改为守护线程
subThread.setDaemon(true);
package thread;
public class ThreadDaemon {
public static void main(String[] args) {
Thread subThread = new Thread(() -> {
while (true) {
}
}, "sub thread");
subThread.setDaemon(true);
subThread.start();
System.out.println("main thread run over");
}
}
Thread.currentThread().setDaemon(true);
也可以
这样在主线程结束时子线程也结束了。
如何安全停止一个线程
stop? stop会清除线程监控器锁的信息,会导致线程安全问题。不建议采用
interrupt? interrupt会打断正在运行/阻塞的线程。如果目标线程在阻塞状态,那么 interrupt生效该线程的中断状态呗清除,抛出InterruptException
异常。 不建议采用
正确方法:设置一个线程标志位然后通过标志位来进行判断。
package thread;
public class ThreadStop extends Thread{
private boolean alive = true;
public void exit(){
this.alive=false;
}
@Override
public void run() {
while (alive){
if (this.isInterrupted()){
break;
}
}
System.out.println("over");
}
public static void main(String[] args) throws InterruptedException {
ThreadStop thread = new ThreadStop();
thread.start();
Thread.sleep(3_000);
thread.exit();
}
}
over
Lock锁
比 synchronized 更灵活
区别
- synchronized锁基于jvm实现 推荐
- lock 基于AQS实现 重量级
用法
lock锁的
获取
和释放
是需要开发人员自己决定
用之前先看看有啥
Lock.java
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock
获取锁
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
count();
}, "thread1").start();
Thread.sleep(1_000);
new Thread(() -> {
count();
}, "thread2").start();
}
public static void count() {
System.out.println(Thread.currentThread().getName() + " try lock");
// 上锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " locked success");
}
}
thread1 try lock
thread1 locked success
thread2 try lock
我们可以看到 thread1 获取锁后,thread2一直在尝试获取锁。
话说 thread1 不能总是占用公共资源吧,那如何释放呢? unlock
unlock
释放锁
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
count();
try {
Thread.sleep(2_000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// thread1 释放锁
lock.unlock();
}
}, "thread1").start();
Thread.sleep(1_000);
new Thread(() -> {
count();
}, "thread2").start();
}
public static void count() {
System.out.println(Thread.currentThread().getName() + " try lock");
// 上锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " locked success");
}
}
thread1 try lock
thread1 locked success
thread2 try lock
thread2 locked success
当thread1释放掉锁后 thread2 立马拿到了锁
设计思想: 俗话说得好:
好借好还,再借不难
既然你要获取锁,那么一般情况下你要考虑释放锁
,年轻人要讲5的
这里 tryLock 两个方法就不讲了 根据字面意思也可以理解,尝试获取锁
,另一个方法就是有个等待超时,一旦超时则抛出异常。
我们知道 用 synchronized 锁 线程之间的 通信 用 wait
和 nofity
来解决,那么lock锁如何在线程之间通信呢? condition
condition
- await 进入阻塞状态等待唤醒
- signal 唤醒进入阻塞状态的线程
需要注意的是这两个方法使用的前提是在拿到锁后使用哦。
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLockCondition {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static Thread build(){
return new Thread(() -> {
try {
// 释放锁区前先上锁
System.out.println(Thread.currentThread().getName()+" try lock");
lock.lock();
// 释放锁 && 当前线程进入阻塞状态
condition.await(); // 我睡一会 一会叫醒我
System.out.println(Thread.currentThread().getName()+" waiting");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+" unlocked");
}
});
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = build();
thread1.start();
Thread.sleep(2_000);
// 唤醒 阻塞线程
lock.lock();
condition.signal(); // 醒醒快醒醒
lock.unlock();
}
}
线程礼让 yield
多线程 yield 会让线程从运行状态进入就绪状态,礼让后调度执行其他线程。
package thread;
public class ThreadYield extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" release cpu schedule");
// 开始礼让
this.yield();
}
}
public static void main(String[] args) {
new ThreadYield().start();
new ThreadYield().start();
}
}
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
可以看到线程1和线程2互相让,儒学思想多好
线程优先级
线程优先级决定了cpu调度的优先级,优先级越高,cpu调度越频繁。
thread1.setPriority(Thread.MAX_PRIORITY);
package thread;
public class ThreadYield extends Thread {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " release cpu schedule");
// 开始礼让
this.yield();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadYield thread1 = new ThreadYield();
ThreadYield thread2 = new ThreadYield();
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.start();
}
}
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
Thread-1 release cpu schedule
Thread-0 release cpu schedule
可以看到线程1明显调度次数比次线程2频繁,因为线程1优先级比线程2高
wait和sleep区别
共同点:
都可以将线程从运行状态变为就绪状态
不同点:
- wait 是会释放当前对象锁的,前提是要拿到对象锁
- sleep 不会释放当前对象锁