[Java] 동기화와 Synchronized 키워드

박상군·2021년 12월 13일
0

Java

목록 보기
3/4
post-thumbnail

"이젠 비동기 없인 살 수 없는 몸이 되어버렸어"

이번에 안드로이드 프로젝트를 진행하면서 동기화에 관련된 이슈가 발생해 다시 한번 동기화의 중요성을 깨닫고 정리하게 되었습니다.

동기와 비동기의 차이점을 쉽게 설명하자면..

예를 들어 1번부터 10번까지 10개의 사진을 한 번에 다운로드한다고 했을 때

  • 동기방식은 1번 사진을 다운로드하고 끝나면 2번 사진을 다운로드하고...
    이런 식으로 한 작업이 끝나야 다음 작업을 실행하는 방식입니다.
  • 비동기 방식은 1번 사진을 다운로드하고 바로 2번 사진을 다운로드하고...
    이렇게 다른 작업의 종료 여부와 상관없이 다음 작업을 실행하는 방식입니다.

이번에 동기화 이슈가 발생한 기능은 간단하게 여러 대의 기기에 압축한 zip파일을 전송하는 기능인데, 통신 방식으로 Retrofit을 사용하다 보니 한 번에 여러 대의 기기로 전송 시 각 기기마다 같은 파일인데도 불구하고 여러 번 압축하는 문제가 발생하였고, 기능 자체에는 문제가 없지만 성능면에서 떨어질 수 있기 때문에 이 방식을 파일 압축시 처음 한 번만 압축 후 생성된 zip파일을 나머지 기기에 전송하는 식으로 변경하였습니다.

굉장히 간단한 문제지만 동기화와 비동기에 대해 지식이 부족하다면 처리하기 힘든 문제일 수도 있다고 생각했습니다.

문제가 발생한 코드입니다.

@Subscribe
    public void onVerifyDoneEvent(VerifySuccessEvent oEvent){
        if (oEvent.isSuccess()){
            ArrayList<String> aOItem = oEvent.getFileList();
            if(aOItem != null && aOItem.size() > 0){
                ZipFromFileListTask oTask = new ZipFromFileListTask(aOItem,new ICreateZipCallback() {
                    @Override
                    public void onICreateZipCallback(boolean result, String sFilePath, String sMd5, String sMes) {
                        if(result){
                            EventBus.getDefault().post(new ZipSuccessEvent(true, sFilePath, sMd5, oEvent.getDevice(), oEvent.getExecItem()));
                        } 
                    }
                });
            }else{
                EventBus.getDefault().post(new UploadSuccessEvent(true, oEvent.getDevice(), oEvent.getExecItem()));
            }
        }
    }

기존 코드는 이런식으로 Eventbus를 통해 들어온 이벤트가 성공했으면 이벤트의 파일리스트를 확인하고 파일리스트가 비어있지 않다면 미리 만들어놓은 ZipFromFileListTask를 사용하여 zip파일을 만들고 만든 파일의 경로를 또다른 이벤트로 넘기는 함수입니다.

이때 예를들어 파일을 5대에 기기에 전송하면 5개의 스레드들이 이 함수로 들어오기 때문에 각 스레드들은 똑같은 파일을 한번씩 압축하여 총 5번 압축하여 전송하는 번거로움이 있습니다.

이제 수정한 코드를 살펴보면

@Subscribe
    public synchronized void onVerifyDoneEvent(VerifySuccessEvent oEvent){
        if (oEvent.isSuccess()){
            ArrayList<String> aOItem = oEvent.getFileList();
            if(aOItem != null && aOItem.size() > 0){
                File file = new File(filePath);
                String md5 = CoreUtils.calMD5(file);
                if (file.exists()){
                    EventBus.getDefault().post(new ZipSuccessEvent(true, filePath, md5, oEvent.getDevice(), oEvent.getExecItem()));
                }
                ZipFromFileListTask oTask = new ZipFromFileListTask(aOItem,new ICreateZipCallback() {
                    @Override
                    public void onICreateZipCallback(boolean result, String sFilePath, String sMd5, String sMes) {
                        if(result){
                            filePath = sFilePath;
                            EventBus.getDefault().post(new ZipSuccessEvent(true, sFilePath, sMd5, oEvent.getDevice(), oEvent.getExecItem()));
                        } 
                      }
                    }
                });
            }else{
                EventBus.getDefault().post(new UploadSuccessEvent(true, oEvent.getDevice(), oEvent.getExecItem()));
            }
        }
    }

synchronized 키워드를 이용해 한번에 하나의 스레드만이 이 함수를 사용할 수 있게 합니다.
처음 들어온 스레드에서 파일의 존재 여부를 확인하고 파일이 존재하지 않는다면 위의 기존 코드와 같이 새로 파일을 생성한 후 생성한 파일의 경로를 filePath 변수에 저장합니다.
함수가 끝난후 다음부터 들어오는 스레드들은 미리 만들어진 경로의 파일 유무를 확인하여 그 파일로 이벤트를 보내게 됩니다.


이렇게 여러 스레드를 사용하는 비동기처리에서 발생하는 동기화 이슈는

동기와 비동기의 차이점만 알면 굉장히 유용하게 사용할 수 있습니다. 👍

0개의 댓글