アートのJava並行プログラミング - 読書概要

要約と誘導「の分野でJavaの同時実行」を読んだ後。質問があれば、私を修正してください。

第1章並行プログラミングの課題

1.1コンテキストの切り替え

1.タスクは、次の時間に戻ってタスクに切り替えるために、あなたは、タスクの状態を読み込むことができ、タスクの状態に切り替える前に保存されます。だから、タスクは、リロードのプロセスからコンテキストスイッチを保存することです。

2.どのようにコンテキストスイッチを軽減するには?

  • ロックフリー並行プログラミング

  • CASアルゴリズム

  • 最小スレッドに

  • 使用コルーチン:マルチタスクスケジューリングは、単一のスレッドで実行され、単一のスレッドで複数のタスク間の切り替えを維持します

1.2デッドロック

1.どのようにデッドロックを回避するには?

  • スレッドがより多くのロックを取得しないでください

  • より多くのリソースを取る再びスレッドロックを避けるため、一つのスレッドだけがリソースを占有することを確実にしてみてください

  • 代わりに、内部ロック機構を使用するlock.tryLock(タイムアウト)を使用して、タイムロックを使用してみてください

  • データベースのロックのために、データベース接続をロックおよびロック解除がでなければならない、そうでない状況が失敗した表示されます

実装の原則を基礎となる第2章のJava並行処理の仕組み

2.1揮発性のアプリケーション

  1. 揮発性は、軽量彼は複数の同時プロセッサ内の変数を共有することを保証するために、同期されている「可視性」フィールドがvolatileとして宣言されている場合、Javaのスレッドのメモリモデルは、すべてのスレッドがこの変数の値が同じであることを保証参照します。

  2. これは、コンテキストの切り替えやスケジューリングが発生することはありません。

  3. 2の原則の揮発性の実現

  • プロセッサキャッシュ原因ロックプレフィックス命令は、メモリに背中を書かれています
  • プロセッサのキャッシュメモリは、他のプロセッサのキャッシュが無効である原因に書き戻されます

同期2.2

同期を達成する1.synchronized基準:Javaの各オブジェクトは、ロックとして使用することができます。

2.三つの形式:

  • 通常の同期方法のために、ロックオブジェクトの現在のインスタンスです。
  • 静的同期方法のために、ロックが現在のクラスのクラス・オブジェクトです。
  • 同期コードブロックのために、オブジェクトロックが同期ブラケットに構成されています。

比較2.2.2アップグレードおよびロック

  1. いいえロック状態ません - 「偏ったロック - 」軽量ロック - 「ヘビー級のロック。ロックだけダウングレードすることはできませんアップグレードすることができます。

  2. 閉じる遅延をロックバイアス(通常は偏っロックは、プログラムが数秒を開始した後にのみアクティブになります)

-XX:BiasedLockingStartupDelay = 0;

  1. 閉じる偏っロック(軽量ロックのプログラムを入力した後、デフォルトの状態)

-XX:UseBiasedLocking = false;

  1. 軽量ロック解除は、ロックは、ロック競合、ヘビーロックの膨張の背面にロックの現在の存在を示す、失敗します。

5.ロック長所と短所:

  • 利点
  1. バイアスロック:ロックと追加消費のロックを解除しないが、ナノ秒だけ隙間があるとに比べて非同期メソッドを実行します

  2. 軽量ロック:スレッドが手順の応答速度を改善し、競争をブロックしません。

  3. ヘビー級のロック:スピンを使用せずにスレッドの競合、CPUを消費しません。

  • 短所
  1. バイアスロック:スレッドのロック競合がある場合は、消費の追加のロックの失効をもたらすでしょう。

  2. 軽量ロック:スレッドが競合するロックを取得することはまだできない場合は、スピンを使用してCPUを消費します

  3. ヘビー級のロック:スレッドがブロックされ、応答時間の遅さ

  • 該当シーン
  1. バイアスされたロッキング:一つだけスレッドがシンクブロックにアクセスするように適合されています

  2. 軽量ロック:応答時間の追求、非常に高速の実装同期ブロック

  3. ヘビー級のロック:特定のシンクブロックの長い実行速度の追求

2.3 原子操作

  1. どのプロセッサがアトミック操作を実装しますか?

(1)バスキー保証アトミックを使用して、

(2)キャッシュロック保証アトミックを使用して、

2例を除い:

  • 操作データは、プロセッサ内にキャッシュされ、またはプロセッサバス・ロック・コール間のデータのキャッシュ・ラインの複数の動作できない場合
  • 一部のプロセッサは、キャッシュのロックをサポートしていません。
  1. Javaのアトミック操作を実装する方法?

ロック及び環状CAS実装。

(1)CASサイクル運転の原子を用いて

プロセッサ実装によって提供さCMPXCHG命令によってJVMのCAS動作。基本的な考え方は、スピンサイクルCAS CASはこれまでのところ成功した操作を知って達成することです。

三つの原子の(2)CAS操作

  1. ABAの問題

    1. ソリューション:バージョン番号を使用しますが、バージョン番号は、変数の前に追加され、各変数が更新されると、バージョン番号+1
  2. 大きなオーバーヘッド長いサイクル時間

  3. アトミック操作は、共有変数を保証することができます

(3)を達成するためのロック機構を使用して

このメカニズムは、メモリ領域がロックを操作できるようにするロックを獲得するための唯一のスレッドであることを保証します。

第4章Javaの並行プログラミングの基礎

4.1はじめスレッド

4.1.1スレッドとは何ですか

1.最新のオペレーティングシステムのスケジューリングの最小単位は、スレッドとも呼ばれる軽量プロセスですあなたが複数のスレッドプロセスを作成することができ、これらのスレッドは、独自のカウンタ、スタック、スタック財産、およびローカル変数を持ち、共有メモリ変数にアクセスすることができます。

4.1.2なぜ使用マルチスレッディング

  1. 以上のプロセッサコア
  2. より高速な応答時間
  3. より良いプログラミングモデル

4.1.3スレッドの優先順位

  1. スレッドの頻繁に閉塞するためのスレッドが(スリープまたはI / O動作)が高い優先度を設定する必要があり、重点を算出する(CPUがより多くの計算時間または部分を必要とする)プロセッサが排他的ではないことを保証するために、より低い優先度に設定されています。

  2. OSは、優先順位設定のためのJavaスレッドを無視することができますので、スレッドの優先順位は、プログラムの正しさに依存していません。

4.1.4スレッドの状態

4.1.5デーモンスレッド

  1. 主にバックグラウンドプログラムのスケジュールと支援作品として使わ支援型スレッド、;平均、デーモンスレッドは、Java仮想マシンが存在しない場合、Java仮想マシンが終了します

  2. 、スレッドを開始する前に設定する必要性を設定するにはThread.setDarmon(真)。

  3. デーモンスレッドを作成するとき、あなたは最終的にクリーンアップまたはシャットダウンするロジックリソースの実装を確実にするためにブロックの内容に依存することはできません。

4.2開始と終了スレッド

4.2.1建設スレッド

4.2.2スタートスレッド

初期化が完了すると、スレッドを開始することができstart()メソッドを呼び出します。スレッドを開始する前に、スレッドは、スレッド名を設定するのが最適です。

4.2.3が中断理解します

  1. 割り込み:別のスレッドによって実行中のスレッドが動作を中断したかどうかを示す、スレッドフラグの特性として理解されます。他のスレッドの割り込みのようなスレッドは、その割り込み操作()メソッドを呼び出すことにより、割り込みコール、スレッドの他のスレッドを作りました。

  2. 中断されたか否かを判断するためのスレッドが()、あなたはまた、isInterruptedをして、現在のスレッドの割り込みフラグをリセットするための静的メソッドThread.interrupted()を呼び出すことができます。

4.3スレッド間通信

4.3.2待機/通知メカニズム

コール待機()、通知()、のnotifyAll()注:

  • 私たちは、オブジェクトのロックを呼び出すために使用する必要があります。
  • wait()を呼び出した後、スレッドの状態を待つ、現在のスレッドとオブジェクトのキューに配置を実行して変更します。
  • 待機からのスレッドに戻るチャンスを待った後、通知とのnotifyAllする、呼び出し元のスレッドのリリースでは、ロックの必要性を、通知のnotifyAll呼び出しの後、まだ待っているスレッドが待機から復帰しません。
  • 通知、のnotifyAll同期キュー、スレッド状態の変化によって移動されるがブロックされた待ち行列に移動するスレッドを待っています
  • 前提待ちメソッドが返すから、呼び出し元のオブジェクトは、ロックを取得することです

4.3.3待機古典的なパラダイム/通知

パーティーのために待って:

  1. オブジェクトのロックを取得
  2. 条件が満たされない場合は、まだ条件をチェックし、通知された後、オブジェクトの待機()メソッドを呼び出します
  3. 論理条件を対応する実行され満足しています
  4. 擬似コード
synchronized(对象) {
    while(条件不满足) {
        对象.wait();
    }
    // 对应的逻辑处理 
}

パーティーに通知:

  1. オブジェクトのロックを取得するには
  2. 変更の条件
  3. オブジェクトのスレッド上のすべての待機を通知
  4. 擬似コード
synchronized(对象) {    
    改变条件    
    对象.notifyAll(); 
} 

4.3.4入力および出力フローダクト

4.3.5 Thread.join()を使用

スレッドAは、()文をthread.join実行した場合、意味がある:スレッド待機しているスレッドの後に現在のスレッドがthread.joinから()が復帰を終了します。スレッドのスレッドを提供することに加えてjoin()メソッドは、(長いミル)に参加し、(長いミル、INT nanos値)は、2つのタイムアウト方法を含むが参加設けられています。

4.4ねじ適用例

4.4.3スレッドプールとその例

スレッドプール:以前のスレッドのいくつかの数、スレッドの作成を作成し、ユーザによって直接制御することができない、ミッションを完了するには、このコンテキストでは、スレッドの固定または比較的一定の繰り返し数を使用これの利点は、一方では、スレッド・システム・リソースのオーバーヘッドの頻繁なスレッドの作成と終焉をなくし、一方で、顔の過剰摂取のタスクは、漸進的な劣化を提出することができます

5章Javaでロック

5.1ロックインタフェース

  1. その差を同期:
  • 暗黙的な取得不足しているロックを解放する(または同期化ブロックによって提供される方法によって)都合。
  • ロック取得と操作性の解放を持っています。割り込み可能ロック取得タイムアウトとsynchronizedキーワードのロックおよび他の同期機能を得るには使用できません。
Lock lock = new ReentrantLock(); 
lock.lock(); 
try {    
} finally {    
    lock.unlock(); 
} 
  1. 最終的にはロックを解除した後、ロックを取得することを確認し、そして最終的にリリースされます

  2. 買収はロックの時に発生したかのように、tryブロックの作成プロセスでロックされません(カスタム実装がロック)異常、例外がスローされ、それはまた、理由もなくロックの解除につながります

4.特徴:

  • ロックを取得しようとする試みを非はブロッキング:現在のスレッドがロックを獲得するために、ロックが正常に取得し、ロックを保持することを別のスレッドによって獲得されていないこの時点場合。
  • ロックを取得するために中断されます。
  • ロックを取得タイムアウト:

5.2キューシンクロナイザ

AbstractQueuedSynchronizer(AQS) - シンクロナイザー

主な用途同期は継承シンクロナイザによって、サブクラスを継承し、同期状態を管理するための抽象メソッドを実装し、必然的に、あなたが提供するシンクロナイザを使用する必要がある、抽象メソッドの実装プロセスで同期状態に変更を加える必要がありますされます動作させるための三つの方法。シンクロナイザは、同期状態への排他的アクセスの両方が、同期成分(ReentrantLockの、ReentrantReadWriteLock、たCountDownLatch)の異なるタイプの実現を容易にするように、同期状態への共有アクセスをサポートすることができるサポート

5.3リエントラントロック

ロックを取得する際のロックは、リソースのスレッドロックの重複をサポートすることが可能であることを示し、加えて、公正かつ不当な選択をサポートしています。

公平なアクセスのロック:ロックへの最長待機しているスレッドの優先アクセスです。

5.4読み書きロック

5.5 LockSupportツール

5.6条件インタフェース

第6章Javaの同時コンテナやフレームワーク

原理と応用6.1 ConcurrentHashMapの実装

スレッドセーフで効率的にHashMap

6.1.1は、なぜあなたは使用しますか?

並行プログラミングでの使用HashMapのは、無限ループを引き起こす可能性があり、使用のスレッドセーフなハッシュテーブルは非効率的

  1. スレッドセーフなのHashMap

マルチスレッドは、プット操作が解決するために、100%のCPU使用率で、その結果、無限ループが発生します。エントリーリスト環状データ構造を形成するHashMapのリード。

  1. 非効率的なハッシュテーブル

セキュリティスレッドを確保するために同期しますが、スレッド競争力のあるインセンティブや非効率的な使用。

  1. ConcurrentHashMapのロック・セグメントの同時アクセス技術を効果的に速度を高めることができます

まず、データによってセグメントに格納され、且つにロック付きデータの各部分、エンドロックアクセスデータのときに一つのスレッドが成立し、他のデータ・セグメントは、他のスレッドがアクセスすることができます。

6.1.2 ConcurrentHashMapの構造

セグメントデータ構造のConcurrentHashMap HashEntryとデータ構造です。セグメントは繰り返しロックされ、ロックの役割、鍵データを格納するためのHashEntry

A ConcurrentHashMapの各HashEntryにリンクされたリスト構造の要素であり、セグメントAセグメントのアレイはHashEntry配列を含ん含ま、HashEntry配列要素を守って、各セグメントの配列HashEntryの要素を変更するとき、それは最初に取得しなければなりません彼の対応するセグメントロック付き

図クラスのConcurrentHashMap
図の設定のConcurrentHashMap

6.1.3 ConcurrentHashMapの初期化

  1. アレイの初期化セグメント

  2. 初期segmentShiftとsegmentMask

    1. 16までsegmentShift
    2. 65535までsegmentMask
  3. 各セグメントを初期化します

6.1.4位置設定セグメント

ロックセグメントセグメントためConcurrentHashMapの要素の挿入は、第1のハッシュアルゴリズムを介してセグメントを見つけなければならない場合、データの異なるセグメントを保護するために使用され、。

再ハッシュ目的:要素が均一それによってアクセス容器の効率を向上させる、異なるセグメントに分散させることができるように、ハッシュ衝突を低減します。

ポジショニングセグメント

最後のセグメント<K、V> segmentFor(INTハッシュ){戻りセグメント[(ハッシュ>>> segmentShift)&segmentMask]。}

6.1.5 ConcurrentHashMapの操作

  1. get操作

public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key,hash); }

整个过程不用加锁,除非读到空值才会加锁重读。

原因: get 方法里将要使用到的共享变量都定义为 volatile 类型,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get 操作里只需要读不需要写共享变量 count 和 value,所以可以不用加锁。

( hash >>> segmentShift ) & segmentMask // 定位 Segment 使用的 hash 函数 int index = hash & (tab.length - 1) // 定位 HashEntry 使用的 hash 函数

  1. put 操作

要写数据,在操作共享变量时,一定要加锁

两个步骤:

  • 判断是否需要对 Segment 里的 HashEntry 数组进行扩容
  • 定位添加元素的位置,然后将其放在 HashEntry 数组里

(1)是否需要扩容

在插入元素前,先判断 Segment 里的 HashEntry 数组是否超过阈值(threshold),若超过,就对 HashEntry 数组扩容。(HashMap 是在插入后对判断是否已经达到容量,再进行扩容)

(2)如何扩容

创建一个容量为原来2倍的数组,然后将原数组里面的元素进行再散列后插入到新的数组里面。ConcurrentHashMap 只对某个 Segment 进行扩容,更高效。

  1. size 操作

先尝试2次通过不锁住 Segment 的方式来统计各个 Segment 大小,如果统计过程中,容器的 count 发生变化,则采用**加锁的方式(把所有的 Segment 的put、get 和clean方法全部锁住)**来统计所有 Segment 的大小。

6.2 ConcurrentLinkedQueue

实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队公用一把锁)或两把锁(出入队用不同锁)的方式来实现。非阻塞的算法可以用循环 CAS 的方式来实现。

ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列,采用 FIFO 的规则对节点进行排序。

6.2.2 入队列

  1. 入队列的过程

将入队节点添加到队列的尾部。入队过程两件事儿:①定位出尾结点;②使用 CAS 算法将入队节点设置成尾结点的 next 节点,如不成功则重试。

  1. 定位尾结点

tail 节点并不总是尾结点,所以每次入队都需要通过 tail 节点来找到尾结点

  1. 设置入队节点为尾结点
  2. HOPS 的设计意图

控制并减少 tail 节点的更新频率,而不是每次节点入队后都将 tail 节点更新成尾结点,而是大于等于常量 HOPS 时才更新,提高了入队的效率。

6.2.3 出队列

也是通过设置 HOPS 来确定什么时候更新 head 节点,提高出队效率。

6.3 Java 中的阻塞队列

6.3.1 什么是阻塞队列

BlockingQueue:

  1. 阻塞插入: 队列满时,阻塞插入元素的线程,直至不满;
  2. 阻塞移除: 队列空时,阻塞移除元素的线程,直至不空。
方法/处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add ( e ) offer ( e ) put ( e ) offer ( e, time, unit)
移除方法 remove ( ) poll ( ) take ( ) poll ( time, unit)
检查方法 element ( ) peek ( ) 不可用 不可用

6.3.2 Java 里的阻塞队列

JDK 7:

  1. ArrayBlockingQueue

数组实现的有界阻塞队列。默认不保证线程访问的公平性。为了保证公平性,可能降低吞吐量。

  1. LinkedBlockingQueue

链表实现的有界阻塞队列。默认和最大长度为 Integer.MAX_VALUE

  1. PriorityBlockingQueue

支持优先级的无界阻塞队列。

  1. DelayQueue

支持延时获取元素的无界阻塞队列。使用优先队列实现

  1. SynchronousQueue

不存储元素的阻塞队列,每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。吞吐量高于 LinkedBlockingQueue 和 ArrayBlockingQueue

  1. LinkedTransferQueue

链表结构的无界阻塞队列

  1. LinkedBlockingDeque

链表结构,双向阻塞。可运用在“工作窃取”模式中。

6.3.3 阻塞队列的实现原理

使用通知模式实现。

6.4 Fork/Join 框架

6.4.1 是什么?

是把一个大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

6.4.2 工作窃取算法

是指从某个线程(这个线程自己的活儿干完了,跑去帮别人干活儿)从其他队列里窃取任务来执行。

使用双端队列,被窃取线程从头部拿任务执行,窃取线程从尾部拿任务执行。

优点:

  • 充分利用线程进行并行运算,减少了线程间的竞争。

缺点:

  • 在某些情况下还是存在竞争,比如队列中只有一个任务时。且该算法会消耗更多的系统资源,比如创建多个线程和多个双端队列。

6.4.3 框架设计

使用两个类完成分割任务,执行任务并合并结果。

①ForkJoinTask:创建ForkJoin 任务

两个子类(用时继承):

  1. RecursiveAction: 用于没有返回结果的任务
  2. RecursiveTask: 用于有返回结果的任务

②ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行。

Chapter 7 Java 中的13个原子操作类

7.1 原子更新基本类型类

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong

7.2 原子更新数组

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
  • AtomicIntegerArray

7.3 原子更新引用类型

  • AtomicReference
  • AtomicReferenceFiledUpdater
  • AtomicMarkableReference

7.4 原子更新字段类

  • AtomicIntegerFiledUpdater
  • AtomicLongFiledUpdater
  • AtomicStampedReference

Chapter 8 Java 中的并发工具类

CountDownLatch、CyclicBarrier和Semphore 并发流程控制;

Exchanger 线程间交换数据

8.1 等待多线程完成的 CountDownLatch

允许一个线程或多个线程等待其他线程完成操作。

CountDownLatch 的构造函数接收一个 int 类型的参数作为计数器,如果你想等待 N 个点完成,这里就传入 N。调用 countDown 方法,N 就减1。CountDownLatch 的 await 方法会阻塞当前线程,直到 N = 0。N 个点,可以是 N 个线程,也可以是1个线程里面的 N 个执行步骤。用在多线程里面,只需要吧 CountDownLatch 的引用传递到线程里即可。

CountDownLatch 不能重新初始化或修改 CountDownLatch 对象的内部计数器的值。

8.2 同步屏障 CyclicBarrier

一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续运行(继续执行的线程没有先后顺序,取决于CPU调度)。

8.2.1 简介

默认构造方法是 CyclicBarrier (int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达屏障,然后当前线程被阻塞。

还有一个高级构造方法 CyclicBarrier (int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行 barrierAction,方便处理更复杂的业务场景。

8.2.3 CyclicBarrier 与 CountDownLatch 的区别

CountDownLatch 的计数器只能使用一次,但是 CyclicBarrier 的计数器可以使用 reset()方法重置,因此 CyclicBarrier 能处理更为复杂的业务场景。

8.3 控制并发线程数的 Semaphore

用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理使用公共资源。

  1. 应用场景

做流量控制,特别是公共资源有限的应用场景,比如数据库连接。

8.4 线程间交换数据的 Exchanger

用于线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以通过 exchange 方法交换彼此的数据。

可以应用于遗传算法

可以应用于校对工作

Chapter 9 Java中的线程池

好处:

  1. 降低资源消耗。
  2. 提高响应速度。
  3. 提高线程的可管理性。

9.1 线程池的实现原理

处理流程中,先判断核心线程池是否已满,满了再判断工作队列是否已满,满了再判断线程池是否已满,满了再执行拒绝策略。

メインの処理フロースレッドプール

ThreadPoolExecutor 执行 execute()方法:

ThreadPoolExecutorの概略実行

四种情况分析:

  1. 若当前运行的线程少于 coorPoolSize,则创建新线程来执行任务(需要获得全局锁,开销大)
  2. 若运行的线程大于等于 coorPoolSize,则将任务加入 BlockingQueue(大多数时候处于这里)
  3. 若 BlockingQueue 队列已满,则创建新的线程来处理任务(需要获得全局锁,开销大)
  4. 若创建新线程将使当前运行的总线程数超出 maximumPoolSize,那么执行拒绝策略。

工作线程: 线程池创建线程时,会将线程封装成工作线程 Worker,Worker在执行完任务之后还会循环获取工作队列里的任务来执行。

9.2 线程池的使用

9.2.1 线程池创建

通过 ThreadPoolExecutor 创建,很多个参数。

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, millseconds, runnableTaskQueue, handler)

9.2.2 向线程池提交任务

execute() // 提交不需要返回值的任务,无法判断任务是否被线程池执行成功 submit() // 提交需要返回值的任务

submit(): 线程池会返回一个 future 类型的对象,通过这个对象可以判断任务是否执行成功,且可以通过 future 的 get 方法获取返回值,get 方法会阻塞当前线程直到任务完成,使用 get(long timeout, TimeUnit unit) 会阻塞一段时间后立即返回,这时任务可能还没有执行完成。

9.2.3 关闭线程池

シャットダウン()またはshutdownNowの()メソッド。

原理は、スレッドプール内のすべてのワーカースレッドをトラバースすることであり、私はタスクが停止しないかもしれない割り込みに応答することはできませんので、その後の一つの方法割り込み1つの呼び出しによって、スレッドを中断します。

通常、スレッドプールをシャットダウンするシャットダウンを使用しています。

質問があれば、私を修正してください!

公開された16元の記事 ウォンの賞賛2 ビュー1278

おすすめ

転載: blog.csdn.net/yx185/article/details/103281607