亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java基礎(chǔ)之代碼死循環(huán)詳解

 更新時(shí)間:2021年04月27日 10:50:03   作者:蘇三說技術(shù)  
這篇文章主要介紹了Java基礎(chǔ)之代碼死循環(huán)詳解,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下

一、前言

代碼死循環(huán)這個(gè)話題,個(gè)人覺得還是挺有趣的。因?yàn)橹灰情_發(fā)人員,必定會(huì)踩過這個(gè)坑。如果真的沒踩過,只能說明你代碼寫少了,或者是真正的大神。

盡管很多時(shí)候,我們在極力避免這類問題的發(fā)生,但很多時(shí)候,死循環(huán)卻悄咪咪的來了,坑你于無形之中。我敢保證,如果你讀完這篇文章,一定會(huì)對(duì)代碼死循環(huán)有一些新的認(rèn)識(shí),學(xué)到一些非常實(shí)用的經(jīng)驗(yàn),少走一些彎路。

二、死循環(huán)的危害

我們先來一起了解一下,代碼死循環(huán)到底有哪些危害?

  • 程序進(jìn)入假死狀態(tài), 當(dāng)某個(gè)請求導(dǎo)致的死循環(huán),該請求將會(huì)在很大的一段時(shí)間內(nèi),都無法獲取接口的返回,程序好像進(jìn)入假死狀態(tài)一樣。
  • cpu使用率飆升,代碼出現(xiàn)死循環(huán)后,由于沒有休眠,一直不斷搶占cpu資源,導(dǎo)致cpu長時(shí)間處于繁忙狀態(tài),必定會(huì)使cpu使用率飆升。
  • 內(nèi)存使用率飆升,如果代碼出現(xiàn)死循環(huán)時(shí),循環(huán)體內(nèi)有大量創(chuàng)建對(duì)象的邏輯,垃圾回收器無法及時(shí)回收,會(huì)導(dǎo)致內(nèi)存使用率飆升。同時(shí),如果垃圾回收器頻繁回收對(duì)象,也會(huì)造成cpu使用率飆升。
  • StackOverflowError,在一些遞歸調(diào)用的場景,如果出現(xiàn)死循環(huán),多次循環(huán)后,最終會(huì)報(bào)StackOverflowError棧溢出,程序直接掛掉。

三、哪些場景會(huì)產(chǎn)生死循環(huán)?

3.1 一般循環(huán)遍歷

這里說的一般循環(huán)遍歷主要是指:

  • for語句
  • foreach語句
  • while語句

這三種循環(huán)語句可能是我們平常使用最多的循環(huán)語句了,但是如果沒有用好,也是最容易出現(xiàn)死循環(huán)的問題的地方。讓我們一起看看,哪些情況會(huì)出現(xiàn)死循環(huán)。

3.1.1 條件恒等

很多時(shí)候我們使用for語句循環(huán)遍歷,不滿足指定條件,程序會(huì)自動(dòng)退出循環(huán),比如:

for(int i=0; i<10; i++) {
   System.out.println(i);
}

但是,如果不小心把條件寫錯(cuò)了,變成這樣的:

for(int i=0; i>=0; i++) {
   System.out.println(i);
}

結(jié)果就悲劇了,必定會(huì)出現(xiàn)死循環(huán),因?yàn)檠h(huán)中的條件變成恒等的了。

很多朋友看到這里,心想這種錯(cuò)誤我肯定不會(huì)犯的。不過我需要特別說明的是,這里舉的例子相對(duì)來說比較簡單,如果i>=0這里是個(gè)非常復(fù)雜的計(jì)算,還真說不準(zhǔn)一定不會(huì)出現(xiàn)死循環(huán)。

3.1.2 不正確的continue

for語句在循環(huán)遍歷數(shù)組list時(shí)更方便,而while語句的使用場景卻更多。

有時(shí)候,在使用while語句遍歷數(shù)據(jù)時(shí),如果遇到特別的條件,可以使用continue關(guān)鍵字跳過本次循環(huán),直接執(zhí)行下次循環(huán)。

例如:

int count = 0;
while(count < 10) {
   count++;
   if(count == 4) {
      continue;
   }
   System.out.println(count);
}

當(dāng)count等于4時(shí),不打印count。

但如果continue沒有被正確使用,可能會(huì)出現(xiàn)莫名奇怪的問題:

int count = 0;
while(count < 10) {
   if(count == 4) {
      continue;
   }
   System.out.println(count);
   count++;
}

當(dāng)count等于4時(shí)直接推出本次循環(huán),count沒有加1,而直接進(jìn)入下次循環(huán),下次循環(huán)時(shí)count依然等4,最后無限循環(huán)了。

這種是我們要千萬小心的場景,說不定,已經(jīng)進(jìn)入了死循環(huán)你還不知道呢。

3.1.3 flag線程間不可見

有時(shí)候我們的代碼需要一直做某件事情,直到某個(gè)條件達(dá)到,有個(gè)狀態(tài)告訴它,要終止任務(wù)了,它就會(huì)自動(dòng)退出。

這時(shí)候,很多人都會(huì)想到用while(flag)實(shí)現(xiàn)這個(gè)功能:

public class FlagTest {
    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void fun() {
        while (flag) {
        }
        System.out.println("done");
    }

    public static void main(String[] args) throws InterruptedException {
        final FlagTest flagTest = new FlagTest();
        new Thread(() -> flagTest.fun()).start();
        Thread.sleep(200);
        flagTest.setFlag(false);
    }
}

這段代碼在子線程中執(zhí)行無限循環(huán),當(dāng)主線程休眠200毫秒后,將flag變成false,這時(shí)子線程就會(huì)自動(dòng)退出了。想法是好的,但是實(shí)際上這段代碼進(jìn)入了死循環(huán),不會(huì)因?yàn)閒lag變成false而自動(dòng)退出。

為什么會(huì)這樣?

線程間flag是不可見的,這時(shí)如果flag加上了volatile關(guān)鍵字,變成:

private volatile boolean flag = true;

會(huì)強(qiáng)制把共享內(nèi)存中的值刷新到主內(nèi)存中,讓多個(gè)線程間可見,程序可以正常退出。

3.2 Iterator遍歷

除了前面介紹過的一般循環(huán)遍歷之外,遍歷集合的元素,還可以使用Iterator遍歷。當(dāng)然并非所有集合都能使用Iterator遍歷,只有實(shí)現(xiàn)了Iterator接口的集合,或者該集合的內(nèi)部類實(shí)現(xiàn)了Iterator接口才可以。

例如:

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("123");
        list.add("456");
        list.add("789");

        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

但如果程序改成這樣:

public class IteratorTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("123");
        list.add("456");
        list.add("789");

        while(list.iterator().hasNext()) {
            System.out.println(list.iterator().next());
        }
    }
}

就會(huì)出現(xiàn)死循環(huán)。

這是什么呢?

如果看過ArrayList源碼的朋友,會(huì)發(fā)現(xiàn)它的底層iterator方法是這樣的實(shí)現(xiàn)的:

public Iterator<E> iterator() {
    return new Itr();
}

每次都new了一個(gè)新的Itr對(duì)象。而hasNext方法的底層是通過判斷游標(biāo)和元素個(gè)數(shù)是否相等實(shí)現(xiàn)的:

public boolean hasNext() {
    return cursor != size;
}

每次new了一個(gè)新的Itr對(duì)象的時(shí)候cursor值是默認(rèn)值0,肯定和元素個(gè)數(shù)不相等。所以導(dǎo)致while語句中的條件一直都成立,所以才會(huì)出現(xiàn)死循環(huán)。

我們都需要注意:在while循環(huán)中使用list.iterator().hasNext(),是個(gè)非常大的坑,千萬小心。

3.3 類中使用自己的對(duì)象

在某個(gè)類中把自己的對(duì)象定義成成員變量,不知道你有沒有這樣做過。

有些可能會(huì)很詫異,為什么要這么做。

假如,你需要在一個(gè)方法中調(diào)用另一個(gè)打了@Transactional注解的方法,這時(shí)如果直接方法調(diào)用,另外一個(gè)方法由于無法走代理事務(wù)會(huì)失效。比如:

@Service
public class ServiceA {

   public void save(User user) {
         System.out.println("業(yè)務(wù)處理");
         doSave(user);
   }

   @Transactional(rollbackFor=Exception.class)
   public void doSave(User user) {
       System.out.println("保存數(shù)據(jù)");
    }
 }

這種場景事務(wù)會(huì)失效。

這時(shí)可以通過把該類自己定義成一個(gè)成員變量,通過該變量調(diào)用doSave方法就能有效的避免該問題。

@Service
public class ServiceA {
   @Autowired
   private ServiceA serviceA;
   
   public void save(User user) {
         System.out.println("業(yè)務(wù)處理");
         serviceA.doSave(user);
   }

   @Transactional(rollbackFor=Exception.class)
   public void doSave(User user) {
       System.out.println("保存數(shù)據(jù)");
    }
 }

當(dāng)然還有其他辦法解決這個(gè)問題,不過這種方法是最簡單的。

問題來了,如果成員變量不是通過@Autowired注入,而是直接new出來的,可以嗎?

成員變量改成這樣之后:

private ServiceA serviceA = new ServiceA();

項(xiàng)目在啟動(dòng)的時(shí)候,程序進(jìn)入無限循環(huán),不斷創(chuàng)建ServiceA對(duì)象,但一直都無法成功。最后會(huì)報(bào)java.lang.StackOverflowError棧溢出,當(dāng)棧深度超過虛擬機(jī)分配給線程的棧大小時(shí)就會(huì)出現(xiàn)此錯(cuò)誤。

為什么會(huì)出現(xiàn)這個(gè)問題?

因?yàn)槌绦蛟趯?shí)例化ServiceA對(duì)象時(shí),要先實(shí)例化它的成員變量serviceA,但是它的成員變量serviceA,又需要實(shí)例化它自己的成員變量serviceA,如此一層層實(shí)例化下去,最終也沒能實(shí)例化。

@Autowired注入為什么沒有問題?

因?yàn)?code>@Autowired是在ServiceA對(duì)象實(shí)例化成功之外,在依賴注入階段,把實(shí)例注入到成員變量serviceA的。在spring中使用了三級(jí)緩存,通過提前暴露ObjectFactory對(duì)象來解決這個(gè)自己依賴自己的循環(huán)依賴問題。

對(duì)spring循環(huán)依賴問題有興趣的朋友,可以看看我之前寫的一篇文章《》。

3.4 無限遞歸

在日常工作中,我們需要經(jīng)常使用樹形結(jié)構(gòu)展示數(shù)據(jù),比如:分類、地區(qū)、組織、菜單等功能。

很多時(shí)候需要從根節(jié)點(diǎn)遍歷找到所有葉子節(jié)點(diǎn),也需要從葉子節(jié)點(diǎn),往上一直追溯到根節(jié)點(diǎn)。

我們以通過根節(jié)點(diǎn)遍歷找到所有葉子節(jié)點(diǎn)為例。由于每次需要一層層遍歷查找,而且調(diào)用的方法基本相同。為了簡化代碼,我們一般都會(huì)選擇使用遞歸來實(shí)現(xiàn)這個(gè)功能。

這里我們以根據(jù)葉子節(jié)點(diǎn)找到根節(jié)點(diǎn)為例,大致代碼如下:

public Category findRoot(Long categoryId) {
    Category category = categoryMapper.findCategoryById(categoryId);
    if(null == category) {
       throw new BusinessException("分類不存在");
    }
    Long parentId = category.getParentId();
    if(null == categoryId || 0 == categoryId) {
       return category;
    }
    return findRoot(parentId);
}

根據(jù)categoryId往上遞歸查找,如果發(fā)現(xiàn)parentId為null或者0的時(shí)候,就是根節(jié)點(diǎn)了,這時(shí)直接返回。

這可能是最普通不過的遞歸調(diào)用了,但是如果有人使壞,或者由于數(shù)據(jù)庫誤操作,把根節(jié)點(diǎn)的parentId改成了二級(jí)分類的categoryId一樣,比如都改成:1222。這樣遞歸調(diào)用會(huì)進(jìn)入無限循環(huán),最終會(huì)報(bào)java.lang.StackOverflowError異常。

為了避免這種慘案的發(fā)生,其實(shí)是有辦法的。

可以定義一個(gè)運(yùn)行遞歸的最大層級(jí)MAX_LEVEL,達(dá)到了最大層級(jí)則直接退出。以上代碼可以做如下調(diào)整:

private static final int MAX_LEVEL = 6;

public Category findRoot(Long categoryId, int level) {
    if(level >= MAX_LEVEL) {
       return null;
    }
    Category category = categoryMapper.findCategoryById(categoryId);
    if(null == category) {
       throw new BusinessException("分類不存在");
    }
    Long parentId = category.getParentId();
    if(null == categoryId || 0 == categoryId) {
       return category;
    }
    return findRoot(parentId, ++level);
}

先定義MAX_LEVEL的值,然后第一次調(diào)用遞歸方法的時(shí)候level字段的值傳1,每遞歸一次level的值加1,當(dāng)發(fā)現(xiàn)level的值大于等于MAX_LEVEL時(shí),說明出現(xiàn)了異常情況,則直接返回null。

我們在寫遞歸方法的時(shí)候,要養(yǎng)成好習(xí)慣,最好定義一個(gè)最大遞歸層級(jí)MAX_LEVEL,防止由于代碼bug,或者數(shù)據(jù)異常,導(dǎo)致出現(xiàn)無限遞歸的情況。

3.5 hashmap

我們在寫代碼時(shí),為了提高效率,使用集合的概率非常大。通常情況下,我們喜歡先把數(shù)據(jù)收集到集合當(dāng)中,然后對(duì)數(shù)據(jù)進(jìn)行批處理,比如批量insert或update,提升數(shù)據(jù)庫操作的性能。

我們使用比較多的集合有:ArrayList、HashSet、HashMap等。我個(gè)人非常喜歡使用HashMap,特別是在java8中需要嵌套循環(huán)的地方,將其中一層循環(huán)的數(shù)據(jù)(list或者set)轉(zhuǎn)換成HashMap,可以減少一層遍歷,提升代碼的執(zhí)行效率。

但是如果HashMap使用不當(dāng),可能會(huì)出現(xiàn)死循環(huán),怎么回事呢?

3.5.1 jdk1.7的HashMap

jdk1.7的HashMap中采用 數(shù)組 + 鏈表 的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)。在多線程環(huán)境下,同時(shí)往HaspMap中put數(shù)據(jù)時(shí),會(huì)觸發(fā)resize方法中的transfer方法,進(jìn)行數(shù)據(jù)重新分配的過程,需要重新組織鏈表的數(shù)據(jù)。

由于采用了頭插法,最終會(huì)形成key3的next等于key7,而key7的next又等于key3的情況,從而構(gòu)成了死循環(huán)。

3.5.2 jdk1.8的HashMap

有了解決jdk1.7擴(kuò)容時(shí)出現(xiàn)死循環(huán)的問題,在jdk1.8中對(duì)HashMap進(jìn)行了優(yōu)化,將jdk1.7中的頭插法改成了尾插法,另外采用 數(shù)組 + 鏈表 + 紅黑樹 的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)。如果鏈表中元素超過8個(gè)時(shí),就將鏈表轉(zhuǎn)化為紅黑樹,以減少查詢的復(fù)雜度,將時(shí)間復(fù)雜度降低為O(logN)。

在多線程環(huán)境下,同時(shí)往HaspMap中put數(shù)據(jù)時(shí),會(huì)觸發(fā)root方法重新組織樹形結(jié)構(gòu)的數(shù)據(jù)。

在for循環(huán)中會(huì)出現(xiàn)兩個(gè)TreeNode節(jié)點(diǎn)的Parent引用都是對(duì)方,從而構(gòu)成死循環(huán)的情況。

3.5.3 ConcurrentHashMap

由于在多線程環(huán)境下,使用無論是jdk1.7,還是jdk1.8的HashMap會(huì)有死循環(huán)的問題。所以很多人建議,不用在多線程環(huán)境下,使用HashMap,而應(yīng)該改用ConcurrentHashMap。

ConcurrentHashMap是線程安全的,同樣采用了 數(shù)組 + 鏈表 + 紅黑樹 的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù),此外還是使用了 cas + 分段鎖,默認(rèn)是16段鎖,保證并發(fā)寫入時(shí),數(shù)據(jù)不會(huì)產(chǎn)生錯(cuò)誤。

在多線程環(huán)境下,同時(shí)往ConcurrentHashMapcomputeIfAbsent數(shù)據(jù)時(shí),如果里面還有一個(gè)computeIfAbsent,它們的key對(duì)應(yīng)的hashCode是一樣的,這時(shí)就會(huì)產(chǎn)生死循環(huán)。

意不意外,驚不驚喜?

幸好這個(gè)bug在jdk1.9中已經(jīng)被Doug Lea修復(fù)了。

3.6 動(dòng)態(tài)代理

我們在實(shí)際工作中,即使沒有自己動(dòng)手寫過動(dòng)態(tài)代理程序,但也聽過或者接觸過,因?yàn)楹芏鄡?yōu)秀的開發(fā)框架,它們的底層必定都會(huì)使用動(dòng)態(tài)代理,實(shí)現(xiàn)一些附加的功能。通常情況下,我們使用最多的動(dòng)態(tài)代理是:JDK動(dòng)態(tài)代理Cglib,spring的AOP就是通過這兩種動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)的。

我們在這里以JDK動(dòng)態(tài)代理為例:

public interface IUser {
    String add();
}
public class User implements IUser {
    @Override
    public String add() {
        System.out.println("===add===");
        return "success";
    }
}
public class JdkProxy implements InvocationHandler {

    private Object target;

    public Object getProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target,args);
        after();
        return result;
    }

    private void before() {
        System.out.println("===before===");
    }

    private void after() {
        System.out.println("===after===");
    }
}
public class Test {
    public static void main(String[] args) {
        User user = new User();
        JdkProxy jdkProxy = new JdkProxy();
        IUser proxy = (IUser)jdkProxy.getProxy(user);
        proxy.add();
    }
}

實(shí)現(xiàn)起來主要有三步:

1.實(shí)現(xiàn)某個(gè)具體業(yè)務(wù)接口

2.InvocationHandler接口,創(chuàng)建調(diào)用關(guān)系

3.使用Proxy創(chuàng)建代理類,指定被代理類的相關(guān)信息

這樣在調(diào)用proxy的add方式時(shí),會(huì)自動(dòng)調(diào)用before和after方法,實(shí)現(xiàn)了動(dòng)態(tài)代理的效果,是不是很酷?

通常情況下,這種寫法是沒有問題的,但是如果在invoke方法中調(diào)用了proxy對(duì)象的toString方法,加了這段代碼:

proxy.toString();

程序再次運(yùn)行,循環(huán)很多次之后,就會(huì)報(bào)java.lang.StackOverflowError異常。

很多人看到這里可能一臉懵逼,到底發(fā)生了什么?

代理對(duì)象本身并沒有自己的方法,它的所有方法都是基于被代理對(duì)象的。通常情況下,如果訪問代理對(duì)象的方法,會(huì)經(jīng)過攔截器的invoke方法。但是如果在invoke方法調(diào)了代理對(duì)象的方法,比如:toString方法,會(huì)經(jīng)過另外一個(gè)攔截器的invoke方法,如此一直反復(fù)調(diào)用,最終形成死循環(huán)。

切記不要在invoke方法中調(diào)用代理對(duì)象的方法,不然會(huì)產(chǎn)生死循環(huán),坑你于無形之中。

3.7 我們自己寫的死循環(huán)

很多朋友看到這個(gè)標(biāo)題,可能會(huì)質(zhì)疑,我們自己會(huì)寫死循環(huán)?

沒錯(cuò),有些場景我們還真的會(huì)寫。

3.7.1 定時(shí)任務(wù)

不知道你有沒有手寫過定時(shí)任務(wù),反正我寫過,是非常簡單的那種(當(dāng)然復(fù)雜的也寫過,在這里就不討論了)。如果有個(gè)需求要求每隔5分鐘,從遠(yuǎn)程下載某個(gè)文件最新的版本,覆蓋當(dāng)前文件。

這時(shí)候,如果你不想用其他的定時(shí)任務(wù)框架,可以實(shí)現(xiàn)一個(gè)簡單的定時(shí)任務(wù),具體代碼如下:

public static void downLoad() {
    new Thread(() -> {
        while (true) {
            try {
                System.out.println("download file");
                Thread.sleep(1000 * 60 * 5);
            } catch (Exception e) {
                log.error(e);
            }
        }
    }).start();
}

其實(shí)很多JDK中的定時(shí)任務(wù),比如:Timer類的底層,也是用了while(true)的無限循環(huán)(也就是死循環(huán))來實(shí)現(xiàn)的。

3.7.2 生產(chǎn)者消費(fèi)者

不知道你有沒有手寫過生產(chǎn)者和消費(fèi)者。假設(shè)有個(gè)需求需要把用戶操作日志寫入表中,但此時(shí)消費(fèi)中還沒有引入消息中間件,比如:kafka等。

最常規(guī)的做法是在接口中同步把日志寫入表中,保存邏輯跟業(yè)務(wù)邏輯可能在同一個(gè)事務(wù)中,但為了性能考慮,避免大事務(wù)的產(chǎn)生,一般建議不放在同一個(gè)事務(wù)。

原本挺好的,但是如果接口并發(fā)量上來了,為了優(yōu)化接口性能,可能會(huì)把同步寫日志到表中的邏輯,拆分出來,做成異步處理的。

這時(shí)候,就可以手動(dòng)擼一個(gè)生產(chǎn)者消費(fèi)者解決這個(gè)問題了。

@Data
public class Car {
    private Integer id;
    private String name;
}
@Slf4j
public class Producer implements Runnable {

    private final ArrayBlockingQueue<Car> queue;

    public Producer(ArrayBlockingQueue<Car> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        int i = 1;
        while (true) {
            try {
                Car car = new Car();
                car.setId(i);
                car.setName("汽車" + i);
                queue.put(car);
                System.out.println("Producer:" + car + ", queueSize:" + queue.size());
            } catch (InterruptedException e) {
                log.error(e.getMessage(),e);
            }
            i++;
        }
    }
}
@Slf4j
public class Consumer implements Runnable {

    private final ArrayBlockingQueue<Car> queue;

    public Consumer(ArrayBlockingQueue<Car> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Car car = queue.take();
                System.out.println("Consumer:" + car + ",queueSize:" + queue.size());
            } catch (InterruptedException e) {
                log.error(e.getMessage(), e);
            }
        }
    }
}
public class ClientTest {

    public static void main(String[] args) {
        ArrayBlockingQueue<Car> queue = new ArrayBlockingQueue<Car>(20);
        new Thread(new Producer(queue)).start();
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

由于ArrayBlockingQueue阻塞隊(duì)列內(nèi)部通過notEmptynotFull 這兩個(gè)Condition實(shí)現(xiàn)了阻塞和喚醒機(jī)制,所以我們無需再做額外控制,用它實(shí)現(xiàn)生產(chǎn)者消費(fèi)者相對(duì)來說要容易多了。

四、自己寫的死循環(huán)要注意什么?

不知道聰明的小伙伴們有沒有發(fā)現(xiàn),我們自定義的定時(shí)任務(wù)生產(chǎn)者消費(fèi)者例子中,也寫了死循環(huán),但跟上面其他的例子都不一樣,我們寫的死循環(huán)沒有出現(xiàn)問題,這是為什么?

定時(shí)任務(wù)中我們用了sleep方法做休眠:Thread.sleep(300000);。

生產(chǎn)者消費(fèi)者用了Condition類的awaitsignal方法實(shí)現(xiàn)了阻塞和喚醒機(jī)制。

這兩種機(jī)制說白了,都會(huì)主動(dòng)讓出cpu一段時(shí)間,讓其他的線程有機(jī)會(huì)使用cpu資源。這樣cpu有上下文切換的過程,有一段時(shí)間是處于空閑狀態(tài)的,不會(huì)像其他的列子中一直處于繁忙狀態(tài)。

一直處于繁忙狀態(tài)才是cpu使用率飆高的真正原因,我們要避免這種情況的產(chǎn)生。

就像我們平時(shí)騎共享單車(cpu資源)一樣,我們一般騎1-2小時(shí)就會(huì)歸還了,這樣其他人就有機(jī)會(huì)使用這輛共享單車。但如果有個(gè)人,騎了一個(gè)天還沒歸還,那么這一天當(dāng)中自行車一直處于繁忙之中,其他人就沒有機(jī)會(huì)騎這輛自行車了。

到此這篇關(guān)于Java基礎(chǔ)之代碼死循環(huán)詳解的文章就介紹到這了,更多相關(guān)Java代碼死循環(huán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • mybatis-plus?Wrapper條件構(gòu)造器updateForSet更新方式

    mybatis-plus?Wrapper條件構(gòu)造器updateForSet更新方式

    這篇文章主要介紹了mybatis-plus?Wrapper條件構(gòu)造器updateForSet更新方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java實(shí)現(xiàn)貪吃蛇游戲的示例代碼

    Java實(shí)現(xiàn)貪吃蛇游戲的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)簡單的貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Java設(shè)計(jì)模式之模板方法模式

    Java設(shè)計(jì)模式之模板方法模式

    這篇文章介紹了Java設(shè)計(jì)模式之模板方法模式,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-10-10
  • Java中的命名與目錄接口JNDI基本操作方法概覽

    Java中的命名與目錄接口JNDI基本操作方法概覽

    這篇文章主要介紹了Java中的命名與目錄接口JNDI基本操作方法概覽,JNDI提供統(tǒng)一的客戶端API使得Java應(yīng)用程序可以和這些命名服務(wù)和目錄服務(wù)之間進(jìn)行交互,需要的朋友可以參考下
    2016-03-03
  • SpringBoot Session共享實(shí)現(xiàn)圖解

    SpringBoot Session共享實(shí)現(xiàn)圖解

    這篇文章主要介紹了SpringBoot Session共享實(shí)現(xiàn)圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • 可視化定時(shí)任務(wù)quartz集成解析全過程

    可視化定時(shí)任務(wù)quartz集成解析全過程

    在開發(fā)中有很多定時(shí)任務(wù)都不是寫死的而是可以人為配置并且寫到數(shù)據(jù)庫中的,下面這篇文章主要給大家介紹了關(guān)于可視化定時(shí)任務(wù)quartz集成解析的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • springboot配置文件讀取pom文件信息方式

    springboot配置文件讀取pom文件信息方式

    這篇文章主要介紹了springboot配置文件讀取pom文件信息方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • SpringBoot之Profile的兩種使用方式詳解

    SpringBoot之Profile的兩種使用方式詳解

    當(dāng)在不同的環(huán)境下,想通過修改配置文件來連接不同的數(shù)據(jù)庫,比如在開發(fā)過程中啟動(dòng)項(xiàng)目時(shí),想連接開發(fā)環(huán)境對(duì)應(yīng)的數(shù)據(jù)庫,可以在配置文件中指定environment=dev,其他環(huán)境類似,此時(shí)就需要用到Spring為我們提供的Profile功能,本文給大家介紹了SpringBoot之Profile的兩種使用方式
    2024-10-10
  • Java針對(duì)ArrayList自定義排序的2種實(shí)現(xiàn)方法

    Java針對(duì)ArrayList自定義排序的2種實(shí)現(xiàn)方法

    這篇文章主要介紹了Java針對(duì)ArrayList自定義排序的2種實(shí)現(xiàn)方法,結(jié)合實(shí)例形式總結(jié)分析了Java操作ArrayList自定義排序的原理與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2018-01-01
  • Java中的Cglib動(dòng)態(tài)代理詳細(xì)解讀

    Java中的Cglib動(dòng)態(tài)代理詳細(xì)解讀

    這篇文章主要介紹了Java中的Cglib動(dòng)態(tài)代理詳細(xì)解讀,CGLib是一個(gè)強(qiáng)大的、高性能、高質(zhì)量的 Code 生成類庫,它可以在運(yùn)行期擴(kuò)展 Java 類與實(shí)現(xiàn) Java 接口,需要的朋友可以參考下
    2023-11-11

最新評(píng)論