동기화를 사용하는 이유는 무엇인가요?
Java에서는 다중 스레드 동시성 제어를 허용합니다. 여러 스레드가 동시에 공유 가능한 리소스 변수를 작동하는 경우(예: 데이터 추가, 삭제, 수정, 확인)
데이터가 부정확해지고 서로 충돌할 수 있습니다. 따라서 잠금은 스레드가 작업을 완료하기 전에 다른 스레드에 의해 호출되는 것을 방지하여 변수의 고유성과 정확성을 보장하는 데 사용됩니다.
1. 동기화 방법 동기화 키워드로 수정된 방법이 있습니다.
Java의 모든 객체에는 내장 잠금이 있으므로 이 키워드로 메소드를 수정하면 내장 잠금이 전체 메소드를 보호합니다. 이 메서드를 호출하기 전에 내장 잠금을 얻어야 합니다. 그렇지 않으면 차단됩니다.
다음과 같은 코드:
참고: 이 때 정적 메서드를 호출하면 전체 클래스가 잠깁니다.
동기화 키워드를 수정한 문장 블록입니다. 이 키워드로 수정된 명령문 블록은 동기화를 달성하기 위해 기본 제공 잠금과 함께 자동으로 추가됩니다.
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(); } }
a. 휘발성 키워드는 도메인 변수에 액세스하기 위한 잠금 없는 메커니즘을 제공합니다.
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에 추가되었습니다.
이는 동기화된 메서드 및 블록을 사용하는 것과 동일한 기본 동작 및 의미를 가지며 해당 기능을 확장합니다.
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(); } } }
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() : 移除并返回队头元素,如果队列空则阻塞
代码实例:
实现商家生产商品和买卖商品的同步
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 }
注:BlockingQueue
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞
7.使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
代码实例:
只改Bank类,其余代码与上面第一个例子同
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 }
补充--原子操作主要有:
对于引用变量和大多数原始变量(long和double除外)的读写操作;
对于所有使用volatile修饰的变量(包括long和double)的读写操作。
相关文章:
위 내용은 【java】동기화를 사용하는 이유는 무엇입니까? 스레드 동기화 정보(7가지 방법)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!