Synchronized
synchronized包含monitor enter, monitor exit 2个JVM指令(遵循happens-before原则), 执行monitor exit之前必须存在monitor enter指令.
由于每个对象与一个monitor产生关联, 线程执行monitor enter时, 就会获取monitor的lock, monitor中存在计数器, 用于记录当前lock被获取的情况, 当线程已经获取了lock, 再次进行线程重入, lock往上自增.
与之相反, 当线程执行到monitor exit时, 对应的monitor计数器就会自减
- this monitor: synchronized修饰的对象是this
public synchronized void test(){//...
}
//A实现Runnable接口
A a = new A();
new Thread(a).start();
new Thread(a).start();
- class monitor: synchronized修饰的对象是class
public synchronized static void test() {//...
}
或者
public void test() {synchronized(A.class) {//...}
}
//A实现Runnable接口
A a1 = new A();
A a2 = new A();
//此时a1 与a2 共用一个class monitor
new Thread(a1).start();
new Thread(a2).start();
Thread API
sleep, yield, wait, notify/notifyAll, interrupt, interrupted, join
- sleep
Thread.sleep(), 线程休眠, 但休眠不会释放锁资源, 线程从running => block的状态切换, 可以使用thread.interrupt中断睡眠 - yield
Thread.yield(), 当前线程放弃CPU资源(CPU资源不紧张, 将忽略), 线程从running => runnable的状态切换, 因此不能使用interrupt中断处于runnable状态的线程 - wait
线程放弃锁资源, 并进入与锁关联的waitSet集合, 状态变化: Running => Blocked, 可以设定
object.wait()等价于object.wait(0), 表示阻塞时间是永远
注: wait必须在同步代码内. waitSet依赖于锁资源, 没有在同步代码内, 抛出IllegalMonitorStateException
wait与sleep相似点:
1. 都能使线程阻塞
2. 都是可中断
3. wait属于Object, sleep属于Thread
4. wait需要在同步代码块中, sleep不需要
5. wait状态下, 将会是否锁资源, sleep不会释放锁资源
6. sleep执行一段自动退出阻塞状态, wait(long)一样.
- notify/notifyAll
notify随机唤醒waitSet集合中某一线程, notifyAll唤醒waitSet集合中所有的线程, 被唤醒的线程重新争抢锁资源, 争抢到之后, 接着刚才wait的地方往后执行
注: notify必须在同步代码内. waitSet依赖于锁资源, 没有在同步代码内, 抛出IllegalMonitorStateException - interrupt
打断当前线程阻塞状态, 可使用interrupt进行中断的方法如下:
object.wait(), object(long), Thread.sleep(long), thread.join(), Selector.wakeup()
public void interrupt() {if (this != Thread.currentThread())checkAccess();synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {//设置中断标识, interrupt0属于native方法interrupt0(); b.interrupt(this);return;}}interrupt0();}
当线程使用休眠方法进入阻塞状态后, 使用thread.interrupt设置中断标志, 随后线程被中断, 抛出InterruptedException, 线程终止运行
isInterrupt: 用于判断线程是否被中断, 从下面源码看出, 判断线程是否被中断, 不会清除中断标志
public boolean isInterrupted() {//native方法, clearInterrupted设置为falsereturn isInterrupted(false);}
思考: wait, sleep, yield被中断后会不会清除中断标志
/*** 测试* @author regotto*/
public class InterruptTest {private static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {synchronized (lock) {try {System.out.println("1.isInterrupted: " + Thread.currentThread().isInterrupted());lock.wait();} catch (InterruptedException e) {System.out.println("2.isInterrupted: " + Thread.currentThread().isInterrupted());}}});thread.start();TimeUnit.SECONDS.sleep(1);thread.interrupt();TimeUnit.SECONDS.sleep(1);System.out.println("3.isInterrupted: " + Thread.currentThread().isInterrupted());}
}
上述结果都为false, 由于isInterrupt不会清除中断标志, 因此得出结论, 中断标志是被wait清除的, 同理sleep, yield也是一样的.
- interrupted: 该方法直接看源码
public static boolean interrupted() {return currentThread().isInterrupted(true);}
从源码可以看出, 该方法就是调用isInterrupt方法, 但是, 注意传的参数是true, 意味着, 调用该方法后, 将会清除中断标志,
- join
A join B, B blocked 直到 A 变为terminal状态
join源码如下:
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, 一定记住, 调用wait时, 谁调用, 谁进入blocked, 不要搞混了
/*** 测试join,join内部使用wait,使调用join的线程进行wait,当任务线程执行完,JVM底层自动调用notify* @author regotto*/
public class JoinTest {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ": " + i);}}, "t1");t1.start();try {//调用join线程是main线程,main线程进入join后处于wait状态,直到t1线程执行完,才notify//notify的过程由JVM底层自动调用//此时的join是相对于main,main线程是最终的调用者t1.join();} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ": " + i);}}
}
测试join状态下的interrupt
/*** 测试join状态下的interrupt* @author regotto*/
public class JoinSateExecuteInterrupt {public static void main(String[] args) {Thread main = Thread.currentThread();Thread thread = new Thread(() -> {while (true) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + " interrupt");e.printStackTrace();break;}}});thread.start();//此处提前设置中断状态
// main.interrupt();thread.interrupt();try {thread.join();} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + " interrupt");e.printStackTrace();}
// thread.interrupt(); //此处调用interrupt不能使join方法进入interrupt,因为一直执行thread的run操作,main线程的此行代码永远不会执行}
}
根据Thread API实现自定义锁
需求分析: 实现synchronized的功能, 保证当线程处于阻塞状态可被中断(此处的阻塞代表线程在等待获取对象monitor), 线程在阻塞状态下可以计时, 避免线程资源浪费.
详细设计: 使用wait, notify, 标志变量设计lock, 模拟synchronized同步过程, A线程进入同步代码块, 将标志变量变为true, 此时wait有效, B进入线程进入阻塞状态, 当A线程执行完毕, 调用notifyAll, 唤醒被阻塞的线程, 在此过程中, 进入阻塞状态下的线程可被中断.
抽象接口定义:
package com.concurrent.definelock;import java.util.List;
import java.util.concurrent.TimeoutException;public interface Lock {void lock() throws InterruptedException;void lock(long mills) throws InterruptedException, TimeoutException;void unlock();/*** 用于获取处于阻塞状态的所有线程* @return list<thread>*/List<Thread> getBlockedThreads();
}
实现类:
package com.concurrent.definelock;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;public class BooleanLock implements Lock {/*** currentThread: 当前拥有锁的线程* locked: 锁是被被获得,默认false未获得,当调用lock能准确加锁* blockedList: 存储哪些线程进入阻塞状态*/private Thread currentThread;private boolean locked = false;private final List<Thread> blockedList = new ArrayList<>();/*** 第一个线程进入的时候, 标记为加锁的线程, 剩下的线程进入之后全都wait* 直到该线程unlock,剩下的线程才重新唤醒* @throws InterruptedException*/@Overridepublic void lock() throws InterruptedException {synchronized (this) {while (locked) {if (!blockedList.contains(currentThread)){blockedList.add(currentThread);}System.out.println(Thread.currentThread().getName() + ": 进入wait状态");//所有线程被唤醒, 只有拿到锁的那个线程才能接着下去执行, 剩余锁都只能处于阻塞状态//wait可中断方法, 当捕捉到Interrupt信号的时候, 抛出异常, 线程中断this.wait();System.out.println(Thread.currentThread().getName() + ": 被唤醒");}blockedList.remove(currentThread);System.out.println(Thread.currentThread().getName() + ": 删除在blockList中值, 设置locked为true");this.locked = true;this.currentThread = Thread.currentThread();}System.out.println(Thread.currentThread().getName() + ": 结束lock方法, 已出synchronize模块");}/*** 传入mills <=0 立刻加锁也可尝试抛异常, 否则经过mills再加锁* @param mills* @throws InterruptedException* @throws TimeoutException*/@Overridepublic void lock(long mills) throws InterruptedException, TimeoutException {synchronized (this) {if (0 >= mills) {this.lock();} else {long remainingMills = mills;long endMills = System.currentTimeMillis() + remainingMills;while (locked) {if (0 >= remainingMills){throw new TimeoutException();}if (!blockedList.contains(Thread.currentThread())) {blockedList.add(Thread.currentThread());}this.wait(remainingMills);remainingMills = endMills - System.currentTimeMillis();}blockedList.remove(Thread.currentThread());this.locked = true;this.currentThread = Thread.currentThread();}}}@Overridepublic void unlock() {System.out.println(Thread.currentThread().getName() + ": 进入unlock");synchronized (this) {//不加此步操作,任何线程都将可以执行notifyif (currentThread == Thread.currentThread()) {//修改locked, 表示未加锁, 唤醒剩余线程System.out.println(Thread.currentThread().getName() + ": 设置locked=false");this.locked = false;this.notifyAll();}}}@Overridepublic List<Thread> getBlockedThreads() {return blockedList;}
}
测试类:
package com.concurrent.definelock;import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;/*** 测试自定义BooleanLock* @author regotto*/
public class BooleanLockTest {private final Lock lock = new BooleanLock();public void syncMethod() {try {lock.lock();TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {BooleanLockTest blt = new BooleanLockTest();//定义10个线程, 进行start测试IntStream.range(0, 10).mapToObj(i -> new Thread(blt::syncMethod)).forEach(Thread::start);}
}
获取线程运行时异常
线程执行过程中, 可以为特定线程/全局线程设置运行时异常捕获, 通常使用Thread.setDefaultUncaughtExceptionHandler进行设置.
package com.concurrent.dealthreadexception;import java.util.concurrent.TimeUnit;public class CaptureThreadException {public static void main(String[] args) {//设置线程回调接口, 使得run方法出现异常, 回调Hook能捕获(该操作类似于监听者模式)//如果当前线程组没有设置回调接口, 那么就去父group中查找, 直到找到, 一直找不到//就执行System.errThread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {System.out.println("回调Hook捕获" + thread.getName() + "的run抛出的异常: " + throwable.getMessage());});final Thread thread = new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//run方法不能抛出异常, 就算抛出也无法捕获, 只能使用特定的Hook获取异常System.out.println(1 / 0);}, "Test0");thread.start();}
}
Thread.setDefaultUncaughtExceptionHandler源码如下:
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("setDefaultUncaughtExceptionHandler"));}//设置默认的异常捕获处理器, 0defaultUncaughtExceptionHandler = eh;}