728x90

테스트 환경 : Visual Studio 2019 Community


동영상 강좌에 나오는 strcpy, strcat 함수는 에러를 뿌리면서 실행이 안된다.

strcpy_s, strcat_s 를 사용하라고 나온다.


#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]) {
    char str[100] = "Hello ";
    char stra[100];

    int len;
    len = strlen(str);
    printf("str의 문자열의 길이는 %d\n", len);

    strcpy_s(stra, str);
    printf("str2의 값 : %s\n", stra);

    strcat_s(str, "World!");
    printf("%s\n", str);

    char str3[] = "sample";
    char str4[] = "simple";

    int cmp = strcmp(str3, str4);
    // 둘다 같으면 0 반환, str3가 str4보다 앞에 있으면 -1, 뒤에 있으면 1을 반환
    // a가 i보다 앞에 있어 아스키코드값이 작기 때문에 -1이 반환한다.

    // 값을 비교할 때 보통 == 연산자를 이용하여 동일 여부를 비교한다.
    // 하지만 문자열의 경우 == 연산자를 사용하면 원치 않는 결과를 가져올 수 있다.
    // 이는 같은 값일지라도 저장된 메모리 주소가 다르기 때문이다.
    // char 배열을 이용한 문자열의 경우 변수는 주소를 가리키므로, strcmp()함수를 이용한다.
    // 하지만 C++ 에서는 string 문자열일 경우 == 연산자를 사용할 수 있다.

}



블로그 이미지

Link2Me

,
728x90

파스칼의 삼각형을 2차원 배열로 출력한 것이다.

위키백과 https://ko.wikipedia.org/wiki/%ED%8C%8C%EC%8A%A4%EC%B9%BC%EC%9D%98_%EC%82%BC%EA%B0%81%ED%98%95 사이트에 자세한 설명이 나온다.


수학적으로, 이 구조는 파스칼의 법칙을 사용하여 아래와 같이 표현한다.

n 번째 줄의 k 번째 값을 라고 하면, 이 값은

으로 정의된다. 이때,


#include <stdio.h>

int main() {
    // 파스칼의 삼각형
    int p[19][19]; // 2차원 배열

    for (int n = 0; n < 19; n++) {
        for (int k = 0; k <= n; k++) {
            if (k == 0 || k == n) {
                p[n][k] = 1;
            }
            else {
                p[n][k] = p[n - 1][k - 1] + p[n - 1][k];
            }
            printf("%d ", p[n][k]);
        }
        printf("\n");
    }
   
}


블로그 이미지

Link2Me

,
728x90

동영상 강좌를 듣다보니 디버그 서명 인증서 SHA-1 을 쉽게 등록하는 방법이 나온다.

CMD 창에서 등록하는 방법 등도 나오는데 쉽게 할 수 있지 않다.


Android Studio 우측에 있는 Gradle 를 누르고 해당 앱에서 android → signingReport 를 선택한다.





블로그 이미지

Link2Me

,
728x90

본 내용은 Android 에서 C++ 을 연동하여 처리하는 것을 알아야 할 거 같아서 C 와 C++ 에 대한 기본 개념을 파악하고자 동영상 강좌를 들으면서 간략하게 필요한 부분을 정리해두는 것이라 체계적인 것과는 거리가 좀 있다.

Visual Studio 2019 Community 버전에서 테스트하고 기록해둔다.

 

체계적으로 차근차근 듣고자 한다면 두들낙서님의 C/C++강좌 https://www.youtube.com/results?search_query=C%2FC%2B%2B+%EA%B0%95%EC%A2%8C 를 참조하면 된다.

강좌 설명은 Visual Studio 2015 로 하는 것 같다.

 

C언어 기본 문법

 

#include <stdio.h>  // # 전처리기
 
int main() {
    printf("Hello, World!\n"); // \n 은 줄바꿈
    printf("%d + %d = %d\n"235);
    printf("%f\n"3.141592);
    printf("%.2f\n"3.141592); // 소수점 둘째짜리까지만 반올림해서 출력
    printf("%g\n"3.141592); // 실수 출력 (지수형태로도 출력)
 
    printf("%c %c %c\n",'a','b','c'); // %c : 문자 출력(알파벳, 숫자, 몇몇 기호, \n)
    printf("%s\n""안녕하세요"); // %s : 문자열 출력 (문자들이 열거) - 한글 출력 가능
 
    int a = 5// int : 정수형 변수로 4 bytes(32비트) 공간을 차지한다.
    int b = 3;
    printf("%d + %d = %d\n", a, b, a + b);
 
 
 
    int a1 = 5// int : 정수형 변수로 4 bytes 즉 32비트 공간을 차지한다.
    int b1 = 3;
    printf("%d + %d = %d\n", a1, b1, a1 + b1);
 
    int mok1 = a1 / b1; // 몫
    int namuji1 = a1 % b1; // 나머지
    printf("%d / %d = %d\n", a1, b1, mok1);
    printf("%d %% %d = %d\n", a1, b1, namuji1);
 
 
    float a2 = 9.8// float : 실수형 변수로 4 bytes(32비트) 공간을 차지한다.
    float b2 = 3.14// float 는 오차가 발생하여 double 형을 많이 사용한다.
    float hap2 = a2 + b2;
    float cha2 = a2 - b2;
    float mok2 = a2 / b2; // 나누기
    printf("%f + %f = %f\n", a2, b2, hap2);
    printf("%f - %f = %f\n", a2, b2, cha2);
    printf("%f / %f = %f\n", a2, b2, mok2);
 
    double a3 = 9.8// double : 실수형 변수로 8 bytes(64비트) 공간을 차지한다.
    double b3 = 3.14;
    double hap3 = a3 + b3;
    double cha3 = a3 - b3;
    double mok3 = a3 / b3; // 나누기
    printf("%f + %f = %f\n", a3, b3, hap3);
    printf("%f - %f = %f\n", a3, b3, cha3);
    printf("%f / %f = %f\n", a3, b3, mok3);
    // 부동소수점(floating point)
 
    /* 2진법
     100110(2) = 1*2^5 + 1*2^2 + 1*2^1 = 38
     int - 32bits(4bytes)
     37 = 100101(2)
     00000000 00000000 00000000 00100101 = 37
     11111111 11111111 11111111 11111111 = -1
    */
 
    /* 자료형 - 정수형, 실수형
       정수형
       char  (1바이트) - 문자를 담는데 쓰임
       short (2바이트)
       long  (4바이트)
       long long (8바이트)
       int   (시스템에 따라서 자동 결정, 대체로 long)
 
       실수형
       float  (4바이트)
       double (8바이트)
 
       unsigned, signed
          unsigned int a;
          unsigned double f;
 
      void : 리턴 값이 없는 함수의 자료형
 
      bool : 참, 거짓을 저장(= char)
    */
 
    printf("%d %d %d %d\n"sizeof(int), sizeof(char), sizeof(float), sizeof(double));
    // sizeof(x) : x의 크기를 알려 줌.
 
    int a4 = 3.14// 실수를 정수형 변수에 담을 수 없다. 소수점은 짤리고 3을 반환한다.
    double b4 = 10;
    printf("%d %f\n", a4, b4);
 
    // 형변환 : 자료형을 다른 자료형으로 바꾸는 작업
    int math = 90, korean = 95, english = 96;
    int sum = math + korean + english;
    double avg = sum / 3// 정수형 sum 앞에 강제로 형변환 (double)sum 으로 하면 값이 달라진다.
 
    printf("%f\n", avg); // 예상 93.666667, 결과 93.000000
    // 정수 / 정수 = 정수, 정수 + 정수 = 정수
    // 실수 / 정수 = 실수, 정수 + 실수 = 실수
    // 실수 / 실수 = 실수, 실수 + 실수 = 실수
   

 

#include <studio.h>
 
int main()
{
    // 포인터 크기는 64비트 컴파일시에는 8 byte = 84bit
    // 32비트 컴파일시에는 4 byte = 32 bit
    printf("void * sizeof : %d\n"sizeof(void*));
    printf("char * sizeof : %d\n"sizeof(char*));
    printf("int * sizeof : %d\n"sizeof(int*));
    printf("long * sizeof : %d\n"sizeof(long*));
    printf("float * sizeof : %d\n"sizeof(float*));
    printf("double * sizeof : %d\n"sizeof(double*));
 
    printf("===========================================\n");
 
    printf("char sizeof : %d\n"sizeof(char));
    printf("int sizeof : %d\n"sizeof(int));
    printf("long sizeof : %d\n"sizeof(long));
    printf("float sizeof : %d\n"sizeof(float));
    printf("double sizeof : %d\n"sizeof(double));
}
 

 

배열

#include <stdio.h>
 
int main() {
    // array
    int arr[] = { 31479-3 };
 
    for (int i = 0; i < sizeof(arr) / sizeof(int); i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

 

 

 

블로그 이미지

Link2Me

,
728x90

Java Thread Synchronized(동기화)란 여러 개의 Thread가 한 개의 자원을 사용하고자 할 때,
해당 Thread만 제외하고 나머지는 접근을 못하도록 막는 것이다.


Multi-Thread로 인하여 동기화를 제어해야하는 경우가 생긴다.
synchronized 키워드를 사용하면, Multi-Thread 상태에서 동일한 자원을 동시에 접근하게 되었을 때 동시 접근을 막게 된다.
synchronized를 사용하는 방법은 아래와 같다.
1. 메서드에 synchronized 하기

    - synchronized 를 붙이면 메소드 전체가 임계 영역으로 설정된다.

    - 쓰레드는 synchronized 메소드가 호출된 시점부터 해당 메소드가 포함된 객체의 Lock을 얻어 작업을 수행하다가 메소드가 종료되면 Lock 을 반환한다.
2. 블록에 synchronized 하기

   - 메소드 내의 코드 일부를 블럭{} 으로 감싸고 블럭 앞에 synchronized를 붙이는 것이다.

   - 이때 참조변수는 Lock 을 걸고자 하는 객체를 참조하는 것이어야 한다.

   - 이 영역 안으로 들어가면서부터 쓰레드는 지정된 객체의 Lock 을 얻게 되고, 이 블럭을 벗어나면 Lock 을 반납한다.


synchronized : 단 하나의 쓰레드만 실행할 수 있는 메소드 또는 블록을 말한다.
- 다른 쓰레드는 메소드나 블록이 실행이 끝날 때까지 대기해야 한다.
- wait(), notify(), notifyAll() 은 동기화 메소드 또는 블록에서만 호출 가능한 Object의 메소드
  두개의 쓰레드가 교대로 번갈아 가며 실행해야 할 경우에 주로 사용한다.
- wait() 호출한 쓰레드는 일시 정지가 된다.
- notify() or notifyAll()을 호출하면
다른 쓰레드가 실행 대기 상태가 된다.

- synchronized를 대충 사용하면 퍼포먼스 저하, 예상치 못한 동작이 발생할 수 있다.

- 임계 영역은 멀티쓰레드 프로그램의 성능을 좌우하기 때문에 가능하면 메소드 전체에 Lock을 거는 것보다 synchronized 블럭으로 임계 영역을 최소화해서 보다 효율적인 프로그램이 되도록 노력해야 한다.

- 쓰레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료된다.




아래 예제를 실행해서 결과가 나오는 걸 이해해야 한다.

synchronized 를 붙인 경우와 붙이지 않은 경우에 결과를 비교해보면 명확하게 이해가 될 것이다.


예제1

public class SynchThread extends Thread {
    int total = 0;

    @Override
    public void run() {
        synchronized (this) { // 해당 객체(this)에 Lock 이 걸린다.
            for (int i = 0; i < 5; i++) {
                System.out.println(i + "를 더한다.");
                total += i;

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            notify(); // 위 작업이 모두 끝나면 notify()를 호출하여 다른 쓰레드를 실행 대기 상태로 만든다.
        }
    }
}

public class SyncMainThread {

    public static void main(String[] args) {
        SynchThread syncThread = new SynchThread();
        syncThread.start();
       
        synchronized (syncThread) {
            System.out.println("syncThread 가 완료될 때까지 기다린다.");
            try {
                syncThread.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Total Sum : " + syncThread.total);
        }
    }

}

실행결과

syncThread 가 완료될 때까지 기다린다.
0를 더한다.
1를 더한다.
2를 더한다.
3를 더한다.
4를 더한다.
Total Sum : 10



예제2 (동기화 개념을 명확히 이해하고자 한다면 아래 동영상 강좌를 꼭 들어라)

두개의 쓰레드가 교대로 번갈아 가며 실행해야 하는 경우에 주로 사용한다.

"이것이 자바다" 유투브 강좌 https://www.youtube.com/watch?v=hao05jNL2m8 35분부터 참조하면 설명이 잘 되어 있다. 아래 코드는 https://www.youtube.com/watch?v=7pNZr8cmjis 강좌에 나온 내용이다.


- notify() 는 현재 waiting 된 다른 쓰레드를 실행대기 상태로 만든다.

- wait() 를 호출해서 자기 자신은 일시 정지가 된다.


public class DataBox {
    private String data;

    public synchronized String getData() { // 소비자 쓰레드가 호출
        if (this.data == null) { // 데이터를 읽을 수 없으니까 wait 으로 만든다.
            try {
                wait(); // 생성자 쓰레드가 데이터를 넣어줄 때가지 일시 정지된다.

            } catch (InterruptedException e) {
            }
        }
        String returnValue = data;
        System.out.println("ConsummerThread가 읽은 데이터: " + returnValue);
        data = null; // 데이터를 읽었으니까 null 로 만든다.
        notify(); //
notify()를 호출해서 현재 일시정지 상태에 있는 생성자 쓰레드를 실행 대기 상태로 만든다.
        return returnValue;
    }

    public synchronized void setData(String data) { // 생성자 쓰레드가 호출
        if (this.data != null) { // 소비자 쓰레드가 아직 데이터를 읽지 않았다면
            try {
                wait(); //

            } catch (InterruptedException e) {
            }
        }
        this.data = data;
// 데이터를 공유 객체에 저장한다.

        System.out.println("ProducerThread가 생성한 데이터: " + data);
        notify(); // notify()를 호출해서 소비자 쓰레드를 실행 대기 상태로 만들어 data를 읽어갈 수 있게 한다.

    }
}

public class ProducerThread extends Thread {
    private DataBox dataBox;

    public ProducerThread(DataBox dataBox) {
        this.dataBox = dataBox;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            String data = "Data-" + i;
            dataBox.setData(data);
        }
    }

}

public class ConsumerThread extends Thread {
    private DataBox dataBox;

    public ConsumerThread(DataBox dataBox) {
        this.dataBox = dataBox;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            String data = dataBox.getData();
        }
    }

}

public class WaitNotifyExam {

    public static void main(String[] args) {
        DataBox dataBox = new DataBox();

        ProducerThread producerThread = new ProducerThread(dataBox);
        ConsumerThread consumerThread = new ConsumerThread(dataBox);

        producerThread.start();
        consumerThread.start();
    }
}

실행 결과

ProducerThread가 생성한 데이터: Data-1
ConsummerThread가 읽은 데이터: Data-1
ProducerThread가 생성한 데이터: Data-2
ConsummerThread가 읽은 데이터: Data-2
ProducerThread가 생성한 데이터: Data-3
ConsummerThread가 읽은 데이터: Data-3


자바의 정석 13장 예제

public class SyncEx {

    public static void main(String[] args) {
        Runnable runnable = new RunnableSync();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }

}

class Account {
    private int balance = 1000; // private으로 해야 동기화 의미가 있다.

    public  int getBalance() {
        return balance;
    }

    public synchronized void withdraw(int money){ // synchronized로 메서드를 동기화
        if(balance >= money) {
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) {}
            balance -= money;
        }
    } // withdraw
}

class RunnableSync implements Runnable {
    Account acc = new Account();

    public void run() {
        while(acc.getBalance() > 0) {
            // 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withdraw(money);
            System.out.println("balance:"+acc.getBalance());
        }
    } // run()
}
 


블로그 이미지

Link2Me

,
728x90

쓰레드는 자신의 run() 메소드가 모두 실행되면 자동적으로 종료된다.

하지만, 경우에 따라서는 실행 중인 쓰레드를 즉시 종료할 필요가 있다.

public class StopThread extends Thread {
    public void run() {
        while(true) {
            System.out.println("실행 중");
        }
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

StopThread는 while(true) 이므로 무한 반복을 하게 된다.


쓰레드를 즉시 종료시키기 위해서 stop() 메소드를 제공하고 있는데, 이는 쓰지 않는다(deprecated 됨).
stop() 메소드는 쓰레드가 사용 중이던 자원들이 불완전한 상태로 남겨지기 때문이다.


안전하게 Thread를 종료시키는 방법은

boolean 변수를 사용하는 방법과 interrupted() 메소드를 이용하는 방법이 있다.



interrupted() 메소드를 이용하는 방법 (권장)

interrupt() 메소드는 일시 정지 상태일 때 정지가 된다.
Thread.sleep(1); // 일시 정지 상태일 경우 interruptedException을 발생시킨다.
실행대기 또는 실행상태에서는 interruptedException이 발생하지 않는다.
일시 정지 상태를 만들지 않고 while문을 빠져나오는 방법 즉 쓰레드를 종료시키는 방법이 있다.
Thread.interrupted() 메소드를 이용하면 된다.

public class StopThread extends Thread {

    public void run() {
        while(true) {
            System.out.println("실행 중");
            if(Thread.interrupted()) {
                break;
            }
        }
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

public class ThreadStopExample {

    public static void main(String[] args) {
        Thread thread = new StopThread();
        thread.start();
       
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       
        thread.interrupt();
    }
}

1초 뒤에 호출되는 thread.interrupt()에 의해 무한 루프는 종료된다.

하지만 더 깔끔하게 처리하는 방법은 StopThread 의 while 문을 아래와 같이 하는 것이다.

public class StopThread extends Thread {

    public void run() {
        while(!Thread.currentThread().isInterrupted()) {
            System.out.println("실행 중");
        }
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}



boolean 변수를 사용하는 방법

public class StopThread extends Thread {
    private boolean flag = false;
   
    StopThread() {
        this.flag = true; // 생성자에 flag 설정
    }
   
    public void Action(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        while(flag) {
            System.out.println("실행 중");
        }
        System.out.println("자원 정리");
        System.out.println("실행 종료");
    }
}

public class ThreadStopExample {

    public static void main(String[] args) {
        Thread thread = new StopThread();
        thread.start();
       
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       
        ((StopThread) thread).Action(false);
    }
}


블로그 이미지

Link2Me

,
728x90

실행중인 하나의 프로그램을 프로세스(Process)라고 한다.

프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.

프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며, 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.

   - 하나의 프로세스 내부에서 독립적으로 실행되는 하나의 작업 단위를 Thread(쓰레드)라고 한다.

   - 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재하며,

   - 둘 이상의 쓰레드를 가진 프로세스를 멀티 프로세스라고 한다.
     메신저로 채팅을 하면서 파일을 다운로드 받을 수 있는 것은 멀티 쓰레드로 작성되었기 때문이다.

 

Java에서 쓰레드를 구현하는 방법은 두가지가 있다.

public class ThreadBasic {
    public static void main(String[] args) {
        // 1. Thread Class를 상속받는 방법
        Thread t1 = new MyThread1(); // 쓰레드의 생성
        t1.start(); // 쓰레드의 실행. run()이 종료되면 MyThread1 소멸된다.
 
        // 2. Runnable 인터페이스를 구현하는 방법
        Thread t2 = new Thread(new MyThread2());
        t2.start(); // 쓰레드의 실행. run()이 종료되면 MyThread2 소멸된다.
 
    }
}
 
// 1. Thread Class를 상속받는 방법
class MyThread1 extends Thread {
    @Override
    public void run() {
        //작업할 내용
    }
}
 
// 2. Runnable 인터페이스를 구현하는 방법
class MyThread2 implements Runnable {
    @Override
    public void run() {
        //작업할 내용
    }
}
 

 

 

Thread Class를 상속받으면 다른 클래스를 상속 받을 수 없기때문에,

Runnable 인터페이스를 구현하는게 일반적인 방법이다.

Runnable 인터페이스는 run()만 정의되어 있는 간단한 인터페이스이다.

 

Runnable 인터페이스를 구현한 경우, Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread 클래스의 생성자의 매개변수로 제공해야 한다.

 

쓰레드를 생성했다고 해서 자동으로 실행되는 것은 아니다. start() 메소드를 호출해야만 쓰레드가 실행된다.

쓰레드 동작시 run()이 자동적으로 호출된다.

main 메소드의 작업을 수행하는 것도 쓰레드이며, 이를 main 쓰레드라고 한다.

 

 

 

작업할 내용에는 무한 루프 while(flag) { } 를 사용하여 쓰레드의 동작을 제어하도록 한다.

thread를 중단시킬 수 있는 키 값(flag)을 꼭 세팅하여 메인 쓰레드가 종료될 때 같이 종료되도록 하여야 한다.

 

예제1) 쓰레드 기본 동작 확인

public class ThreadEX1 {
    static long startTime = 0;
    public static void main(String[] args) { // ==> main 메소드의 코드를 수행하는 쓰레드
        // 모든 프로세스(실행중인 프로그램)는 최소한 하나의 쓰레드(프로세스 내에서 실제 작업을 수행)를 갖고 있다.
        // 대부분의 프로그램이 멀티쓰레드로 작성되어 있다.
        // 멀티쓰레드 장점 : 1. 시스템 자원의 효율적 사용
        // 2. 사용자에 대한 응답성 향상(채팅 중에 파일 전송)
        // 3. 작업이 분리되어 코드가 간결해진다.
        // 멀티쓰레드 단점 : 1. 동기화에 주의해야 한다. 2. 교착상태가 발생하지 않도록 해야 한다.
        // 3. 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
 
        // 쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작한다.
        ThreadEx1_A t1 = new ThreadEx1_A(); // 쓰레드의 생성
 
        Runnable r = new ThreadEx1_B();
        Thread t2 = new Thread(r);
 
        t1.start(); // 쓰레드 t1을 실행시킨다. ==> 새로운 호출스택 생성
        t2.start(); // 쓰레드 t2를 실행시킨다. ==> 새로운 호출스택 생성
        // 쓰레드는 순차적으로 실행되지 않고 섞여서 실행된다.
        // 실행순서는 OS 스케줄러가 결정한다.
        // 쓰레드는 '사용자 쓰레드' 와 '데몬 쓰레드' 두 종류가 있다.
        // 실행중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.
 
        startTime = System.currentTimeMillis();
        try {
            t1.join();    // main쓰레드가 t1의 작업이 끝날 때까지 기다린다.
            t2.join();    // main쓰레드가 t2의 작업이 끝날 때까지 기다린다.
        } catch(InterruptedException e) {}
 
        System.out.print("소요시간:" + (System.currentTimeMillis() - ThreadEX1.startTime));
    }
}
 
class ThreadEx1_A extends Thread { // 1. 쓰레드 Class 상속
    public void run() { // Thread 클래스의 run()을 오버라이딩
        for(int i=0; i < 300; i++) {
            //System.out.println(getName()); // 조상인 Thread의 getName()을 호출
            System.out.print(new String("-"));
        }
    }
}
 
class ThreadEx1_B implements Runnable { // 2. Runnable  인터페이스 구현
    public void run() { // Runnable 인터페이스의 추상메소드 run()을 구현
        // run() : 쓰레드가 수행할 작업을 작성
        for(int i=0; i < 300; i++) {
            // Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
            //System.out.println(Thread.currentThread().getName());
            System.out.print(new String("|"));
        }
    }
}

 

예제2) 싱글 쓰레드

public class SingleThread {
    public static void main(String[] args) {
        // main 쓰레드 내에서 for 문이 순차적으로 실행되는 걸 확인할 수 있다.
        long startTime = System.currentTimeMillis();
 
        for(int i=0; i < 300; i++)
            System.out.printf("%s"new String("-"));
 
        System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime));
 
        System.out.println(); // 줄바꿈 표시 목적
 
        for(int i=0; i < 300; i++)
            System.out.printf("%s"new String("|"));
 
        System.out.print("소요시간2:"+(System.currentTimeMillis() - startTime));
    }
}
 

 

예제3) 멀티쓰레드

public class MultiThread {
    static long startTime = 0;
    public static void main(String[] args) {
        // 실행할 때마다 출력되는 두 작업의 결과가 섞이는 걸 확인할 수 있다.
        // 두개의 작업이 번갈아 가면서 실행될 때 context switching이 발생하여
        // 멀티쓰레드시 작업시간이 싱글쓰레드보다 약간 더 소요된다.
        // 하지만 동시 작업 수행이 가능하다(채팅 중에서 파일 다운로드)
        ThreadEx_C t1 = new ThreadEx_C();
        t1.start();
        startTime = System.currentTimeMillis();
 
        for(int i=0; i < 300; i++)
            System.out.printf("%s"new String("-"));
 
        System.out.print("소요시간1:" + (System.currentTimeMillis() - MultiThread.startTime));
    }
}
 
class ThreadEx_C extends Thread {
    public void run() {
        for(int i=0; i < 300; i++)
            System.out.printf("%s"new String("|"));
 
        System.out.print("소요시간2:" + (System.currentTimeMillis() - MultiThread.startTime));
    }
}
 

 

 

main thread
- 모든 자바 프로그램은 메인 쓰레드가 main() 메소드를 실행하면서 시작한다.
- return 을 만나면 실행을 종료한다.
- main 쓰레드는 작업 쓰레드를 만들어서 병렬로 코드를 실행할 수 있다.
  작업 쓰레드를 만들려면, Thread 클래스를 상속하거나 Runnable 인터페이스를 구현한다.
- 멀티 쓰레드는 실행중인 쓰레드가 하나라도 있다면, 프로세스는 종료되지 않는다.
- 멀티 쓰레드 작업 시에는 각 쓰레드 끼리 정보를 주고받을 수 있어 처리 과정의 오류를 줄일 수 있다.
- 프로세스끼리는 정보를 주고 받을 수 없다.


 

데몬(daemon) 쓰레드

- 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행한다.
- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
- 가비지 컬렉터, 자동저장, 화면자동갱신 등에 사용된다.
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.

- setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 한다.

 

 

public class DaemonThreadEx implements Runnable {
    static boolean autoSave = false;
 
    public static void main(String[] args) {
        Thread t = new Thread(new DaemonThreadEx()); // Thread(Runnable r)
        // run() 메소드를 가지고 있는 클래스의 객체를 매개변수로 준다.
        t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
        t.start();
 
        for(int i=1; i <= 20; i++) {
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) {
 
            }
            System.out.println(i);
 
            if(i == 5) autoSave = true;
        }
        System.out.println("프로그램을 종료합니다.");
    }
 
    public void run() {
        while(true) { // 무한루프
            try {
                Thread.sleep(3 * 1000); // 3초 마다
            } catch(InterruptedException e) {
 
            }
 
            // autoSava 의 값이 true 이면 autoSave()를 호출한다.
            if(autoSave) autoSave();
        }
    }
 
    private void autoSave() {
        System.out.println("작업 파일이 자동 저장되었습니다.");
    }
}
 
 

 

쓰레드의 생명 주기(Java Thread Life Cycle and Thread States)

 

 

1. New

   - 객체 생성

   - 쓰레드가 만들어진 상태로 아직 start() 메소드가 호출되지 않은 상태다.

 

2. Runnable (실행대기)
   - 쓰레드가 실행되기 위한 준비단계이다.
   - 코드 상에서 start() 메소드를 호출하면 run() 메소드에 설정된 쓰레드가 Runnable 상태로 진입한다.

3. Running (실행상태)
   - CPU를 점유하여 실행하고 있는 상태이며 run() 메서드는 JVM만이 호출 가능하다.
   - Runnable(준비상태)에 있는 여러 쓰레드 중 우선 순위를 가진 쓰레드가 결정되면

   - JVM이 자동으로 run() 메소드를 호출하여 쓰레드가 Running 상태로 진입한다.

4. Terminated (실행종료)
   - Running 상태에서 쓰레드가 모두 실행되고 난 후 완료 상태다.

   - run() 메소드 완료시 쓰레드가 종료되며, 그 쓰레드는 다시 시작할 수 없게 된다.

 

5. Blocked (일시 정지)
   - 사용하고자 하는 객체의 Lock이 풀릴 때까지 기다리는 상태다.
   - wait() 메소드에 의해 Blocked 상태가 된 쓰레드는 notify() 메소드가 호출되면 Runnable 상태로 간다.
   - sleep(시간) 메소드에 의해 Blocked 상태가 된 쓰레드는 지정된 시간이 지나면 Runnable 상태로 간다.

 

join() 메소드는 다른 쓰레드의 종료를 기다리는 것이다.

계산 작업을 하는 쓰레드가 모든 계산 작업을 마쳤을 때, 계산 결과값을 받아 이용하는 경우에 주로 사용한다.

 

 
public class JoinMain {
    public static void main(String[] args) {
        JoinWorkerThread thread = new JoinWorkerThread();
        thread.start(); // 작업 쓰레드 시작
 
        System.out.println("Thread가 종료될때까지 기다린다.");
        try {
            // 별도의 쓰레드에서 처리한 작업이 완료될 때까지 기다려야 하는 경우
            thread.join(); // 해당 작업 쓰레드가 종료될 때까지 main 쓰레드가 대기한다.
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread가 종료되었다.");
    }
}
 
class JoinWorkerThread extends Thread {
 
    @Override
    public void run() {
        for(int i = 0; i < 5; i++){
            System.out.println("JoinWorkerThread : "+ i);
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}

 

실행 결과

Thread가 종료될때까지 기다린다.
JoinWorkerThread : 0
JoinWorkerThread : 1
JoinWorkerThread : 2
JoinWorkerThread : 3
JoinWorkerThread : 4
Thread가 종료되었다.

 

Android 어플을 GitHub 에서 받아서 테스트 해보니 제대로 동작이 잘 안되어 수정해보려고 했으나 쓰레드에 대한 이해가 부족하여 쓰레드 자료/강좌를 찾아서 공부하고 있다.

"이것이 자바다" 유투브 동영상 강좌(https://www.youtube.com/watch?v=1mut1RwpEvw)에 설명이 잘 되어 있다.

Do IT 안드로이드 앱 프로그래밍 책 저자의 동영상을 들었을 때, 자바를 몰라도 안드로이드 어플 만들 수 있다고 한다.

물론 간단한 앱 테스트하는 것은 얼마든지 만들 수 있다.

하지만 응용을 해야 한다거나, 난이도 있는 어플을 만들려고 하면 그 분야의 지식을 깊게 만들어야 어플이 문제없이 동작된다는 걸 절감하게 된다.

블로그 이미지

Link2Me

,
728x90

"이것이 자바다" 유투브 동영상 강좌(https://www.youtube.com/watch?v=twd6mwUS1Bc)에 나오는 코드를 적고 테스트해보고 기록해둔다.

Socket을 활용하여 Clinet측에서 Server로 일대일 연결을 유지하면서 Client측에서 보낸 메세지를 Server측에서 수신하여 수신 받은 메세지를 다시 Client측으로 송신하는 프로그램 구현이다.




서버 소켓 코드

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;

        try {
            // 소켓 생성
            serverSocket = new ServerSocket();
            // 포트 바인딩
            serverSocket.bind(new InetSocketAddress("localhost", 5001));

            while (true) {
                System.out.println("[연결 기다림]");
                // 연결 수락
                socket = serverSocket.accept(); // 클라이언트가 접속해 오기를 기다리고, 접속이 되면 통신용 socket 을 리턴한다.
                // 연결된 클라이언트 IP 주소 얻기
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                System.out.println("[연결 수락함] " + isa.getHostName());

                byte[] bytes = null;
                String message = null;

                // 데이터 받기
                InputStream is = socket.getInputStream();
                bytes = new byte[100];
                int readByteCount = is.read(bytes);
                message = new String(bytes, 0, readByteCount, "UTF-8");
                System.out.println("[데이터 받기 성공] " + message);

                // 데이터 보내기
                OutputStream os = socket.getOutputStream();
                message = "Hello Client";
                bytes = message.getBytes("UTF-8");
                os.write(bytes);
                os.flush();
                System.out.println("[데이터 보내기 성공]");

                is.close();
                os.close();
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (!serverSocket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}



클라이언트 코드

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

public class ChatClient {

    public static void main(String[] args) {

        Socket socket = null;

        try {
            socket = new Socket();
            System.out.println("[연결 요청]");
            socket.connect(new InetSocketAddress("localhost", 5001));
            System.out.println("[연결 성공]");
           
            byte[] bytes = null;
            String message = null;

            OutputStream os = socket.getOutputStream();
            message = "Hello Server, I'm Client.";
            bytes = message.getBytes("UTF-8");
            os.write(bytes);
            os.flush();
            System.out.println("[데이터 보내기 성공]");
           
            InputStream is = socket.getInputStream();
            bytes = new byte[100];
            int readByteCount = is.read(bytes);
            message = new String(bytes,0,readByteCount,"UTF-8");
            System.out.println("[데이터 받기 성공] " + message);
           
            os.close();
            is.close();
           
        } catch (Exception e) {
            e.printStackTrace();
        }
       
        if (!socket.isClosed()) {
            try {
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

}


코드를 작성하고 실행하면 콘솔에 나오는 출력 결과를 확인하고 알 수 있다.


GitHub 에 간단하면서도 좋은 예제가 있다.

https://gist.github.com/junsuk5/f0ff2298e17853dc48e89f2dfc7bd985

블로그 이미지

Link2Me

,
728x90

안드로이드 인터페이스 처리하는 걸 많이 다뤄보지 않아서 그런지 인터페이스 사용을 자유롭게 하지 못한다.


"이것이 자바다" 를 유투브에서 인터페이스 강좌를 찾아서 들어보면 설명을 참 잘 해준다.

https://www.youtube.com/watch?v=8GcOYOd67KY


구글 검색으로 블로그에 적혀 있는 내용만으로 손쉽게 인터페이스 처리 코드를 만들줄 알면 고민되지 않겠지만 대게는 단순한 구현 예시만 나와 아직 초보를 탈출하지 못한 나에겐 어렵다.


로그인한 세션 정보를 받아서 다음 단계를 처리해야 하는 어플을 테스트하다보니 인터페이스 부분이 너무 약해서 막힌다.


가장 먼저 인터페이스를 정의한다.

OnCallbackListener.java

public interface OnCallbackListener<T> {
    void onSuccess(T object);
    void onFailure(Exception e);
}
 


두번째 단계는 위 그림에서 개발코드에 해당되는 SessionProcessTask.java 파일을 구현한다.

import android.content.Context;
import android.os.AsyncTask;

public class SessionProcessTask extends AsyncTask<Void, Void, String> {
    private Context mContext;
    private OnCallbackListener<String> mCallBack;
    public Exception mException;


    // 변수를 먼저 선언하고 ALT+ Insert 키를 생성자 생성시 아래와 같은 순서로 생성자 자동 생성 가능

    public SessionProcessTask(Context mContext, OnCallbackListener<String> mCallBack) {
        this.mContext = mContext;
        this.mCallBack = mCallBack;
    }

    @Override
    protected String doInBackground(Void... params) {

        try {
            // 백그라운드 작업할 코드를 여기에서 구현한다.
            return "Welcome to my blog.";

        } catch (Exception e) {
            mException = e;
        }
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        if (mCallBack != null) {
            if (mException == null) {
                mCallBack.onSuccess(result);
            } else {
                mCallBack.onFailure(mException);
            }
        }
    }
}


객체를 구현할 MainActivity.java 파일의 구현 내용이다.

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;

        // 객체 생성
        SessionProcessTask processTask = new SessionProcessTask(mContext, new OnCallbackListener<String>() {
            @Override
            public void onSuccess(String object) {
                Toast.makeText(mContext, "SUCCESS: "+object, Toast.LENGTH_LONG).show();
                ActionActivity android = new ActionActivity(mContext);
                android.btnTouch();
            }

            @Override
            public void onFailure(Exception e) {
                Toast.makeText(mContext, "ERROR: " + e.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
        processTask.execute();
    }

}


onSuccess 결과를 받으면 후속으로 

ActionActivity android = new ActionActivity(mContext);

android.btnTouch();

를 실행하도록 했다.


이제 이와 관련된 코드를 또다른 인터페이스를 통해 처리되도록 구현했다.

// OnLoadListener.java 파일

public interface OnLoadListener { 
    public void onLoadFinish();
}

// ImageLoader.java 파일

import android.content.Context;
import android.widget.Toast;

public class ImageLoader {
    private Context mContext;
    private OnLoadListener loadListener;

    public ImageLoader(Context context, OnLoadListener loadListener) {
        mContext = context;
        this.loadListener = loadListener;
    }

    public void start(){
        try {
            System.out.println("이미지를 로딩합니다.");
            Toast.makeText(mContext, "이미지를 로딩합니다.", Toast.LENGTH_LONG).show();
            Thread.sleep(1000);
            loadListener.onLoadFinish();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// ActionActivity.java 파일

import android.content.Context;
import android.widget.Toast;

public class ActionActivity implements OnLoadListener {
    private Context mContext;

    public ActionActivity(Context mContext) {
        this.mContext = mContext;
    }

    public void btnTouch(){
        ImageLoader imageLoader = new ImageLoader(mContext,this);
        imageLoader.start();
    }

    @Override
    public void onLoadFinish() {
        Toast.makeText(mContext, "토스트 팝업을 띄웁니다.", Toast.LENGTH_LONG).show();
        System.out.println("토스트 팝업을 띄웁니다.");
    }
}


Log 메시지를 통해서 확인할 수도 있지만, Toast 메시지 창을 띄워서 순차적으로 실행되는 결과를 확인할 수 있게 했다.

아직 응용력이 떨어지는 나를 위해 테스트하고 적어둔다.

구글링하다가 발견한 좋은 글이 와 닿는다.

인터넷에서 짜집기한 것만 읽지 말고, 책을 읽어라. 같은 책을 여러 번 읽어라. 꾸준히 해라.

하지만, 요즈음에는 "유투브 강좌를 열심히 봐라" 라고 하는게 더 맞을 거 같다.

좋은 강좌들이 너무 많이 오픈되어 있다.


테스트에 사용한 코드

Interface_src.zip


'안드로이드 > Interface' 카테고리의 다른 글

Android 인터페이스 상속 ISerialListener 예제  (0) 2019.12.12
Java Interface 예제  (0) 2019.11.18
Java Interface Example  (0) 2019.09.05
Java 인터페이스(interface) 개요  (0) 2019.08.20
Android Interface 예제 ★★★  (0) 2018.08.22
블로그 이미지

Link2Me

,