Java基礎(chǔ)-并發(fā)編程-synchronized關(guān)鍵字使用與原理解析

Java工程師知識(shí)樹(shù) / Java基礎(chǔ)


synchronized的使用

JDK針對(duì)共享資源數(shù)據(jù)同步問(wèn)題有一種方式為使用synchronized關(guān)鍵字谒臼,synchronized提供了一種排他鎖機(jī)制例隆,可以讓程序在同一時(shí)間段內(nèi)只有一個(gè)線程執(zhí)行某些操作。

使用synchronized修飾執(zhí)行內(nèi)容后:

package com.thread.study;

public class TicketWindow implements Runnable {

    public static int TICKET_NUM = 10;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (TICKET_NUM > 0) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "賣了票號(hào)為" + TICKET_NUM-- + "的票");
                } else {
                    return;
                }
            }
        }
    }

    public static void main(String[] args) {

        TicketWindow ticketWindow = new TicketWindow();

        Thread t1 = new Thread(ticketWindow, "1號(hào)售票窗口");
        Thread t2 = new Thread(ticketWindow, "2號(hào)售票窗口");
        Thread t3 = new Thread(ticketWindow, "3號(hào)售票窗口");

        t1.start();
        t2.start();
        t3.start();

    }
}
// 執(zhí)行結(jié)果
1號(hào)售票窗口賣了票號(hào)為10的票
3號(hào)售票窗口賣了票號(hào)為9的票
2號(hào)售票窗口賣了票號(hào)為8的票
2號(hào)售票窗口賣了票號(hào)為7的票
2號(hào)售票窗口賣了票號(hào)為6的票
2號(hào)售票窗口賣了票號(hào)為5的票
2號(hào)售票窗口賣了票號(hào)為4的票
2號(hào)售票窗口賣了票號(hào)為3的票
2號(hào)售票窗口賣了票號(hào)為2的票
2號(hào)售票窗口賣了票號(hào)為1的票

將synchronized改為修改run()方法:

package com.thread.study;

public class TicketWindow implements Runnable {

    public static int TICKET_NUM = 10;

    @Override
    public synchronized void run() {
        while (true) {
            if (TICKET_NUM > 0) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "賣了票號(hào)為" + TICKET_NUM-- + "的票");
            } else {
                return;
            }
        }
    }

    public static void main(String[] args) {

        TicketWindow ticketWindow = new TicketWindow();

        Thread t1 = new Thread(ticketWindow, "1號(hào)售票窗口");
        Thread t2 = new Thread(ticketWindow, "2號(hào)售票窗口");
        Thread t3 = new Thread(ticketWindow, "3號(hào)售票窗口");

        t1.start();
        t2.start();
        t3.start();

    }
}
//執(zhí)行結(jié)果 不管執(zhí)行多少次都是
1號(hào)售票窗口賣了票號(hào)為10的票
1號(hào)售票窗口賣了票號(hào)為9的票
1號(hào)售票窗口賣了票號(hào)為8的票
1號(hào)售票窗口賣了票號(hào)為7的票
1號(hào)售票窗口賣了票號(hào)為6的票
1號(hào)售票窗口賣了票號(hào)為5的票
1號(hào)售票窗口賣了票號(hào)為4的票
1號(hào)售票窗口賣了票號(hào)為3的票
1號(hào)售票窗口賣了票號(hào)為2的票
1號(hào)售票窗口賣了票號(hào)為1的票

通過(guò)上述兩個(gè)例子對(duì)比,總結(jié)synchronized的使用:

  1. 由于synchronized關(guān)鍵字存在排他性,也就是說(shuō)所有的線程必須串行地經(jīng)過(guò) synchronized保護(hù)的共享區(qū)域,如果synchronized作用域越大,則代表著其效率越低,甚至還會(huì)喪失并發(fā)的優(yōu)勢(shì)疲酌。
  2. synchronized關(guān)鍵字應(yīng)該盡可能地只作用于共享資源(數(shù)據(jù))的讀寫作用域,或者說(shuō)是synchronized鎖的是有增刪改操作的對(duì)象了袁。eg:
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
    new Thread(() -> {
        synchronized (list) {//synchronized鎖的是有增刪改操作的對(duì)象
            list.add(Thread.currentThread().getName());
        }
    }, String.valueOf(i)).start();
}
try {
    Thread.sleep(100);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println(list.size());

synchronized 關(guān)鍵字原理

synchronized說(shuō)明:

synchronized 關(guān)鍵字是解決共享資源數(shù)據(jù)同步的常用解決方案朗恳,有以下三種使用方式:

  • 同步普通方法,鎖的是當(dāng)前對(duì)象载绿。
  • 同步靜態(tài)方法粥诫,鎖的是當(dāng)前 Class 對(duì)象。
  • 同步塊崭庸,鎖的是 {} 中的對(duì)象怀浆。

synchronized 實(shí)現(xiàn)原理:

JVM 是通過(guò)進(jìn)入、退出對(duì)象監(jiān)視器( Monitor )來(lái)實(shí)現(xiàn)對(duì)方法怕享、同步塊的同步的执赡。

具體實(shí)現(xiàn)是在編譯之后在同步方法調(diào)用前加入一個(gè) monitor.enter 指令,在退出方法和可能發(fā)生異常處插入 monitor.exit 的指令函筋。monitor指令是使用C++實(shí)現(xiàn)的沙合。

其本質(zhì)就是對(duì)一個(gè)對(duì)象監(jiān)視器( Monitor )進(jìn)行獲取,而這個(gè)獲取過(guò)程具有排他性從而達(dá)到了同一時(shí)刻只能一個(gè)線程訪問(wèn)的目的跌帐。

而對(duì)于沒(méi)有獲取到鎖的線程將會(huì)阻塞到方法入口處首懈,直到獲取鎖的線程 monitor.exit 之后才能嘗試?yán)^續(xù)獲取鎖绊率。

synchronized 特性:

  • 互斥性(確保線程互斥的訪問(wèn)同步代碼)
  • 可見(jiàn)性(保證共享變量的修改能夠及時(shí)可見(jiàn))
  • 有序性(有效解決重排序問(wèn)題)
互斥性

1.互斥性,可以認(rèn)為獨(dú)享的意思究履,每次只允許一個(gè)操作者擁有共享資源滤否;
2.被synchronized修飾的代碼塊、實(shí)例方法最仑、靜態(tài)方法藐俺,多線程并發(fā)訪問(wèn)時(shí),只能有一個(gè)線程獲取到鎖泥彤,其它線程都處于阻塞等待欲芹,但在此期間,這些線程仍然可以訪問(wèn)其它非synchronized修飾的方法全景;

可見(jiàn)性

1.可見(jiàn)性,就是每次線程的到來(lái)牵囤,都能訪問(wèn)到最新的值爸黄;
2.因?yàn)樵诨コ庑缘幕A(chǔ)上,由于每次僅有一個(gè)線程執(zhí)行臨界區(qū)的代碼揭鳞,因此其修改的任何變量值對(duì)于稍后執(zhí)行該臨界區(qū)的線程來(lái)說(shuō)是可見(jiàn)的炕贵;
3.因?yàn)榛コ庑缘拇嬖冢脖WC了臨界區(qū)變量修改的原子性野崇,而volatile僅僅只能保證變量修改的可見(jiàn)性称开,并不能保證原子性;

有序性

1.有序性乓梨,就是按照順序來(lái)執(zhí)行鳖轰;
2.同樣因?yàn)樵诨コ庑缘幕A(chǔ)上,代碼塊也好扶镀,實(shí)例方法或靜態(tài)方法也好蕴侣,一旦被synchronized后,各個(gè)線程相互競(jìng)爭(zhēng)臭觉,反正每次只能有一個(gè)線程執(zhí)行昆雀;
3.打個(gè)比方,舉例靜態(tài)方法蝠筑,TestSynchronized.java 中有個(gè)靜態(tài) synchronized static test(){ i++, j++} 方法狞膘,并且代碼塊被synchronized修飾,讓N個(gè)線程都去調(diào)用這個(gè)方法什乙,最后會(huì)發(fā)現(xiàn)每次i和j的輸出值都是一樣的挽封。i++和j++要么一起執(zhí)行完,要么都不執(zhí)行臣镣,不會(huì)出現(xiàn)先i++后场仲,執(zhí)行了其他代碼和悦,過(guò)一會(huì)再執(zhí)行j++的情況。

流程圖如下:

同步代碼塊

public class Synchronize{
    public static void main(String[] args) {
        synchronized (Synchronize.class){
            System.out.println("Synchronize");
        }
    }
}
---------使用 javap-c 編譯 Synchronize類 可以查看編譯之后的具體信息渠缕。-----------
{
  public com.thread.study.Synchronize();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class com/thread/study/Synchronize
         2: dup
         3: astore_1
         4: monitorenter    //同步方法調(diào)用前加入一個(gè) monitor.enter 指令
         5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #4                  // String Synchronize
        10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit     //退出的地方插入 monitor.exit 的指令
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit     //異掣胨兀可能出現(xiàn)的地方插入 monitor.exit 的指令 確保可以正常退出
        21: aload_2
        22: athrow
        23: return
      Exception table:
         from    to  target type
             5    15    18   any
            18    21    18   any
      LineNumberTable:
        line 5: 0
        line 6: 5
        line 7: 13
        line 8: 23
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 18
          locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "Synchronize.java"

可以看到在同步塊的入口和出口分別有 monitorenter,monitorexit指令亦鳞。

monitorenter指令JVM規(guī)范翻譯:
? 每個(gè)對(duì)象有打自娘胎出來(lái)就自帶一個(gè)內(nèi)置監(jiān)視器鎖(monitor)馍忽。當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán)燕差,過(guò)程如下:
? ? 如果monitor的進(jìn)入數(shù)為0遭笋,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1徒探,該線程即為monitor的所有者瓦呼。
? ? 如果線程已經(jīng)占有該monitor,只是重新進(jìn)入测暗,則進(jìn)入monitor的進(jìn)入數(shù)加1.
? ? 如果其他線程已經(jīng)占用了monitor央串,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0碗啄,再重新嘗試獲取monitor的所有權(quán)。

monitorexit指令JVM規(guī)范翻譯:
? ? 執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者稚字。
? ? 指令執(zhí)行時(shí)饲宿,monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0胆描,那線程退出monitor瘫想,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)昌讲。

同步方法

package com.thread.study;

public class TicketThread {

    public static void main(String[] args) {
        TicketRunnable ticketRunnable = new TicketThread.TicketRunnable();
        Thread t1 = new Thread(ticketRunnable,"zhao");
        Thread t2 = new Thread(ticketRunnable,"qian");
        t1.start();
        t2.start();
    }

    static class TicketRunnable implements Runnable{
        private int TICKET_NUM = 10;
        @Override
        public synchronized void run() {  // 同步方法
            if (TICKET_NUM > 0) {
                System.out.println(Thread.currentThread().getName() +TICKET_NUM);
                TICKET_NUM --;
            }
        }

    }
}
----------使用 javap-c 編譯 TicketThread 可以查看編譯之后的具體信息殿托。主要看內(nèi)部類TicketRunnable------------

{
  com.thread.study.TicketRunnable();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 16: 0

  public synchronized void run();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED     // ACC_SYNCHRONIZED標(biāo)志
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #2                  // Field TICKET_NUM:I
         3: ifle          45
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: new           #4                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        16: invokestatic  #6                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        19: invokevirtual #7                  // Method java/lang/Thread.getName:()Ljava/lang/String;
        22: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        25: getstatic     #2                  // Field TICKET_NUM:I
        28: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        31: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        37: getstatic     #2                  // Field TICKET_NUM:I
        40: iconst_1
        41: isub
        42: putstatic     #2                  // Field TICKET_NUM:I
        45: return
      LineNumberTable:
        line 20: 0
        line 21: 6
        line 22: 37
        line 24: 45
      StackMapTable: number_of_entries = 1
        frame_type = 45 /* same */

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        10
         2: putstatic     #2                  // Field TICKET_NUM:I
         5: return
      LineNumberTable:
        line 17: 0
}
SourceFile: "TicketThread.java"

可以看出在synchronized修飾的同步方法的flags中會(huì)有ACC_SYNCHRONIZED標(biāo)識(shí)

ACC_SYNCHRONIZED指令JVM規(guī)范翻譯:
? ? 方法級(jí)的同步是隱式的。同步方法的常量池中會(huì)有一個(gè)ACC_SYNCHRONIZED標(biāo)志剧蚣。
? ? 當(dāng)某個(gè)線程要訪問(wèn)某個(gè)方法的時(shí)候支竹,會(huì)檢查是否有ACC_SYNCHRONIZED,
? ? 如果有設(shè)置鸠按,則需要先獲得監(jiān)視器鎖礼搁,然后開(kāi)始執(zhí)行方法,方法執(zhí)行之后再釋放監(jiān)視器鎖目尖。
? ? 這時(shí)如果其他線程來(lái)請(qǐng)求執(zhí)行方法馒吴,會(huì)因?yàn)闊o(wú)法獲得監(jiān)視器鎖而被阻斷住。
? ? 值得注意的是,如果在方法執(zhí)行過(guò)程中饮戳,發(fā)生了異常豪治,并且方法內(nèi)部并沒(méi)有處理該異常,那么在異常被拋到方法外面之前監(jiān)視器鎖會(huì)被自動(dòng)釋放扯罐。

無(wú)論是monitorenter负拟、 monitorexit,或者是ACC_SYNCHRONIZED歹河,其都是基于Monitor機(jī)制實(shí)現(xiàn)的掩浙。

Monitor

monitor直譯過(guò)來(lái)是監(jiān)視器的意思,專業(yè)一點(diǎn)叫管程秸歧。Monitor機(jī)制一個(gè)重要特點(diǎn)是厨姚,在同一時(shí)間,只有一個(gè)線程/進(jìn)程能進(jìn)入monitor所定義的臨界區(qū)键菱,這使得monitor能夠實(shí)現(xiàn)互斥的效果谬墙。無(wú)法進(jìn)入monitor的臨界區(qū)的進(jìn)程/線程,應(yīng)該被阻塞经备,并且在適當(dāng)?shù)臅r(shí)候被喚醒拭抬。

java則基于monitor機(jī)制實(shí)現(xiàn)了它自己的線程同步機(jī)制,就是synchronized內(nèi)置鎖弄喘。

基本元素
  • 臨界區(qū)

臨界區(qū)是被synchronized包裹的代碼塊玖喘,可能是個(gè)代碼塊甩牺,也可能是個(gè)方法蘑志。

  • monitor對(duì)象和鎖

monitor對(duì)象是monitor機(jī)制的核心,它本質(zhì)上是jvm用c語(yǔ)言定義的一個(gè)數(shù)據(jù)類型贬派。對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)保存了線程同步所需的信息急但,比如保存了被阻塞的線程的列表,還維護(hù)了一個(gè)基于mutex的鎖搞乏,monitor的線程互斥就是通過(guò)mutex互斥鎖實(shí)現(xiàn)的波桩。

  • 條件變量

條件變量和下方wait signal方法的使用有密切關(guān)系 。在獲取鎖進(jìn)入臨界區(qū)之后请敦,如果發(fā)現(xiàn)條件變量不滿足使用wait方法使線程阻塞镐躲,條件變量滿足后signal喚醒被阻塞線程。 tips:當(dāng)線程被signal喚醒之后侍筛,不是從wait那繼續(xù)執(zhí)行的萤皂,而是重新while循環(huán)一次判斷條件是否成立

  • 定義在monitor對(duì)象上的wait,signal操作
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匣椰,一起剝皮案震驚了整個(gè)濱河市裆熙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖入录,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛤奥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡僚稿,警方通過(guò)查閱死者的電腦和手機(jī)凡桥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贫奠,“玉大人唬血,你說(shuō)我怎么就攤上這事』秸福” “怎么了拷恨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谢肾。 經(jīng)常有香客問(wèn)我腕侄,道長(zhǎng),這世上最難降的妖魔是什么芦疏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任冕杠,我火速辦了婚禮,結(jié)果婚禮上酸茴,老公的妹妹穿的比我還像新娘分预。我一直安慰自己,他們只是感情好薪捍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布笼痹。 她就那樣靜靜地躺著,像睡著了一般酪穿。 火紅的嫁衣襯著肌膚如雪凳干。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天被济,我揣著相機(jī)與錄音救赐,去河邊找鬼。 笑死只磷,一個(gè)胖子當(dāng)著我的面吹牛经磅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钮追,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼预厌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了畏陕?” 一聲冷哼從身側(cè)響起配乓,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后犹芹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體崎页,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年腰埂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了飒焦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屿笼,死狀恐怖牺荠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驴一,我是刑警寧澤休雌,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站肝断,受9級(jí)特大地震影響杈曲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胸懈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一担扑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趣钱,春花似錦涌献、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绞灼,卻和暖如春利术,著一層夾襖步出監(jiān)牢的瞬間呈野,已是汗流浹背低矮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留被冒,地道東北人军掂。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像昨悼,于是被迫代替她去往敵國(guó)和親蝗锥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容