并发编程(第二天)

interrupt 方法详解

  • 打断 sleep,wait,join 的线程 这几个方法都会让线程进入阻塞状态 打断 sleep 的线程, 会清空打断状态
  • 打断正常运行的线程 打断正常运行的线程, 不会清空打断状态
  • 打断 park 线程 打断 park 线程, 不会清空打断状态

如果打断标记已经是 true, 则 park 会失效

两阶段终止模式

class TPTInterrupt {
 private Thread thread;
 public void start(){
     thread = new Thread(() -> {
     while(true) {
     Thread current = Thread.currentThread();
     if(current.isInterrupted()) {
         log.debug("料理后事");
         break;
    }
 try {
     Thread.sleep(1000);
     log.debug("将结果保存");
  } 
  catch (InterruptedException e) {
      current.interrupt();
  }
 // 执行监控操作               
 }
},"监控线程");
thread.start();
}
 public void stop() {
 thread.interrupt();
    }
 }

 调用

TPTInterrupt t = new TPTInterrupt();
 t.start();
 Thread.sleep(3500);
 log.debug("stop");
 t.stop();
  • 11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
  • 11:49:43.919 c.TwoPhaseTermination[监控线程] - 将结果保存
  • 11:49:44.919 c.TwoPhaseTermination[监控线程] - 将结果保存
  • 11:49:45.413 c.TestTwoPhaseTermination [main] - stop
  • 11:49:45.413 c.TwoPhaseTermination[监控线程] - 料理后事 

主线程与守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守 护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

log.debug("开始运行...");
 Thread t1 = new Thread(() -> {
 log.debug("开始运行...");
 sleep(2);
 log.debug("运行结束...");
 }, "daemon");
 // 设置该线程为守护线程
t1.setDaemon(true);
 t1.start();
 sleep(1);
 log.debug("运行结束...");
  •  垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求

五种状态(线程)

从 操作系统 层面来描述

【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联

【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行

【运行状态】指获取了 CPU 时间片运行中的状态

  • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换

【阻塞状态】

  • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 【阻塞状态】
  • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
  • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑 调度它们

【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

六种状态

是从 Java API 层面来描述

根据 Thread.State 枚举,分为六种状态

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行)
  • BLOCKED , WAITING , 详述 TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节
  • TERMINATED 当线程代码运行结束

共享模型之管程

临界区 Critical Section

一个程序运行多个线程本身是没有问题的

问题出在多个线程访问共享资源

  • 多个线程读共享资源其实也没有问题
  • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

synchronized

俗称的【对象锁】,它采用互斥的方式让同一 时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁 的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

语法 

synchronized(对象) // 线程1, 线程2(blocked)
{
     临界区
}
static int counter = 0;
 static final Object room = new Object();
 public static void main(String[] args) throws InterruptedException {
 Thread t1 = new Thread(() -> {
 for (int i = 0; i < 5000; i++) {
 synchronized (room) {
 counter++;
            }
        }
    }, 
"t1");
 Thread t2 = new Thread(() -> {
 for (int i = 0; i < 5000; i++) {
 synchronized (room) {
 counter--;
            }
        }
    }, 
"t2");
 }
 t1.start();
 t2.start();
 t1.join();
 t2.join();
 log.debug("{}",counter);

加上同一把对象所之后结果就只会是0,不会因为多线程问题导致结果不唯一。 

方法上的 synchronized

class Test{
 public synchronized void test() {
  }
}
等价于
class Test{
 public void test() {
 synchronized(this) {
     }
    }
 }
 class Test{
 public synchronized static void test() {
   }
 }
等价于
class Test{
 public static void test() {
 synchronized(Test.class) {
   }
 }

加在普通方法上表示锁的对象是自己

加在静态方法上表示锁的对象是类(字节码) 

所谓的“线程八锁”

情况1: 12 或 21

 @Slf4j(topic = "c.Number")
 class Number{
 public synchronized void a() {
 log.debug("1");
    }
 public synchronized void b() {
 log.debug("2");
    }
 }
 public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
 }

 情况2: 1s后12,或 2 1s后 1 

 @Slf4j(topic = "c.Number")
 class Number{
 public synchronized void a() {
 sleep(1);
 log.debug("1");
    }
 public synchronized void b() {
 log.debug("2");
    }
 }
 public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
 }

情况3: 3 1s 12 或 23 1s 1 或 32 1s 1

@Slf4j(topic = "c.Number")
class Number{
 public synchronized void a() {
 sleep(1);
 log.debug("1");
    }
 public synchronized void b() {
 log.debug("2");
    }
 public void c() {
 log.debug("3");
    }
 }
 public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
 new Thread(()->{ n1.c(); }).start();
 }

过多也就不展示了,本质上其实就是考察 synchronized 锁住的是哪个对象 

 

 

 

 

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值