synchronized中耗时操作或者sleep问题

2021-12-14  本文已影响0人  zhengaoly

先说结论

synchronized为非公平锁,耗时代码块在while(true)中,执行完毕以后,大概率下次还会抢占锁,导致其他线程一直拿不到锁,从而阻塞。

synchronized代码块中,如果有sleep,此时锁并不会被释放,就算本次while循环执行完毕,释放了,由于synchronized是非公平锁,下次大概率还会进入sleep代码块,这样会导致锁被长期占用,从而阻塞其他线程。
sleep不会释放锁,但会让出cpu,wait会释放锁

image.png
如上代码:
线程1,向容器中写入缓存
线程2,判断容器中有没有数据,没有数据,sleep 100ms
有数据处理数据

代码看上去没什么问题。
但是,由于sleep期间,锁并未释放,导致线程1被阻塞,即容器无法被写入。

线程2调度分两种情况

  1. 线程调度时,线程2拿到锁,sleep 100ms,此时让出了cpu,但是由于synchronized非公平锁,线程可能会再次调度到线程2,导致线程2while(true)再次拿到锁,接着拿着锁sleep 100ms,如此往复,线程2可能一直拿到锁然后sleep,可达几十次甚至上百次。在此期间,线程1会一直阻塞。
    2.线程2拿到锁,然后sleep 100ms,此时线程2让出cpu,然后切到其他不相关线程如线程3,线程3执行了一段耗时的代码,然后又切回了线程2,然后在执行线程2,在此期间,线程1一直阻塞,因为线程2一直站着锁。

以上两种情况都是线程2切换期间,都为释放锁导致。
因此,sleep期间,一定不能拿着锁睡,否则会导致锁长时间被占用,引起其他线程阻塞。
如果线程需要等待,需要搭配wait,notify将线程暂停,并释放锁
例如:

线程2:
synchronized(queue){
  while(queue.isEmpty()){
  queue.wait();
  }
线程1:
synchronized(queue){
  queue.add();
  queue.notifyAll();
  }

如下代码演示同步中的耗时代码块问题

import lombok.extern.slf4j.Slf4j;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;

public class TestSync {
    public static void main(String[] args) {
        Queue<String> con = new ConcurrentLinkedDeque<>();
        ThreadA threadA = new ThreadA(con);
        ThreadB threadB = new ThreadB(con);
        threadA.setName("threadA");
        threadB.setName("threadB");
        threadB.start();
        threadA.start();
    }


}

@Slf4j
class ThreadA extends Thread {
    Queue<String> con;

    ThreadA(Queue<String> p) {
        con = p;
    }

    @Override
    public void run() {
        super.run();
        int a = 0;
        while (true) {
            synchronized (con) {
                log.info("线程A获得锁"+a++);
                try {
                    //模拟耗时操作
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

@Slf4j
class ThreadB extends Thread {
    Queue<String> con;

    ThreadB(Queue<String> p) {
        con = p;
    }

    @Override
    public void run() {
        super.run();
        int b=0;
        while (true) {
            synchronized (con) {
                log.info("线程B获得锁"+b++);
            }
        }
    }
}

1.线程A,线程B启动以后竞争con锁
2.线程A获得锁以后,执行一段耗时的代码(sleep模拟),然后释放锁
3.线程B获得锁很快执行完成,并释放锁。

通过jprofiler查看阻塞情况。


image.png
image.png
上一篇 下一篇

猜你喜欢

热点阅读