Why use synchronization?
Java allows multi-threaded concurrency control. When multiple threads operate a shareable resource variable at the same time (such as adding, deleting, modifying, and checking data),
will lead to inaccurate data. , conflict with each other, so a synchronization lock is added to avoid being called by other threads before the thread completes the operation,
thereby ensuring the uniqueness and accuracy of the variable.
1. Synchronization method
It is a method modified by the synchronized keyword.
Since every object in Java has a built-in lock, when a method is modified with this keyword,
the built-in lock will protect the entire method. Before calling this method, you need to obtain the built-in lock, otherwise it will be blocked.
Code such as:
public synchronized void save(){}
Note: The synchronized keyword can also modify a static method. At this time, if the static method is called, the entire Class
2. Synchronized code block
is a statement block modified with the synchronized keyword.
The statement block modified by this keyword will automatically be added with a built-in lock to achieve synchronization
Code such as:
synchronized(object){ }
Note: Synchronization is a high-cost method operation, so synchronized content should be minimized.
Usually there is no need to synchronize the entire method, just use synchronized code blocks to synchronize key codes.
Code example:
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. Use special domain variables (volatile) to achieve thread synchronization
a. The volatile keyword is the domain Variable access provides a lock-free mechanism,
b. Using volatile to modify a domain is equivalent to telling the virtual machine that the domain may be updated by other threads,
c. Therefore, it must be recalculated every time the domain is used, Instead of using the value in the register
d.volatile does not provide any atomic operations, nor can it be used to modify final type variables
For example:
In the above example, just add in front of account Modify volatile to achieve thread synchronization.
Code example:
//只给出要修改的代码,其余代码与上同 class Bank { //需要同步的变量加上volatile private volatile int account = 100; public int getAccount() { return account; } //这里不再需要synchronized public void save(int money) { account += money; } }
Note: The non-synchronization problem in multi-threading mainly occurs in reading and writing the domain. If the domain itself avoids this problem, there is no need to modify the operation. domain methods.
Use final fields, lock-protected fields and volatile fields to avoid asynchronous problems.
4. Use reentrancy locks to achieve thread synchronization
A new java.util.concurrent package has been added in JavaSE5.0 to support synchronization.
The ReentrantLock class is a reentrant, mutually exclusive lock that implements the Lock interface.
It has the same basic behavior and semantics as using synchronized methods and blocks, and extends its capabilities
ReenreantLock Commonly used methods of the class are:
ReentrantLock(): Create a ReentrantLock instance
lock(): Obtain the lock
unlock(): Release the lock
Note: There is another option for ReentrantLock() Create a fair lock construction method, but because it can greatly reduce program running efficiency, it is not recommended to use
For example:
Based on the above example, the rewritten code is:
Code example:
//只给出要修改的代码,其余代码与上同 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(); } } }
Note: Regarding the selection of Lock object and synchronized keyword:
a. It is best not to use either, and use a mechanism provided by the java.util.concurrent package.
It can help users deal with all lock-related codes.
B. If the synchronized keyword can meet the needs of the user, use synchronized, because it can simplify the code
C. If you need more advanced features, use the ReentrantLock class. At this time, pay attention to release the lock in time, otherwise Deadlock will occur, usually the lock is released in the finally code
The copies of this variable are independent of each other, so that each thread can modify its own copy of the variable at will without affecting other threads.
Common methods of ThreadLocal class
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)的读写操作。
相关文章:
The above is the detailed content of 【java】Why use synchronization? About thread synchronization (7 ways). For more information, please follow other related articles on the PHP Chinese website!