elevne's Study Note

Java Keywords ... 본문

Backend/Java

Java Keywords ...

elevne 2023. 4. 12. 21:43

Native

 

native 키워드는 자바 코드 내에서 다른 언어를 사용할 수 있게끔 해준다. JNI (Java Native Interface) 를 사용하는데, 이는 자바로 만들어진 프로그램에서 특정 플랫폼에서만 실행되는 코드 (Native Code) 에 접근하기 위한 API 인 것이다. Native Code 를 생성하는 다른 언어와 함께 공동작업, 하드웨어에 보다 깊게 접근하여 컨트롤할 수 있다는 장점이 있다. 

 

 

예를 들어 C 언어로 작성된 외부 라이브러리를 Java 프로그램에서 사용하려면 해당 라이브러리의 함수를 Java 메서드로 래핑하고, 해당 메서드에 native 키워드를 사용하여 해당 함수가 C 언어로 작성된 코드와 연결되도록 선언할 수 있는 것이다. 

 

 

public class NativeExample {
   static {
      System.loadLibrary("myLibrary"); // 외부 라이브러리 로드
   }

   // Native 키워드를 사용하여 C 언어로 작성된 메서드를 호출
   public native void myNativeMethod();
   
   public static void main(String[] args) {
      NativeExample ne = new NativeExample();
      ne.myNativeMethod(); // Native 메서드 호출
   }
}

 

 

System.loadLibrary() 를 통해 외부 라이브러리를 로드한다. native 키워드를 사용하여 선언된 메서드는 Java 가 아닌 다른 언어로 작성된 코드와 연결되므로 해당 코드를 실행하기 위해서는 외부 라이브러리, 시스템에 대한 의존성이 존재하게 된다.

 

 

 

Volatile

 

volatile 키워드는 멀티쓰레드 환경에서 공유된 변수에 대한 접근을 동기화하는 기능을 제공한다. volatile 키워드가 적용된 변수는 메인 메모리에 저장되며 각 쓰레드는 이 변수의 값을 읽고 쓸 때 캐시된 값이 아니라, 메인 메모리에 저장된 값으로 접근한다. (CPU 내에는 성능 향상을 위해 L1 Cache 가 내장되어 있는데, CPU 코어는 메모리에서 읽어온 값을 캐시에 저장하고 캐시에서 값을 읽어서 작업한다. 값을 읽어올 때는 우선 캐시에 해당 값이 있는지 확인하고 없는 경우에만 메인메모리에서 가져온다. 도중에 메모리에 저장된 변수의 값이 변경되었는데도 캐시에 저장된 값이 갱신되지 않아 메모리에 저장된 값과 달라지는 경우가 발생한다.) 

 

 

public class SharedCounter {
   private int counter = 0;

   public void increment() {
      counter++;
   }

   public int getCounter() {
      return counter;
   }
}

 

 

위와 같은 코드는, 멀티쓰레드 환경에서는 변수에 대한 접근이 동기화되지 않아 counter 값이 예상과 다르게 증가할 수 있다. 이 문제를 해결하기 위해 counter 변수를 volatile 키워드로 선언하여 각 쓰레드가 counter 값을 메인 메모리에서 직접 읽고 쓸 수 있도록 하는 것이다.

 

 

public class SharedCounter {
   private volatile int counter = 0;

   public void increment() {
      counter++;
   }

   public int getCounter() {
      return counter;
   }
}

 

 

increment() 메서드에서 counter 값을 증가시키는 작업은 모든 쓰레드에서 동일한 메모리 주소에 직접 접근하여 수행된다. 이를 통해 쓰레드 간의 값 불일치 문제를 방지할 수 있다. volatile 키워드는 변수의 값을 읽거나 쓸 때마다 메인 메모리에서 직접 읽고 쓰기 때문에 성능에 미치는 영향이 크다. 너무 많이 사용하지 않고 필요한 경우에만 사용할 필요가 있다.

 

 

 

Transient

 

transientSerialize 의 대상에서 제외할 필드에 사용된다. Serialize (직렬화) 는 객체를 파일이나 네트워크로 전송하기 위해 이진형태로 변환하는 프로세스인데, transient 키워드가 사용된 필드는 직렬화 과정에서 제외되는 것이다. 

 

 

public class User implements Serializable {
   private String username;
   private transient String password;

   public User(String username, String password) {
      this.username = username;
      this.password = password;
   }
   
   // getters and setters
}

 

 

위 코드에서 password 필드는 transient 키워드로 선언되어있다. 

 

 

User user = new User("john", "password123");
// 직렬화
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.ser"));
out.writeObject(user);
out.close();

// 역직렬화
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.ser"));
User deserializedUser = (User) in.readObject();
in.close();

System.out.println(deserializedUser.getUsername()); // 출력: john
System.out.println(deserializedUser.getPassword()); // 출력: null

 

 

User 객체를 직렬화하여 user.ser 파일에 저장하고, 역직렬화하여 deserializedUser 객체를 생성한다. getPassword 시에는 null 이 출력되는데, 이는 password 필드가 transient 로 선언되어 있어서 직렬화 대상에서 제외되었기 때문이다. 

 

 

 

Synchronized

 

synchronized 키워드는 멀티쓰레딩 환경에서 공유 자원에 대한 동시 접근을 제어하는데 사용한다. synchronized 키워드를 사용하면, 특정 객체나 메서드에 대해 하나의 쓰레드만 접근할 수 있도록 보호할 수 있다.

 

 

synchronized 키워드는 크게 두 가지 방법으로 사용된다. 첫 번째로는 synchronized 메서드를 정의하는 것이다. 이 경우, 메서드의 선언부에 synchronized 키워드를 추가하여 해당 메서드를 호출할 때는 하나의 쓰레드만 해당 메서드를 실행할 수 있도록 보호할 수 있다.

 

 

public class SynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized void decrement() {
        count--;
    }

    public synchronized int getCount() {
        return count;
    }
}

 

 

두 번째 방법은 synchronized 블록을 사용하는 것이다. 이 경우 특정 객체나 클래스의 인스턴스를 사용하여 synchronized 블록을 생성한다. 이 블록 내부에서는 해당 객체나 인스턴스에 대한 접근을 동기화하여 멀티쓰레딩 환경에서 안정적인 동작을 보장한다.

 

 

public class SynchronizedList {
    private List<Integer> list = new ArrayList<>();

    public void add(Integer value) {
        synchronized (list) {
            list.add(value);
        }
    }

    public void remove(Integer value) {
        synchronized (list) {
            list.remove(value);
        }
    }

    public int size() {
        synchronized (list) {
            return list.size();
        }
    }
}

'Backend > Java' 카테고리의 다른 글

Java Functional Interface  (0) 2023.04.18
Thread, Future, ScheduledFuture, Runnable  (0) 2023.04.13
Cache 에 대하여  (0) 2023.03.27
Java HttpServer 사용해보기  (0) 2023.02.23
Java 공부 (File 다운로드)  (0) 2023.02.16