Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

스레딩 - Java SDK

이 페이지의 내용

  • 명심해야 할 세 가지 규칙
  • 스레드 간 커뮤니케이션
  • 의도
  • 동결된 객체
  • Realm 새로고침
  • Realm의 스레딩 모델 심층 분석
  • Git과 비교 및 대조
  • 내부 구조
  • 요약

Android 앱을 빠르고 응답성이 뛰어나게 만들려면 시각 자료를 배치하고 사용자 상호작용을 처리하는 데 필요한 컴퓨팅 시간과 데이터를 처리하고 비즈니스 로직을 실행하는 데 필요한 시간의 균형을 맞춰야 합니다. 일반적으로 앱 개발자는 모든 사용자 인터페이스 관련 작업을 위한 메인 스레드 또는 UI 스레드, 프레젠테이션을 위해 UI 스레드로 전송하기 전에 더 많은 워크로드를 계산하는 하나 이상의 백그라운드 스레드 등 여러 스레드에 작업을 분산합니다. 많은 작업을 백그라운드 스레드로 오프로드함으로써 UI 스레드는 워크로드의 크기에 관계없이 높은 반응성을 유지할 수 있습니다.

다음 세 가지 규칙을 따르면 Realm에서 간단하고 안전한 멀티스레드 코드를 사용할 수 있습니다.

백그라운드 스레드에서 작성하는 경우 UI 스레드에서 작성하지 않도록 합니다.
모든 스레드에서 영역 에 쓰기 (write) 수 있지만 한 번에 하나의 작성자만 있을 수 있습니다. 따라서 쓰기 (write) 트랜잭션(write transaction)은 서로를 차단 합니다. UI 스레드에 쓰기 (write) 를 수행하면 배경 스레드의 쓰기 (write) 가 완료될 때까지 기다리는 동안 앱 이 응답하지 않는 것처럼 보일 수 있습니다. 동기화 를 사용하는 경우 동기화가 배경 스레드에 쓰는 것처럼 UI 스레드에 쓰지 않도록 합니다.
라이브 객체, 컬렉션 또는 영역을 다른 스레드에 전달하지 않도록 합니다.
라이브 객체, 컬렉션 및 Realm 인스턴스는 스레드에 한정되어 있으므로 해당 인스턴스가 생성된 스레드에서만 유효합니다. 실질적으로 이는 라이브 인스턴스를 다른 스레드로 전달할 수 없음을 의미합니다. 그러나 Realm은 스레드 간에 객체를 공유하기 위한 몇 가지 메커니즘을 제공합니다.
읽기 위해 락하지 않도록 합니다.
Realm의 MVCC(Multiversion Concurrency Control) 아키텍처는 읽기 작업을 잠글 필요가 없습니다. 읽은 값은 절대 손상되거나 부분적으로 수정된 상태가 되지 않습니다. 잠금이나 뮤텍스 없이도 모든 스레드의 Realm에서 자유롭게 읽을 수 있습니다. 각 스레드가 읽기 전에 차례를 기다려야 할 수 있으므로 불필요하게 잠그면 성능 병목 현상이 발생할 수 있습니다.

라이브 객체, 컬렉션 및 Realm은 스레드에 한정되어 있습니다. 여러 스레드에서 동일한 데이터로 작업해야 하는 경우 여러 스레드에서 동일한 Realm을 별도의 Realm 인스턴스로 열어야 합니다. Java SDK 는 가능한 경우 스레드 간의 기본 연결을 통합 하여 이 패턴을 보다 효율적으로 만듭니다.

스레드 간에 통신해야 하는 경우 사용 사례에 따라 여러 가지 옵션이 있습니다.

  • 두 스레드의 데이터를 수정하려면 기본 키 를 사용하여 두 스레드 모두에서 객체를 쿼리합니다.

  • 객체의 빠른 읽기 전용 보기를 다른 스레드로 보내려면 객체를 고정 합니다.

  • 앱에서 객체의 많은 읽기 전용 뷰를 유지하고 공유하려면 영역에서 객체를 복사합니다.

  • 스레드의 변경 사항에 React하려면 알림을 사용하세요.

  • 현재 스레드에서 Realm에 있는 다른 스레드의 변경 사항을 보려면 Realm 인스턴스를 새로 고치 세요(이벤트 루프 스레드는 자동으로 새로 고침).

MANAGED RealmObject 인스턴스는 스레드로부터 안전하지 않거나 Parcelable 아니므로 Intent 를 통해 활동이나 스레드 간에 전달할 수 없습니다. 대신 Intent 추가 번들에서 프라이머리 키 와 같은 ObjectId를 전달한 다음 별도의 스레드에서 새 Realm 인스턴스를 열어 해당 식별자를 쿼리할 수 있습니다. 또는 Realm 객체를 동결 할 수 있습니다.

다음도 참조하세요.

작업 예제는 객체 전달 에서 찾을 수 있습니다. Java SDK 스레딩 예제 의 일부입니다. . 이 예시 에서는 일반적인 RealmObject Android 사용 사례에서 ID를 전달하고 를 조회 하는 방법을 보여줍니다.

스레드에 제한된 라이브 객체는 대부분의 경우 제대로 작동합니다. 그러나 일부 앱(예: 반응형 이벤트 스트림 기반 아키텍처를 기반으로 하는 앱)은 스레드 간에 변경 불가능한 복사본을 전송해야 합니다. 이 경우 객체, collection 및 Realm을 동결 할 수 있습니다.

동결하면 디스크에 여전히 존재하며 다른 스레드로 전달될 때 딥 카피가 필요하지 않은 특정 객체, collection 또는 영역에 대한 변경할 수 없는 뷰가 생성됩니다. 스레드 문제에 대한 걱정 없이 스레드 간에 동결된 객체를 자유롭게 공유할 수 있습니다.

동결된 객체는 라이브 상태가 아니며 자동으로 업데이트되지 않습니다. 이는 사실상 정지 시점의 객체 상태에 대한 스냅샷입니다. 영역을 동결하면 모든 하위 객체와 collection도 동결됩니다. 동결된 객체를 수정할 수는 없지만 동결된 객체에서 기본 키를 읽고 기본 객체에 대한 라이브 영역을 쿼리한 다음 해당 라이브 객체 인스턴스를 업데이트할 수 있습니다.

동결된 객체는 해당 객체를 생성한 영역이 열려 있는 한 유효합니다. 모든 스레드가 해당 동결된 객체에 대한 작업을 완료할 때까지 동결된 객체가 포함된 Realm을 닫지 않도록 합니다.

경고

동결된 객체 예외

동결된 객체로 작업할 때 다음 중 하나를 시도하면 예외가 발생합니다.

  • 동결된 영역에서 쓰기 트랜잭션(write transaction) 열기

  • 동결된 객체 수정

  • 동결된 영역, 컬렉션 또는 객체에 변경 리스너 추가

일단 동결되면 객체의 동결을 해제할 수 없습니다. isFrozen() 를 사용하여 객체가 동결되었는지 확인할 수 있습니다. 이 메서드는 항상 스레드로부터 안전합니다.

객체, 컬렉션 또는 Realm을 동결하려면 동결() 메서드를 사용합니다.

Realm realm = Realm.getInstance(config);
// Get an immutable copy of the realm that can be passed across threads
Realm frozenRealm = realm.freeze();
Assert.assertTrue(frozenRealm.isFrozen());
RealmResults<Frog> frogs = realm.where(Frog.class).findAll();
// You can freeze collections
RealmResults<Frog> frozenFrogs = frogs.freeze();
Assert.assertTrue(frozenFrogs.isFrozen());
// You can still read from frozen realms
RealmResults<Frog> frozenFrogs2 = frozenRealm.where(Frog.class).findAll();
Assert.assertTrue(frozenFrogs2.isFrozen());
Frog frog = frogs.first();
Assert.assertTrue(!frog.getRealm().isFrozen());
// You can freeze objects
Frog frozenFrog = frog.freeze();
Assert.assertTrue(frozenFrog.isFrozen());
// Frozen objects have a reference to a frozen realm
Assert.assertTrue(frozenFrog.getRealm().isFrozen());
val realm = Realm.getInstance(config)
// Get an immutable copy of the realm that can be passed across threads
val frozenRealm = realm.freeze()
Assert.assertTrue(frozenRealm.isFrozen)
val frogs = realm.where(Frog::class.java).findAll()
// You can freeze collections
val frozenFrogs = frogs.freeze()
Assert.assertTrue(frozenFrogs.isFrozen)
// You can still read from frozen realms
val frozenFrogs2 =
frozenRealm.where(Frog::class.java).findAll()
Assert.assertTrue(frozenFrogs2.isFrozen)
val frog: Frog = frogs.first()!!
Assert.assertTrue(!frog.realm.isFrozen)
// You can freeze objects
val frozenFrog: Frog = frog.freeze()
Assert.assertTrue(frozenFrog.isFrozen)
Assert.assertTrue(frozenFrog.realm.isFrozen)

중요

동결된 객체 및 Realm 크기

동결된 객체는 동결된 시점에 해당 객체를 포함하는 Realm의 전체 복사본을 보존합니다. 결과적으로 많은 수의 객체를 동결하면 영역이 동결된 객체가 없는 경우보다 더 많은 메모리와 스토리지를 소비할 수 있습니다. 많은 수의 객체를 장기간 개별적으로 동결해야 하는 경우, 필요한 객체를 Realm 외부로 복사하는 것이 좋습니다.

Realm을 열면 가장 최근에 성공한 쓰기 커밋이 반영되며 새로 고침 할 때까지 해당 버전으로 유지됩니다. 즉, 다음 새로 고침까지 다른 스레드에서 발생한 변경 사항이 영역에 표시되지 않습니다. 모든 이벤트 루프 스레드(UI 스레드 포함)의 Realm은 해당 스레드의 루프가 시작될 때 자동으로 새로 고쳐집니다. 그러나 반복되지 않는 스레드에 연결되거나 자동 새로 고침이 비활성화된 Realm 인스턴스는 수동으로 새로 고침해야 합니다. 영역을 새로 고치려면 Realm.refresh()를 호출합니다.

if (!realm.isAutoRefresh()) {
// manually refresh
realm.refresh();
}
if (!realm.isAutoRefresh) {
// manually refresh
realm.refresh()
}

쓰기 시 새로 고침

Realm은 쓰기 트랜잭션(write transaction)이 완료되면 자동으로 새로 고침됩니다.

Realm은 MVCC(Multiversion Concurrency Control) 를 통해 스레드 전반에 걸쳐 안전하고 빠르며 잠금 없는 동시 액세스를 제공합니다. 아키텍처.

Git 과 같은 분산된 버전 관리 시스템에 익숙한 경우 를 사용하면 이미 MVCC를 직관적으로 이해하고 있을 수 있습니다. Git의 두 가지 기본 요소는 다음과 같습니다.

  • 커밋, 즉 원자성 쓰기입니다.

  • 브랜치, 즉 커밋 기록의 다른 버전입니다.

마찬가지로 Realm은 트랜잭션형태의 원자 단위로 커밋된 쓰기가 있습니다. 또한 Realm은 브랜치처럼 주어진 시간에 다양한 버전의 히스토리를 갖고 있습니다.

포크를 통한 배포와 분산을 적극적으로 지원하는 Git과 달리 영역에는 항상 하나의 최신 버전만 있고 항상 최신 버전의 헤드에 데이터를 기록합니다. Realm은 이전 버전에 쓸 수 없습니다. 이는 일리가 있습니다: 데이터는 하나의 최신 버전으로 수렴되어야 합니다.

영역은 B-트리 데이터 구조를 사용하여 구현됩니다. 최상위 노드는 영역의 버전을 나타냅니다. 하위 노드는 해당 버전의 영역에 있는 객체입니다. 영역은 Git이 HEAD 커밋에 대한 포인터를 갖는 것과 마찬가지로 최신 버전에 대한 포인터를 가지고 있습니다.

Realm은 쓰기 중 복사(copy-on-write) 기술을 사용하여 격리 를 보장합니다. 및 내구성 . 변경을 수행하면 Realm은 쓰기를 위해 트리의 관련 부분을 복사한 다음 두 단계로 변경 사항을 커밋합니다.

  • 변경 사항을 디스크에 쓰고 성공 여부를 확인합니다.

  • 최신 버전 포인터가 새로 작성된 버전을 점 설정합니다.

2단계 커밋 프로세스는 쓰기가 도중에 실패하더라도 트리의 관련 부분 복사본이 변경되었기 때문에 원래 버전이 어떤 식으로든 손상되지 않도록 보장합니다. 마찬가지로 영역의 루트 포인터는 새 버전이 유효하다는 것이 보장될 때까지 원본 버전을 가리킵니다.

예시

다음 다이어그램은 커밋 프로세스를 보여줍니다.

Realm은 쓰기를 위해 트리의 관련 부분을 복사한 다음 포인터를 업데이트하여 최신 버전을 대체합니다.
클릭하여 확대
  1. 영역은 트리 구조로 되어 있습니다. 영역에는 최신 버전인 V1에 대한 포인터가 있습니다.

  2. 기록 시 Realm은 V1을 기반으로 V2 버전을 새로 만듭니다. Realm은 수정을 위해 객체의 복사본을 만들고(A 1, C 1) 수정되지 않은 객체에 대한 링크는 계속해서 원본 버전(B, D)을 가리킵니다.

  3. 커밋을 확인한 후 Realm은 포인터를 새로운 최신 버전인 V2로 업데이트합니다. 그런 다음 Realm은 더 이상 트리에 연결되지 않은 오래된 노드를 버립니다.

Realm은 메모리 매핑과 같은 제로 카피 기술을 사용하여 데이터를 처리합니다. 영역에서 값을 읽으면 복사본이 아닌 사실상 실제 디스크 값을 보는 것입니다. 이는 라이브 객체의 기본이며 디스크 쓰기가 검증된 후 영역 헤드 포인터가 새 버전을 가리키도록 설정할 수 있는 이유이기도 합니다.

  • Realm은 다음 세 가지 규칙을 따르면 간단하고 안전한 멀티스레드 코드를 가능하게 합니다.

  • 영역 인스턴스의 다른 스레드에 대한 변경 사항을 보려면 '루프' 스레드에 존재하지 않거나 자동 새로 고침이 비활성화된 인스턴스를 수동으로 새로 고침합니다.

  • 반응형 이벤트 스트림 기반 아키텍처를 기반으로 하는 앱의 경우 객체, 컬렉션 및 Realm을 동결 하여 사본을 다른 스레드로 효율적으로 전달하여 처리할 수 있습니다.

  • Realm의 멀티버젼 동시성 제어(MVCC) 아키텍처는 Git과 유사합니다. 그러나 Git과는 달리 Realm은 각 영역에 대해 하나의 실제 최신 버전만 있습니다.

  • Realm은 격리 및 내구성을 보장하기 위해 두 단계로 커밋합니다.

돌아가기

데이터 필터링