JUC--上下文切换

JUC–上下文切换

背景

  • JUC–上下文切换

  • 博主以黑马JUC进行学习

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test1{
static int counter = 0;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(() -> {
for(int i = 0;i < 5000; i++){
counter++;
}
},"t1");

Thread t2 = new Thread(() -> {
for(int i = 0;i < 5000; i++){
counter--;
}
},"t2");
t1.start();
t2.start();

t1.join();
t2.join();
log.debug("{}",counter);
}
}
  • 结果不为0,因为由于分时系统造成的线程切换而导致的安全问题

临界区 Critical Section

  • 一个程序运行多个线程本身是没有问题的
  • 问题出在多个线程访问共享资源
    • 多个线程读共享资源其实也没有问题
    • 在多个线程对共享资源读写时发生指令交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码为临界区
1
2
3
4
5
6
7
8
9
10
11
12
static int counter = 0;

static void increment()
//临界区
{
counter++;
}
static void decrement()
//临界区
{
counter--;
}

竞态条件 Race Condition

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

synchronized 解决方案

  • 为了避免临界区的竞态条件发生,有多种手段可以达到目的。

    • 阻塞式的解决方案:synchronized,Lock
    • 非阻塞式的解决方案:原子变量
  • 使用synchronized解决问题,即熟称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获得这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区的代码,不用担心线程上下文切换

    • 注意
    • 虽然java中互斥和同步都可以采用synchronized 关键字来完成,但它们还是有区别的:
      • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
      • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
  • synchronized

    • 语法
    1
    2
    3
    4
    synchronized(对象)
    {
    临界区
    }
    • 解决
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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);
    }
    • synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
      • 如果把synchronized(obj)放在for循环的外面,如何理解?–原子性
      • 如果t1 synchronized(obj1) 而t2 synchronized(obj2) 会怎样运作?–锁对象
      • 如果t1 synchronized(obj) 而t2没有加会怎么样?如何理解? --锁对象
  • 锁对象面向对象改进

    • 把需要保护的共享变量放入一个类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public class Test2{
    public static void main(String[] args) throws InterruptedException{
    Room room = new Room();
    Thread t1 = new Thread(() -> {
    for(int i = 0;i < 5000; i++){
    room.increment();
    }
    },"t1");

    Thread t2 = new Thread(() -> {
    for(int i = 0;i < 5000; i++){
    room.decrement();
    }
    },"t2");

    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("{}",room.counter);
    }
    }


    class Room{
    private int counter = 0;
    public void increment(){
    synchronized(this){
    counter++;
    }
    }
    public void decrement(){
    synchronized(this){
    counter--;
    }
    }

    public int getCounter(){
    synchronized(this){
    return counter;
    }
    }
    }
    • 两种语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Test{
    public synchronized void test(){

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

    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Test{
    public synchronized static void test(){

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

    }
    }
    }
    • 不加synchronized的方法
      • 不加synchronized 的方法就好比不遵守规则的人,不去老实排队