线程学习笔记
线程学习笔记
线程上下文切换;会存储上一个执行线程的状态,比如栈帧信息,程序计数器信息等,切换会耗费计算机资源的,所以并不是线程越多,执行效率就越高,如果线程数大于CPU的核心数,切换就会越频繁
线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法,需要保存线程状态
创建线程
- 重写Thread的run()方法
- 将线程和线程要执行的东西分离,使用接口Runnable
- 用Future附加更多功能
Callable和Runnable
- Runnable,run()方法是void,所以没有任何返回结果
- Callable位于java.util.concurrent包,call()方法能返回结果
- 这两者是没有比较的,根本就是不同的东西,Runnable是线程执行的内容,Callalbe只是说明该线程最后会返回数据,而返回的数据需要用Future去获取
...
FutureTask<Integer> future = new FutureTask<Integer>(callable);
// FutureTask是实现Runnable接口的,只是又实现了Future接口
new Thread(future, "future").start();
...
future.get(); // 会返回future线程执行完毕后返回的结果,是一个阻塞方法,可以用另一个线程去获取
// 下面是一个例子说明Runnable和Callable, Future的使用
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("Runnable执行操作,普通线程");
}
};
new Thread(runnable, "normal_thread").start();
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
log.debug("Callable标记操作,返回数据线程");
sleep(2);
return new Random().nextInt(100);
}
};
// FutureTask implements Runnable, Future<V>
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future, "callable_thread").start();
new Thread(()->{
try {
log.debug("得到线程结果" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}, "future_thread").start();
可以理解为,Callable和Future只是增强了Runnable接口,Callable标记这个线程会有返回,而Future就是去接收这个返回
常见方法
- start()方法,启动线程
- run()方法,启动的线程执行的操作,无返回的Runnable和又返回的Callable,Future最后都执行的是Runnable的run()方法
- join()方法,等待线程运行结束,哪个线程调用join,哪个等待哪个线程结束,带参数的等待,线程还没结束,则继续后面的代码,如果线程提前结束,则join会提前结束
Thread t1 = new Thread(() -> {
log.debug("t1 start");
// sleep(1); // join(),等待结束
// sleep(4); // join(2000),等待2s,之后继续执行
sleep(3); // join(7000),等待7s,但线程提前结束,join()也结束
log.debug("t1 end");
}, "t1_thread");
Thread t2 = new Thread(() -> {
log.debug("t2 start");
try {
// t1.join(); // 等待t1线程执行结束,结束之后才会继续执行后面的代码
// t1.join(2000); // 等待t1线程2s,2s之后继续执行后面的代码,不管t1是否执行完成,都会继续后面的代码
t1.join(7000); // 等待t1线程7s,但线程提前结束,则join()也会提前结束
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t2 end");
}, "t2_thread");
t1.start();
t2.start();
-
interrupt(),标记打断状态
-
static interrupted(),判断当前线程是否被打断,会清除打断标记
-
isInterrupted(),判断当前线程是否被打断,不会清除打断标记
-
yield(),让出线程的使用权
打断线程
调用该方法并不会打断线程,只是会标记该线程是否打断
- 打断阻塞的线程:调用sleep,wait,join的线程会进入阻塞状态,打断阻塞的线程,会清除打断标记,也就是isInterupted为false,会抛出异常(InterruptedException)
- 打断正常的线程,清除标记设置为true
- 打断park线程,LockSupport.park()会让线程进入park线程,打断标记为true时,调用LockSupport.park()不会进入park状态
Thread t1 = new Thread(() -> {
log.debug("t1 thread is running..");
while(true){
}
}, "t1");
t1.start();
t1.interrupt(); // 如果线程没有启动(未调用start()方法),打断标记为false
log.debug("t1 thread interrupt normal: " + t1.isInterrupted()); // true
Thread t2 = new Thread(() -> {
log.debug("t2 thread is running..");
while(true){
sleep(3);
log.debug("t2 running");
}
}, "t2");
t2.start();
t2.interrupt();
log.debug("t2 thread interrupt with no sleep: " + t2.isInterrupted()); // true,t2线程启动后打断,但t2线程还没进入sleep就打断,所以返回false
Thread t3 = new Thread(() -> {
log.debug("t3 thread is running..");
while(true){
sleep(3);
log.debug("t3 running");
}
}, "t3");
t3.start();
sleep(1); // 保证t3线程进入sleep
t3.interrupt();
log.debug("t3 thread interrupt with real sleep: " + t3.isInterrupted()); // false,t3线程已经进入sleep,所以打断标记为false
Thread t4 = new Thread(()->{
log.debug("t4 thread execute");
}, "t4");
t4.start();
sleep(1);
t4.interrupt();
log.debug("t4 thread has terminated: " + t4.isInterrupted()); // false
Object lock = new Object();
Thread t5 = new Thread(()->{
synchronized (lock){
while(true){
sleep(1);
log.debug("t5 execute");
}
}
}, "t5");
t5.start();
sleep(1);
Thread t6 = new Thread(()->{
synchronized (lock){
while(true){
log.debug("t6 execute");
}
}
}, "t6");
t6.start();
sleep(1); // 此时t6线程肯定已经执行到获取lock的代码,此时题t6线程状态是BLOCKED
log.debug("t6 thread " + t6.getState()); // t6 thread RUNNABLE,原因是t6线程启动后,还没执行到synchronized代码
t6.interrupt();
log.debug("t6 thread at blocked state: " + t6.isInterrupted()); // true,证明线程处于BLOCKED时执行interrupt(),打断标记会设置为true
虽然调用了interrupt()方法,t1,t2,t3线程依然会继续执行,该方法做的事仅仅是设置打断标记,设置成功与否还要分情况,并不会真正停止线程,而是要求线程自己去判断是否需要停止,这么做的理由和stop()方法一样,如果是外部线程能终止一个线程,可能导致终止的线程锁死共享资源永远不会释放,所以只是设置打断标记,然后由线程自己去监控是否终止线程,就是两阶段终止模式。
线程中的两阶段终止模式
stop()会真正的终止线程,如果此线程锁住了共享资源,则永远不会释放锁了,所以最好不要使用这个API,使用两阶段终止模式,让线程自己终止线程。
Thread t1 = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){// 执行阶段
log.debug("执行收尾工作,释放资源等操作");
break; // 由线程自己来结束,而不是外部线程,就是避免资源锁死的情况
}
log.debug("模拟线程的各种操作");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 保证打断标记设置成功
e.printStackTrace();
}
}
}, "t1");
t1.start();
sleep(3);
t1.interrupt(); // 准备阶段,标记线程被打断,但不是真正终止线程
线程状态
- 操作系统的概念:初始,可运行,运行,阻塞,终止,操作系统将线程分为这5个状态
- java中的状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,java中定义了6中状态(java.lang.Thread.State)
- NEW,创建了线程,但未启动
- RUNNABLE,其他状态以外的状态,比如等待IO,在操作系统看属于阻塞,在java看属于RUNNABLE状态
- BLOCKED,等待锁的状态
- WAITING,等待其他线程,比如join,park,wait
- TIMED_WAITING,等待其他线程,只是有时间限制,比如sleep,join(1000),wait(1000)
- TERMINATED,线程结束
经过测试,线程处于RUNNABLE和BLOCKED状态的时候,执行interrupt()方法会将设置打断标记为true,其他时候不会设置。参考https://blog.csdn.net/crazyzxljing0621/article/details/56666418,当线程IO阻塞时,执行interrupt()方法会将设置打断标记为true,可以理解为IO阻塞对java而言线程依然是RUNNABLE状态,简单理解就是,只要线程处于RUNNABLE和BLOCKED状态,调用interrupt()方法,就是设置打断标记为true,我的理解,线程的RUNNABLE和BLOCKED这两个状态是线程活动状态的时候,能进入CPU时间片执行,像WAITING,TIMED_WAITING因为要等其他线程,NEW和TERMINATED线程没有在执行,所以只有RUNNABLE和BLOCKED可以成功标记打断状态isInterrupt()返回true
守护线程
没有其他线程运行的时候,守护线程就会结束。
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (; ; ) {
log.debug("t1 daemon thread execute");
sleep(1);
}
}, "t1");
t1.setDaemon(true);
t1.start();
Thread t2 = new Thread(()->{
sleep(1);
log.debug("t2 thread finish");
}, "t2");
t2.start();
}
虽然t1线程是死循环,但它是一个守护线程,当t2线程结束的时候,t1也会结束。
synchronized使用对象锁保证临界区内代码的原子性
能锁住对象,只能是对象,也就是在堆中有空间。synchronized修饰方法时,锁住的是this对象。
面向对象的改进
线程安全的类。共享资源的对象化。
@Slf4j
public class ThreadSafeTest {
private static int lockCount = 0;
public static void main(String[] args) {
useLockCount();
useLockObject();
}
private static void useLockCount() {
Thread incr = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (ThreadSafeTest.class) {
lockCount++;
}
}
}, "incr");
Thread decr = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (ThreadSafeTest.class) {
lockCount--;
}
}
}, "decr");
decr.start();
incr.start();
try {
incr.join();
decr.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("lockCount is: " + lockCount); // 一定为0,使用锁保证资源的安全
}
// 两个线程同时访问lockCount,需要保证安全,可以将lockCount用对象的形式实现
private static void useLockObject() {
Room room = new Room();
Thread incr = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
room.incr();
}
}, "incr");
Thread decr = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
room.desc();
}
}, "decr");
decr.start();
incr.start();
try {
incr.join();
decr.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("useLockObject is: " + room.getCount()); // 一定为0,使用锁保证资源的安全
}
// 加锁的行为对象化
// lockCount的对象化
public static class Room {
private static int count = 0;
public synchronized void incr() {
count++;
}
public synchronized void desc() {
count--;
}
public synchronized int getCount() {
return count;
}
}
}
线程安全的类就是这么提出来的,像String, StringBuffer, Vector, Hashtable就是线程安全的类。