[펌] 임도형 님의 예외처리 가이드

4년 전 현재 직장입사 후 약 한 달 정도 함께 근무하였던 임도형 님의 예외처리에 대한 명 ppt 2개를 소개한다.

첫번째 ppt 는 java 예외 처리의 원칙에 대한 내용이고

두번째 ppt 는 언제 어디에 예외를 남길지에 대한 내용이다.

 

요약하면

  • 발생하는 예외를 뭉개지 마라. IDE로부터 자동생성된 e.printStackTrace() 만 남기는건 아무 처리도 안하는 것 만 못하다.
  • caller / callee 의 경계에서 예외를 처리하면 로그의 중복/누락을 방지할 수 있다.
  • 해당 상황을 인지할 수 있는 충분한 정보를 남겨라.

 

부연하자면

Exception 이란 정의되지 않은 동작을 처리하는 규약이다. 개발자가 정의한 io 의 범주를 벗어날지라도 우리는 이를 처리하는 규약을 정의할 수 있다.

java 에는 크게 두 가지 Exception 이 존재한다.

  • CheckedException
  • UncheckedException(=RuntimeException)

CheckedException 의 경우, 처리의 기준이 명백하고 프로그램은 복구가 가능하다. 처리에 크게 고민할 필요가 없다.

문제는 RuntimeException이다. RuntimeException이 발생하였다는 것은 이 프로그램이 복구 불가능한 상태에 진입하였다는 의미이다. 아래 소개된 임도형 님의 ppt 는 RuntimeException 을 어떻게 잘 처리할지에 대한 내용이다. Exception 처리같은 경우 충분한 경험이 없다면 매끄럽게 해결하기가 어려운데, 이 ppt는 실천적인 가이드를 제시해주는 매우 훌륭한 자료이므로 java 개발자라면 반드시 한번은 읽어볼 필요가 있다.

 

 

python으로 minecraft 를 해보자 #1

아이들 코딩 교육을 어떻게 하는게 재미있을까 이것 저것 알아보다 결국 minecraft 로 결정

minecraft로 여러가지 재미있는 일을 할 수 있다(..고한다.) 그 중에 하나가 malmo project 인데, 개인이 쉽게 구성하기 어려운 unsupervised learning 환경을 minecraft 로 제공하는 프로젝트다.

이 글에선 malmo 는 다루지 않고 minecraft – python 인터페이스를 어떻게 구성하는지만 정리한다.

prerequisite

python 은 설치되어 있어야 한다.

python 실행환경이 minecraft 에서 접근 가능해야 한다.

 

minecraft 설치

설치 : https://minecraft.net/ko-kr/
주의할 점: windows 10 edition 은 설치하지 말 것. python 인터페이스를 구성할 수 없음
현재 설치할 수 있는 가장 최신 버전은 1.11.2 이다.

minecraft forge 설치

minecraft mod 를 설치할 수 있도록 도와주는 tool
처음 설치했던 minecraft와 같은 버전으로 설치한다.
설치 : https://files.minecraftforge.net/

raspberry jam mod 설치

본래는 raspberrypi 에서 minecraft 를 동작시키기 위한 mod.
허나 이 mod 를 설치하면 python 인터페이스를 바로 구성할 수 있다.
설치 : https://github.com/arpruss/raspberryjammod/releases

  1. 위 링크에서 mods.zip 을 받은 후 %APPDATA%\.minecraft\mods 디렉토리 안에 압축 해제한다.
  2. 위 링크에서 python-scripts.zip 을 받은 후 %APPDATA%\.minecraft 디렉토리 안에 압축 해제한다.

확인

minecraft 를 실행한다.

mc1

실행환경이 forge 로 되어있는지 확인한다.

mc2.PNG

%APPDATA%.minecraft\mcpipy 디렉토리 안에 포함된 예제 코드 중 아무거나 minecraft 안에서 실행해 본다.

실행 방법은 /py [python filename] 이다.

mc3.PNG

tensorflow 윈도우 환경 빌드

readme 만 잘 읽고 따라하면 특별할 것은 없다.

근데 정말 잘(!) 읽어야 하므로 주의사항을 기록해 둔다.

 

build tool

bezel 과 cmake 중 하나를 선택할 수 있다.

bezel 이 더 좋아보이긴 하지만 윈도우 환경을 제대로 지원하지 않으므로 mingw 를 설치하여야 한다. 그러니 왠만하면 cmake 로 빌드하자.

 

prerequisite

swig

python 3.5

  • numpy 1.11.0 이상이 설치되어 있어야 한다.

visual studio 2015

(중요) cmake 3.5~3.6

  • 현재 cmake 버전이 3.8rc 까지 나왔는데 이걸로 빌드하면 nvcc 쪽에서 공백 처리 문제 때문에 빌드 에러 난다.
  • 이 문서에서는 이 팁이 가장 중요하다. cmake 버전 확인!
  • 관련 issue

nvidia cuda toolkit 8.0

nvidia cudnn 5.1

 

여기까지만 구성하면 나머지는 readme 의 설명대로 따르면 된다.

cmake 버전만 조심하자.

 

 

python trivia

개요

python 이해도를 높이기 위한 목적으로,

잘 만들어진 library 소스코드(tensorflow)를 보다가 특기할 만한 짧은 내용들을 간략히 소개한다.

 

__future__

compiler 지시자로 동작한다.

하위 호환성이 없는 기능을 미리 사용하기 위함이다.

링크 : PEP 236 – Back to the __future__

링크 : future statement

 

__all__

init.py 내에서 작성하는 variable 이다.

package 내의 공개 가능한 submodule 의 범위를 한정하기 위함이다.

복잡한 프로젝트의 구조와 별개로 사용자 인터페이스를 단순하게 하고 싶을 때 사용할 수도 있다.


# tensorflow\__init__.py

# tensorflow.python 패키지 하위의 submodule 들을 사용자가 접근할 때

# tensorflow.xxx 으로 사용 가능하도록 한다.

from tensorflow.python import *

링크 : importing * from a package

 

pylint

정적분석 도구다.

PEP8 스타일 가이드를 따르도록 도와준다.

pylint: 로 시작하는 주석으로  message 를 제어할 수 있다.

# pylint: disable=wildcard-import
from tensorflow.python import *
# pylint: enable=wildcard-import

링크 : PEP8 style guilde for python code

 

SWIG

python – native code wrapper 다.

같은 목적의 도구로 boost::python 가 있다.

둘 다 써보지 않아서 잘은 모르겠지만

SWIG 는 별도의 인터페이스 file 을 작성하여야 하는 번거로움이 있는 대신

python / tcl / perl / java /c# 등 다양한 타겟으로 인터페이스가 가능하다.

boost 빠인 나로선 boost::python 쪽에 더 마음이 간다.

openAI gym 을 windows 에서 실행하는 방법

윈도우에서 openAI gym 환경을 구성하는 가장 쉬운 방법을 공유한다.
cygwin 이나 mingw 보다는 windows10 에 포함된 linux subsystem 사용을 권한다.

준비물

dependencies 설치

# dependencies 인스톨
sudo apt-get update
sudo apt-get install cmake zlib1g-dev libjpeg-dev xvfb libav-tools xorg-dev libboost-all-dev libsdl2-dev swig git g++

# anaconda 로 python3 설치
wget https://repo.continuum.io/archive/Anaconda3-4.3.0-Linux-x86_64.sh
./Anaconda3-4.3.0-Linux-x86_64.sh

# graphic driver 설치
sudo apt-get install nvidia-319 nvidia-settings-319 nvidia-prime

export DISPLAY=:0

 

vcxsrv 설치

https://sourceforge.net/projects/vcxsrv/

 

gym 설치

pip 로 하던 git 으로 하던 각자 알아서

pip install gym
pip install gym['atari']

 

gym 예제 코드 실행

import gym
env = gym.make('Copy-v0')
env.reset()
env.render()

java synchronization internal

java의 동기화는 크게 두 가지로 분류

  • 암묵적인 동기화(synchronized keyword)
  • 명시적인 동기화(concurrent.locks.Lock)

이 두가지는 구현 방식이 다르다.

  • synchronized는 jvm의 monitorenter / monitorexit 인스트럭션을 호출
  • Lock 은 hotspot에서 바로 native intrinsic function 을 호출

이 글에선 synchronized 의 구현을 살펴본다.

jvm-hotspot 소스코드는 openjdk8 을 참조하였으므로 다른 구현체(oracle, android 등)에서는 상황이 다를 수도 있지만 실제로 그럴 것 같지는 않다.

synchronized keyword

알다시피 synchronized keyword는 두 가지 경우에 쓰인다.

  • synchronized method
  • synchronized block

두 경우 모두 lock의 획득/반환은 jvm bytecode 중 monitorenter/monitorexit 인스트럭션으로
수행한다.

글에 착오가 있어 수정한다.

  1. synchronized block은 컴파일러에 의해 monitorenter / monitorexit 인스트럭션으로 변환이 되는 반면,
  2. synchronized method 는 bytecodeInterpreter가 method를 수행하는 시점에 동기화 여부를 판별하지 monitorenter/monitorexit 인스트럭션으로 컴파일되지는 않는다.

두 경우 모두 InterpreterRuntime::monitorenter() InterpreterRuntime::monitorexit() 메쏘드를 호출하기 때문에 최종적으로 lock을 획득하는 방식은 같지만 메커니즘은 다르다. hotspot 소스코드를 주의깊게 보지 않아 이런 혼동이 왔다. 이 글을 통해 잘못된 정보가 전달될 수 있으므로 앞으로는 조심하겠다.

jvm spec

synchronized method

synchronized method 에서 주의깊게 살펴 볼 내용은 biased lock 이라는 성능 향상을 위한 기법이다. document에 의하면 un-contented thread(=경쟁상태가 아닌 thread)의 성능 향상 효과가 있다고 하며 실제로 그렇다.

biased lock

biased lock이란 대부분의 java 쓰레드는 동기화를 위한 object가 1개인 경우가 많은데에서 착안,

object header 에 thread ID 와 biased 여부를 확인하는 flag 를 두어

동일 thread 가 연속적으로 critical section에 접근하는 경우 (= resource 경쟁 상태가 아닌경우)

atomic operation 을 수행하지 않는 lock 을 제공하여 수행 속도를 향상시키는 기법이다.

요약하면, biased lock = atomic operation을 하지 않는 lock = 흉내만 내는 lock 이다.

Dice-Moir-Scherer QR lock 최초 논문

hotspot에서의 biased lock 을 설명한 오라클 블로그

openJDK wiki 중 synchronization

implementation

openJDK8에서 bytecode interpreter가 synchronized method를 해석하는 구현. 모든 invoke 된 method는 이 구문에 진입하는데, synchronized keyword가 있으면 다음의 순서로 단계적으로 lock 획득을 시도한다.

  1. lock 최초 획득 – 이때 오브젝트 헤더에 thread id 와 bias flag 를 설정한다.
  2. biased lock 획득 시도 – 오브젝트 헤더와 동일한 thread 접근 시
  3. reboke bias – biased lock 획득을 실패
    1. thin lock 시도 – aging count 증가시킨다
    2. inflate lock 시도
  4. re-bias

hotspot/share/vm/interpreter/bytecodeInterpreter.cpp line:683

//bytecode interpreter 가 method 진입
case method_entry: {
... 중략 ...
// synchronized method 이면 Lock
if (METHOD->is_synchronized()) {
... 중략 ...
// 가장 먼저 biased lock 이 가능한지 확인한다.
// revoke 가능하면 revoke
// 아니면 re-bias
// 아니면 anonymously bias
if (mark->has_bias_pattern()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
thread_ident = (uintptr_t)istate->thread();
anticipated_bias_locking_value =
(((uintptr_t)rcvr->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);

if (anticipated_bias_locking_value == 0) {
// Already biased towards this thread, nothing to do.
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
} else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// Try to revoke bias.
markOop header = rcvr->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
if (Atomic::cmpxchg_ptr(header, rcvr->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
} else if ((anticipated_bias_locking_value & epoch_mask_in_place) != 0) {
// Try to rebias.
markOop new_header = (markOop) ( (intptr_t) rcvr->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
if (Atomic::cmpxchg_ptr((void*)new_header, rcvr->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
}
success = true;
} else {
// Try to bias towards thread in case object is anonymously biased.
markOop header = (markOop) ((uintptr_t) mark &
((uintptr_t)markOopDesc::biased_lock_mask_in_place |
(uintptr_t)markOopDesc::age_mask_in_place | epoch_mask_in_place));
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// Debugging hint.
DEBUG_ONLY(mon->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
if (Atomic::cmpxchg_ptr((void*)new_header, rcvr->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics) {
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
}
success = true;
}
}

// biased lock 이 실패하면 기존 방식대로 lock을 획득한다.
// Traditional lightweight locking.
if (!success) {
markOop displaced = rcvr->mark()->set_unlocked();
mon->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
if (call_vm || Atomic::cmpxchg_ptr(mon, rcvr->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
mon->lock()->set_displaced_header(NULL);
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, mon), handle_exception);
}
}
}
}
THREAD->clr_do_not_unlock();

아래 코드는 monitorenter / monitorexit 구현이다. 궁극적으로 synchronized method나 synchronized block 모두 InterpreterRuntime::monitorenter() 메소드를 호출한다.
hotspot/share/vm/interpreter/interpreterRuntime.cpp line:606

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

//%note monitor_1
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
// Free entry. This must be done here, since a pending exception might be installed on
// exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
elem->set_obj(NULL);
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

synchronized block

synchronized block은 해당하는 block의 시작과 끝을 java compiler가 monitorenter / monitorexit 로 변환한다.

아래 예제코드는 synchronized block 과 synchronized method 를 bytecode 로 변환하면 어떻게 되는지 보여준다.

public class SynchronizedExample {
public static void main(String[] args) {
Foo foo = new Foo();
synchronized (foo) {
++foo.foo;
}

methodFoo(foo);
}

static synchronized void methodFoo(Foo foo) {
++foo.foo;
}

static class Foo {
public int foo;
}
}

main 메쏘드. synchronized block 은 monitorenter 로 진입, monitorexit 로 끝나지만 synchronized method는 메소드를 invoke할 뿐이다.

0 new #2 <SynchronizedExample$Foo>
3 dup
4 invokespecial #3 <SynchronizedExample$Foo.<init>>
7 astore_1
8 aload_1
9 dup
10 astore_2
11 monitorenter //synchronized block 시작
12 aload_1
13 dup
14 getfield #4 <SynchronizedExample$Foo.foo>
17 iconst_1
18 iadd
19 putfield #4 <SynchronizedExample$Foo.foo>
22 aload_2
23 monitorexit  // synchronized block 끝
24 goto 32 (+8)
27 astore_3
28 aload_2
29 monitorexit
30 aload_3
31 athrow
32 aload_1
33 invokestatic #5 <SynchronizedExample.methodFoo> // method invoke
36 return

synchronized 메소드

0 aload_0
1 dup
2 getfield #5 <SynchronizedExample$Foo.foo>
5 iconst_1
6 iadd
7 putfield #5 <SynchronizedExample$Foo.foo>
10 return

결론

Lock object 를 명시적으로 사용하는 것 보다 synchronized keyword 를 사용하였을 때 유리한 경우는 다음과 같다.

  • 리소스가 공유될 필요는 있으나, 빈도가 적다.(=경쟁적이지 않다.)
  • 공유 리소스에 한 번 접근하였던 thread가 재진입하는 경우가 빈번하다.

concurrent programming #1

왜 concurrent programming 인가?

clockspeed

2000년대 초반까지 cpu의 처리속도는 2년 마다 2배씩 증가해 왔습니다. 복잡한 연산을 하고 싶으면 돈을 더 내고 비싼 cpu를 사면 해결되는 시대였죠. 그런데 어느 순간부터 cpu의 처리속도는 더 이상 빨라지지 않고 있습니다. 회로의 집적도와 전자의 이동속도에 한계가 있기 때문에 양자컴퓨터라도 나오지 않는 이상 현재로선 cpu의 수직적인 처리속도를 높일 수 있는 마땅한 방법은 없는 듯 합니다.

Cores.png

2004년 부터는 cpu의 처리속도를 높이는 대신 병렬적으로 코어의 갯수를 늘리는 시도를 하였습니다. 그리고 10여 년이 지난 현재, 손바닥만한 핸드폰에도 예외없이 cpu는 2개 이상 들어가는 것이 당연한 시대가 되었습니다. 멀티 코어 환경이 당연한 시대에 과연 그 안에서 동작하는 프로그램은 멀티 코어 환경에 적합하도록 만들어 졌을까요?

 

thread vs process

잘 아시겠지만 다수의 컴퓨팅 유닛을 사용하는 방법은

  1. multi processing
  2. multi threading

이 두 가지 방법 뿐입니다.

그렇다면 쓰레드랑 프로세스의 차이점은 무엇일까요? 찾아보면 많이들 나오니 자세한 설명은 패스하고 주요 차이점만 서술하면

쓰레드는 프로세스에 비해

  1. 생성 비용이 낮고
  2. 리소스 공유가 쉽습니다.

runtimearea2

위 그림은 jvm의 run-time data area 인데요. java 프로세스를 하나 생성하기 위해선 위 6가지 영역(정확히는 5가지, run-time constant pool은 method area에 포함) 이 모두 새로 생성되어야 합니다. 허나 쓰레드는 초록색 박스 영역만 새로 생성하고, 빨간색 박스 영역은 부모 프로세스와 공유합니다. java 를 예로 들었지만 다른 언어나 os도 상황은 대동소이합니다.

프로세스가 서로 리소스를 공유하는 수단은 1) named pipe 2) socket 3) shared memory 4) message 등이 있습니다. 이러한 프로세스 간 통신 수단은 어플리케이션 외부 커널에서 관리되기 때문에 비용이 높고 규약 외 처리(=exception)를 잘 정의하여야 합니다.

반면 쓰레드는 프로세스에 비해 리소스를 공유하는 방법이 쉽습니다.

  1. heap 에 리소스를 할당하거나
  2. 전역 변수를 설정하거나
  3. static 변수를 설정하면

해당 리소스는 쓰레드 간 공유가 가능합니다.

java의 경우, 전역변수가 없고 static keyword 는 멤버 변수에만 할당이 가능하므로 리소스가 공유되는 경우는 다음 두 가지 뿐입니다.

  • heap에 할당된 리소스 및 레퍼런스
  • static 멤버 변수 및 레퍼런스

 

thread 문제들

멀티쓰레드 프로그램은 분명 cpu 처리속도의 한계를 극복하기 위한 좋은 대안입니다만, 개발자에게 다양한 문제를 안겨주기도 합니다. 아마도 개발 초년생에게 가장 어려운 문제가 thread 문제일겁니다. thread 문제가 어려운 이유는 무엇일까요?

  1. 재현이 어렵고
  2. 현상이 그때그때 다르고
  3. 인간의 뇌가 동시에 두 가지 사고를 할 수 없기 때문

이 아닐까 생각합니다. 허나 얼핏 복잡다단해 보이는 thread 문제에도 유형이 있으니 문제의 유형들을 분류해 보도록 합시다.

thread 문제의 양상은 크게 두 가지로 구분합니다.

  1. thread safety 문제
  2. 기대하지 않는 동작

thread safety 문제는 프로그래머가 리소스 공유에 대한 이해가 부족하기 때문에 발생합니다. 공유되는 리소스는 반드시라고 해도 좋을 정도로 대부분 동기화 문제가 발생합니다. 리소스가 공유되더라도 동기화 문제가 발생하지 않는 경우는 다음 세 가지 뿐입니다. 이 세 가지는 반드시 기억해 두길 바랍니다. 여러 가지 다른 thread 문제들은 쉽게 예측이 어렵고 해결방안이 뚜렷하지 않을 수도 있으나 thread safety 문제는 명약관화하고 해결방안이 분명합니다.

  1. 공유 리소스가 immutable 하거나
  2. critical section에 대한 동기화 보장을 하였거나
  3. instruction 이 atomic 하거나

thread safety를 보장하였는데도 어플리케이션이 기대하지 않는 엉뚱한 동작을 한다면 이는 어플리케이션 동작 환경을 고려하지 않은 설계/정책의 문제입니다. 쓰레드는 태생적으로 제어가 어렵습니다. 실행중인 쓰레드가 언제 종료될지, sleep 상태의 쓰레드가 언제 다시 작업을 재개할 지 실행중에는 알 수가 없기 때문입니다. 또한 모든 thread 는 태생적으로 다른 thread들과 리소스를 점유하기 위한 경쟁관계에 놓여있기 때문에 경쟁의 과열로 인한 문제가 발생할 가능성이 항상 존재합니다.

  1. starvation
  2. dead lock / live lock 문제
  3. aba 문제

이러한 문제들은 설계와 정책으로 해결하여야 합니다. 어플리케이션의 동작환경에 따라, 사용자의 사용 패턴에 따라 해당 문제는 발생할 수도 있고 안할 수도 있습니다. 또한 어떤 문제들은 소프트웨어 설계 시점에 원천적으로 문제의 원인을 봉쇄할 수도 있습니다. 모든 분야가 마찬가지겠지만 소프트웨어 설계와 정책은 외부 환경에 대한 고려가 필수적입니다.

 

동기화 방식과 성능 병목

monitor

critical section

mutex

semaphore

spinlock

 

reentrantlock

synchronized

 

volatile

memory transaction (hardware / software)

lock-free / wait-free