>  기사  >  Java  >  【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)

【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)

php是最好的语言
php是最好的语言원래의
2018-08-06 11:58:184331검색

동기화를 사용하는 이유는 무엇인가요?

Java에서는 다중 스레드 동시성 제어를 허용합니다. 여러 스레드가 동시에 공유 가능한 리소스 변수를 작동하는 경우(예: 데이터 추가, 삭제, 수정, 확인)
데이터가 부정확해지고 서로 충돌할 수 있습니다. 따라서 잠금은 스레드가 작업을 완료하기 전에 다른 스레드에 의해 호출되는 것을 방지하여 변수의 고유성과 정확성을 보장하는 데 사용됩니다.

1. 동기화 방법 동기화 키워드로 수정된 방법이 있습니다.
Java의 모든 객체에는 내장 잠금이 있으므로 이 키워드로 메소드를 수정하면 내장 잠금이 전체 메소드를 보호합니다. 이 메서드를 호출하기 전에 내장 잠금을 얻어야 합니다. 그렇지 않으면 차단됩니다.

다음과 같은 코드:

publicsynchronous void save(){}


참고: 이 때 정적 메서드를 호출하면 전체 클래스가 잠깁니다.

2. 코드 블록

동기화 키워드를 수정한 문장 블록입니다. ​ 이 키워드로 수정된 명령문 블록은 동기화를 달성하기 위해 기본 제공 잠금과 함께 자동으로 추가됩니다.

Code such as:

   synchronized(object){ 
    }

​ 참고: 동기화는 비용이 많이 드는 작업이므로 동기화 내용은 다음과 같아야 합니다. 최소화.

일반적으로 전체 방법을 동기화할 필요는 없으며, 동기화된 코드 블록을 사용하여 키 코드를 동기화하면 됩니다.

코드 예:

package com.xhj.thread;

    /**
     * 线程同步的运用
     * 
     * @author XIEHEJUN
     * 
     */
    public class SynchronizedThread {

        class Bank {

            private int account = 100;

            public int getAccount() {
                return account;
            }

            /**
             * 用同步方法实现
             * 
             * @param money
             */
            public synchronized void save(int money) {
                account += money;
            }

            /**
             * 用同步代码块实现
             * 
             * @param money
             */
            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }
        }

        class NewThread implements Runnable {
            private Bank bank;

            public NewThread(Bank bank) {
                this.bank = bank;
            }

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "账户余额为:" + bank.getAccount());
                }
            }

        }

        /**
         * 建立线程,调用内部类
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("线程1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("线程2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }

        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }

    }

3. 특수 도메인 변수(휘발성)를 사용하여 스레드 동기화 달성


a. 휘발성 키워드는 도메인 변수에 액세스하기 위한 잠금 없는 메커니즘을 제공합니다.

b. 도메인 다른 스레드에 의해 필드가 업데이트될 수 있음을 가상 머신에 알리는 것과 같습니다.

 c. 따라서 레지스터의 값을 사용하는 대신 필드가 사용될 때마다 다시 계산해야 합니다.
d.휘발성은 그렇지 않습니다. 원자 연산을 제공하며 사용할 수 없습니다. 최종 유형 변수를 수정하려면
예를 들면 다음과 같습니다.
위의 예에서는 스레드 동기화를 달성하기 위해 계정 앞에 휘발성 수정을 추가하기만 하면 됩니다.
코드 예:

      //只给出要修改的代码,其余代码与上同
        class Bank {
            //需要同步的变量加上volatile
            private volatile int account = 100;

            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }

참고: 멀티스레딩의 비동기화 문제는 주로 필드 읽기 및 쓰기에서 발생합니다. 도메인 자체에서 이 문제를 방지하는 경우 필드 작동 방법을 수정할 필요가 없습니다.

비동기 문제를 방지하려면 최종 필드, 잠금 보호 필드 및 휘발성 필드를 사용하세요.

4. 재진입 잠금을 사용하여 스레드 동기화 달성
동기화를 지원하기 위해 새로운 java.util.concurrent 패키지가 JavaSE5.0에 추가되었습니다.

ReentrantLock 클래스는 Lock 인터페이스를 구현하는 상호 배타적인 재진입 잠금입니다.

이는 동기화된 메서드 및 블록을 사용하는 것과 동일한 기본 동작 및 의미를 가지며 해당 기능을 확장합니다.

ReenreantLock 클래스의 일반적인 메서드는 다음과 같습니다.

ReentrantLock (): ReentrantLock 인스턴스 생성

    lock(): 잠금 획득

      Unlock(): 잠금 해제
  참고: ReentrantLock()에도 공정한 잠금을 생성할 수 있는 생성 메서드가 있지만 효율성이 크게 떨어질 수 있기 때문입니다. 해당 프로그램은 적합하지 않습니다.
사용을 권장합니다. 예:
위의 예를 바탕으로 다시 작성된 코드는 다음과 같습니다.
코드 예:

//只给出要修改的代码,其余代码与上同
        class Bank {
            
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }

참고: Lock 개체 및 동기화 키워드 선택 관련:


a. 둘 중 어느 것도 사용하지 않는 것이 가장 좋습니다. java.util.concurrent 패키지에서 제공하는 메커니즘을 사용하면 사용자가 모든 잠금 관련 코드를 처리하는 데 도움이 됩니다. C b. 동기화된 키워드가 사용자의 요구를 충족할 수 있으면 코드를 단순화할 수 있으므로 동기화를 사용합니다. 더 높은 수준의 기능이 필요한 경우 이때 ReentrantLock 클래스를 사용합니다. 일반적으로 최종 코드에서 잠금이 해제됩니다.

스레드는 다른 스레드에 영향을 주지 않고 마음대로 자체 변수 복사본을 수정할 수 있습니다.


ThreadLocal 클래스의 일반적인 메서드

    ThreadLocal() : 创建一个线程本地变量 
    get() : 返回此线程局部变量的当前线程副本中的值 
    initialValue() : 返回此线程局部变量的当前线程的"初始值" 
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

    例如: 
        在上面例子基础上,修改后的代码为:     
    代码实例: 

//只改Bank类,其余代码与上同
        public class Bank{
            //使用ThreadLocal类管理共享变量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }

    注:ThreadLocal与同步机制 
        a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 
        b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方法

6.使用阻塞队列实现线程同步

    前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 
    使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 
    本小节主要是使用LinkedBlockingQueue来实现线程的同步 
    LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。 
    队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~ 
    
   LinkedBlockingQueue 类常用方法 
    LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
    size() : 返回队列中的元素个数 
    take() : 移除并返回队头元素,如果队列空则阻塞 
    
   代码实例: 
        实现商家生产商品和买卖商品的同步

【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)

 1 package com.xhj.thread;
 2 
 3 import java.util.Random;
 4 import java.util.concurrent.LinkedBlockingQueue;
 5 
 6 /**
 7  * 用阻塞队列实现线程同步 LinkedBlockingQueue的使用
 8  * 
 9  * @author XIEHEJUN
10  * 
11  */
12 public class BlockingSynchronizedThread {
13     /**
14      * 定义一个阻塞队列用来存储生产出来的商品
15      */
16     private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
17     /**
18      * 定义生产商品个数
19      */
20     private static final int size = 10;
21     /**
22      * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
23      */
24     private int flag = 0;
25 
26     private class LinkBlockThread implements Runnable {
27         @Override
28         public void run() {
29             int new_flag = flag++;
30             System.out.println("启动线程 " + new_flag);
31             if (new_flag == 0) {
32                 for (int i = 0; i < size; i++) {
33                     int b = new Random().nextInt(255);
34                     System.out.println("生产商品:" + b + "号");
35                     try {
36                         queue.put(b);
37                     } catch (InterruptedException e) {
38                         // TODO Auto-generated catch block
39                         e.printStackTrace();
40                     }
41                     System.out.println("仓库中还有商品:" + queue.size() + "个");
42                     try {
43                         Thread.sleep(100);
44                     } catch (InterruptedException e) {
45                         // TODO Auto-generated catch block
46                         e.printStackTrace();
47                     }
48                 }
49             } else {
50                 for (int i = 0; i < size / 2; i++) {
51                     try {
52                         int n = queue.take();
53                         System.out.println("消费者买去了" + n + "号商品");
54                     } catch (InterruptedException e) {
55                         // TODO Auto-generated catch block
56                         e.printStackTrace();
57                     }
58                     System.out.println("仓库中还有商品:" + queue.size() + "个");
59                     try {
60                         Thread.sleep(100);
61                     } catch (Exception e) {
62                         // TODO: handle exception
63                     }
64                 }
65             }
66         }
67     }
68 
69     public static void main(String[] args) {
70         BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
71         LinkBlockThread lbt = bst.new LinkBlockThread();
72         Thread thread1 = new Thread(lbt);
73         Thread thread2 = new Thread(lbt);
74         thread1.start();
75         thread2.start();
76 
77     }
78 
79 }

【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)

注:BlockingQueue定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

  add()方法会抛出异常

  offer()方法返回false

  put()方法会阻塞

7.使用原子变量实现线程同步

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
代码实例:
只改Bank类,其余代码与上面第一个例子同

【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)

 1 class Bank {
 2         private AtomicInteger account = new AtomicInteger(100);
 3 
 4         public AtomicInteger getAccount() {
 5             return account;
 6         }
 7 
 8         public void save(int money) {
 9             account.addAndGet(money);
10         }
11     }

【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)

补充--原子操作主要有:
  对于引用变量和大多数原始变量(long和double除外)的读写操作;
  对于所有使用volatile修饰的变量(包括long和double)的读写操作。

 相关文章:

关于Java线程同步和同步方法的详解

详解Java多线程编程中的线程同步方法

위 내용은 【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.