1. 程式人生 > >java趣談(2)說說自增那些事

java趣談(2)說說自增那些事

最近,做題的時候經常遇到自增的問題,就此總結整理一下

一、常見情況

自增方式有兩種:前置和後置,即i++和++i,它們的不同點在於i++是在程式執行完畢後進行自增,而++i是在程式開始執行前進行自增,示例如下:

public static void main(String[] args){
		int i = 1;
		System.out.println(i++ + i++);
		System.out.println("i=" + i);
		System.out.println(i++ + ++i);
		System.out.println("i=" + i);
		System.out.println(i++ + i++ + i++);
		System.out.println("i=" + i);
	}

結果如下


不明白的話可看下面的分析,看懂的直接跳過即可

表示式 i++ + i++首先執行第一個i++操作,由於自增操作會稍後執行,因此,運算時,i的值還是1,但自增操作後,i的值變為了2,接著執行第二個i++,運算時,i的值已經為2了,二執行了一個自增操作後,i的值變為了3,所以i++ + i++ = 1 + 2 =3,而運算完成後,i的值變為3

表示式 i++ + ++i首先執行第一個i++,但是自增操作後稍後執行。因此,此時i的值還為3,接著執行++i,此時i的值變為4,同時還要補執行i++的自增操作,所以i++ + ++i = 3 + 5 = 8

同理,i++ + i++ + i++ = 5 + 6 + 7 =18

二、迴圈中的自增

for迴圈中,由於for迴圈的第三個條件是最後判斷(先執行迴圈語句,再執行第三個條件),所以i++和++i無甚區別,上程式碼說明:



while迴圈中,要首先判斷括號內的條件,所以++i相比i++,少執行一次



三、a=a++?

最初看到這個表示式,沒在意,以為和a++一個意思,後來跑了一段程式碼才發現不對勁

上程式碼



簡單版的解釋:可以理解為中間快取變數機制

a = a++ 等同於

temp = a;

i = i + 1;

i = temp;

而a = ++a 等同於

a = a + 1;

temp = i;

i = temp;

深層次解釋(結合jvm解釋):

第1步:將int型別的0入棧,就是放到運算元棧的棧頂
第2步:將運算元棧棧頂的值0彈出,儲存到區域性變量表 index (索引)值為1的位置。(區域性變量表也是從0開始的,0位置一般儲存當前例項的this引用,當然靜態方法例外,因為靜態方法是類方法而不是例項方法)第3步:將區域性變量表index 1位置的值的副本入棧。(這時區域性變量表index為1的值是0,運算元棧頂的值也是0)第4步:int型別的值進行自增操作,(這時區域性變量表index為1的值因為執行了自增操作變為1了,但是運算元棧中棧頂的值仍然是0)第5步:將運算元棧頂的值彈出(值0),放到區域性變量表index為1的位置(舊值:1,新值:0),覆蓋了上一步區域性變量表的計算結果。第6步:將區域性變量表index 1位置的值的副本入棧。(這時區域性變量表index為1的值是0,運算元棧頂的值也是0)總結:從執行順序可以看到,這裡第2和第5步執行了2次將0賦值給變數i的操作,i++操作是在這兩次操作之間執行的,自增操作是對區域性變量表中的值進行自增,而棧頂的值沒有發生變化,這裡需要注意的是儲存這個初始值的地方是運算元棧而不是區域性變量表,最後再將棧頂的值覆蓋到區域性變量表i所在的索引位置中去。

四、i++是執行緒安全的嗎?

看到這個問題一臉蒙逼,i++ 居然還有執行緒安全問題?

只能說自己瞭解的不夠多,水平有限

假設場景,1000個執行緒,每個執行緒對共享變數 count 進行 1000 次 ++ 操作

上程式碼

	static int count = 0;
	static CountDownLatch cd1 = new CountDownLatch(1000);
	public static void main(String[] args) throws Exception{
		CountRunnable countRunnable = new CountRunnable();
		for (int i = 0; i <1000; i++) {
			new Thread(countRunnable).start();
		}
		cd1.await();
		System.out.println(count);
	}
	static class CountRunnable implements Runnable{
		private void count(){
			for (int i = 0; i < 1000; i++) {
				count++;
			}
		}
		@Override
		public void run(){
			count();
			cd1.countDown();
		}
	}

第一次輸出結果:995904

第一次輸出結果:997097

第一次輸出結果:999681

第一次輸出結果:995186

第一次輸出結果:998063

......

期望的結果應該是 1000000,但執行 N 遍,發現總是不為 1000000

所以,i++ 操作它不是執行緒安全的

每個執行緒都有自己的工作記憶體,每個執行緒需要對共享變數操作時必須先把共享變數從主記憶體 load 到自己的工作記憶體,等完成對共享變數的操作時再 save 到主記憶體。

問題就出在這了,如果一個執行緒運算完後還沒刷到主記憶體,此時這個共享變數的值被另外一個執行緒從主記憶體讀取到了,這個時候讀取的資料就是髒資料了,它會覆蓋其他執行緒計算完的值。。。

這也是經典的記憶體不可見問題,那麼把 count 加上 volatile 讓記憶體可見是否能解決這個問題呢? 答案是:不能。因為 volatile 只能保證可見性,不能保證原子性。多個執行緒同時讀取這個共享變數的值,就算保證其他執行緒修改的可見性,也不能保證執行緒之間讀取到同樣的值然後相互覆蓋對方的值的情況。

解決方案

對 i++ 操作的方法加同步鎖,同時只能有一個執行緒執行 i++ 操作;

如圖所示



相關推薦

java2說說那些

最近,做題的時候經常遇到自增的問題,就此總結整理一下一、常見情況自增方式有兩種:前置和後置,即i++和++i,它們的不同點在於i++是在程式執行完畢後進行自增,而++i是在程式開始執行前進行自增,示例如下:public static void main(String[] ar

MongoDB2刪改操作

db nosql mongo 增刪改 curd 附加命令:1、進入前端操作命令./mongo [ip:端口]說明:默認會自動選本地,端口270172、顯示所有的庫> show dbs; 或者 show databases;3、選擇庫> use 庫名;4、顯示庫所有的集合&g

Java】DateUtil2

繼承 ava sim pla bool private throw ons tar import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat;

暑假自學JAVA Web心得2

聲明 代碼 請求 區別 處理請求 nbsp 編譯器 心得 最終 3.JSP腳本 1.JSP中應用代碼片段 格式:<% Java代碼或是腳本代碼 %> 在頁面請求處理 期間被執行。通過java代碼可以定義變量或是流程控制語句,通過腳本代碼可以應用JSP的內置對象

java入門學習2—基本數據類型

堆內存 類指針 erl 相互 lean 就是 沒有初始化 true ++ 1、變量:定義變量:【數據類型】 變量名 = 賦值(這樣定義的變量一般屬於局部變量,放置在棧內存中); 2、標識符:可以有字母(可以使任意文字),數字,下劃線,$等組成;但是不能以數字開頭,不能是保留

java基礎筆記2----流程控制

特性 byte 增加 基本 執行 size 判斷 efault 跳轉 java流程控制結構包括順序結構,分支結構,循環結構。 順序結構: 程序從上到下依次執行,中間沒有任何判斷和跳轉。 代碼如下: package c

Angularjs進階筆記2-定義指令中的數據綁定

粘貼 文章 發送 制作 取值 不同的 過多 簡單 也會 Angularjs進階筆記(2)-自定義指令中的數據綁定 有關自定義指令的scope參數,網上很多文章都在講這3種綁定方式實現的效果是什麽,但幾乎沒有人講到底怎麽使用,本篇希望聊聊到底怎麽用這個話題。 一. 自定義

Java筆試題2

spl res new void scan system.in lin () static /** * 一個物體從高h處下落,下落後會反彈到離地面高上一次下降高度的1/2,求當第m次接觸地面時走過路徑的長度 * 輸入: * 100,1

「深入Java虛擬機2」:Class類文件結構

1.5 trac 三種 type 類構造 face 方法 class throw Java是與平臺無關的語言,這得益於Java源代碼編譯後生成的存儲字節碼的文件,即Class文件,以及Java虛擬機的實現。不僅使用Java編譯器可以把Java代碼編譯成存儲字節碼的Class

Java語法糖2:自動裝箱和自動拆箱

eth 空指針 lang 指針 反編譯 class path load pointer 自動拆箱和自動裝箱 Java為每種基本數據類型都提供了對應的包裝器類型。舉個例子: public class TestMain{public static void main(Strin

Java基礎部分2

Java語言中常用的類和方法   方法格式 修飾符 返回值型別 方法名(引數型別 引數名1,引數型別 引數名2...){ 函式體; return 返回值; } //具體例項 public static void main(String[] args){ System

Java併發程式設計2:執行緒中斷含程式碼

使用interrupt()中斷執行緒當一個執行緒執行時,另一個執行緒可以呼叫對應的Thread物件的interrupt()方法來中斷它,該方法只是在目標執行緒中設定一個標誌,表示它已經被中斷,並立即返回。這裡需要注意的是,如果只是單純的呼叫interrupt()方法,執行緒並沒有實際被中斷,會繼續往下執行。

Java詳解2--JDK安裝與環境變數配置

JDK安裝與環境變數配置 ---------------------  作者:文動天下 來源:CSDN  連結:https://blog.csdn.net/li_yi_kun?t=1 版權宣告:本文為博主原創文章,轉載請附上博文連結! 1、JDK&nb

集合類淺2

雙列集合: -------------| Map  如果是實現了Map介面的集合類,具備的特點: 儲存的資料都是以鍵值對的形式存在的,鍵不可重複,值可以重複。 ----------------| HashMap  底層也是基於雜湊表實現 的。 Hash

Java原始碼系列2:Iterable介面

對於以陣列形式儲存的多條資料,我們通常是用下表index來遍歷陣列,或進行相關操作,結構如下: 對於以連結串列形式儲存的多條資料,我們通常是用指標next來遍歷陣列,或進行相關操作,結構如下: 這主要是由他們的資料結構決定的,陣列是一塊連續的空間儲存,而連結串列則不是連

Java學習隨筆2--爬蟲--天氣預報

public class Spiderweather { public static void main(String[] args) { List<String> list = null; BufferedReader bufr = null; BufferedWriter bufw = nul

Ehcache學習筆記2--定義ehcache工具類

二:自定義EhcacheUtils 1、CacheUtils package cn.kexq.commons.utils; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.eh

Java面試題2

Java面試題(1)見:https://blog.csdn.net/Number_oneEngineer/article/details/83412521 相關概念 面向物件的三個特徵 封裝,繼承,多型,這個應該是人人皆知,有時候也會加上抽象。 多型的好處 允許不同類物件

負載均衡演算法---Java簡單實現2

上一篇介紹了負載均衡的輪詢,隨機,跟hash演算法,這邊我們一起了解下,加權的輪詢以及加權的隨機。其實理解好了輪詢跟隨機演算法,再加權的話其實是差不多的。 看面通過程式碼來了解: (1)為了不重複建立一個server列表,我們先建立一個共有的server列表,如下: pu

JAVA高階基礎2---Collection

Collection Collection介面成員方法         boolean add(E e)         boolean remo