Why synchronization is required
Java allows multi-thread concurrency control. When multiple threads operate a shared Resource variables (such as addition, deletion, modification and query of data) will lead to inaccurate data and conflicts with each other. Therefore, a synchronization lock is added to avoid being called by other threads before the thread completes the operation, thereby ensuring that the thread Uniqueness and accuracy of variables.
1. Example
For example, if a bank account is operated by two threads at the same time, one takes 100 yuan and the other deposits 100 yuan. Assume that the account originally has 0 blocks. If the withdrawal thread and the deposit thread occur at the same time, what will happen? The withdrawal was unsuccessful and the account balance is 100. The withdrawal was successful and the account balance is 0. But which balance corresponds to which? It's hard to tell clearly, so the problem of multi-thread synchronization arises.
2. Situation without synchronization
For example, if a bank account is operated by two threads at the same time, one takes 100 yuan and the other deposits 100 yuan. . Assume that the account originally has 0 blocks. If the withdrawal thread and the deposit thread occur at the same time, what will happen? If the money withdrawal is unsuccessful, the account balance is 100. If the money withdrawal is successful, the account balance is 0. But which balance corresponds to which? It's hard to tell clearly, so the problem of multi-thread synchronization arises.
public class Bank { private int count =0;//账户余额 //存钱 public void addMoney(int money){ count +=money; System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public void subMoney(int money){ if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
package threadTest; public class SyncThreadTest { public static void main(String args[]){ final Bank bank=new Bank(); Thread tadd=new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } bank.addMoney(100); bank.lookMoney(); System.out.println("\n"); } } }); Thread tsub = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(true){ bank.subMoney(100); bank.lookMoney(); System.out.println("\n"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); tsub.start(); tadd.start(); } }
Run result:
1502542307917取出:100 账号余额:100 1502542308917存进:100 1502542308917取出:100 账号余额:0 账号余额:0 1502542309917存进:100 账号余额:0 1502542309917取出:100 账号余额:0
A non-thread safety problem occurs at this time, because two threads access an unsynchronized method at the same time. If these two threads operate instances in the business object at the same time, variables, non-thread safety issues may occur.
Solution: Just add the synchronized keyword in front of public void run().
3. Synchronization method
synchronized keyword modification method
There is a synchronized keyword modification method. 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 static methods. If the static method is called at this time, the entire class will be locked.
public class Bank { private int count =0;//账户余额 //存钱 public synchronized void addMoney(int money){ count +=money; System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public synchronized void subMoney(int money){ if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
Run result:
余额不足 账号余额:0 1502543814934存进:100 账号余额:100 1502543815934存进:100 账号余额:200 1502543815934取出:100 账号余额:100
Thread synchronization is achieved in this way
Synchronization code block
That is, modified by the synchronized keyword block of statements.
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.
public class Bank { private int count =0;//账户余额 //存钱 public void addMoney(int money){ synchronized (this) { count +=money; } System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public void subMoney(int money){ synchronized (this) { if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; } System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
The running results are as follows:
余额不足 账户余额:0 余额不足 账户余额:100 1502544966411存进:100 账户余额:100 1502544967411存进:100 账户余额:100 1502544967411取出:100 账户余额:100 1502544968422取出:100
This also achieves thread synchronization, and the operating efficiency is higher than method synchronization. Synchronization is a high-cost operation, so synchronization should be minimized. Content. Usually there is no need to synchronize the entire method, just use synchronized code blocks to synchronize key code.
Use special domain variables (volatile) to achieve thread synchronization
a. The volatile keyword provides a lock-free mechanism for access to member variables;
b. Using volatile to modify member variables is equivalent to telling the virtual machine that the field may be updated by other threads;
c. Therefore, each time the member variable is used, it must be recalculated instead of using the value in the register. Value;
d.volatile does not provide any atomic operations, nor can it be used to modify final type variables.
Bank.java code is as follows:
package com.thread.demo; /** * Created by HJS on 2017/8/12. */ public class Bank { private volatile int count =0;//账户余额 //存钱 public void addMoney(int money){ synchronized (this) { count +=money; } System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public void subMoney(int money){ synchronized (this) { if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; } System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
Running result:
余额不足 账户余额:0 余额不足 账户余额:100 1502546287474存进:100 账户余额:100 1502546288474存进:100 1502546288474取出:100 账户余额:100
At this time, the order is messed up again, indicating that there is another synchronization problem, because volatile cannot guarantee atomic operations. Caused by this, volatile cannot replace synchronized. In addition, volatile will prevent the compiler from optimizing the code, so if you can't use it, don't apply it. Its principle is that every time a thread wants to access a volatile-modified variable, it reads it from the memory instead of reading it from the cache, so the variable value accessed by each thread is the same. This ensures synchronization.
Using 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.
Commonly used methods of the ReenreantLock class are:
ReentrantLock(): Create a ReentrantLock instance
lock(): Obtain the lock
unlock(): Release Lock
Note: ReentrantLock() also has a construction method that can create a fair lock, but it is not recommended because it can greatly reduce the running efficiency of the program.
Bank.java code is modified as follows:
public class Bank { private int count = 0;// 账户余额 //需要声明这个锁 private Lock lock = new ReentrantLock(); // 存钱 public void addMoney(int money) { lock.lock();//上锁 try{ count += money; System.out.println(System.currentTimeMillis() + "存进:" + money); }finally{ lock.unlock();//解锁 } } // 取钱 public void subMoney(int money) { lock.lock(); try{ if (count - money < 0) { System.out.println("余额不足"); return; } count -= money; System.out.println(+System.currentTimeMillis() + "取出:" + money); }finally{ lock.unlock(); } } // 查询 public void lookMoney() { System.out.println("账户余额:" + count); } }
Running result:
余额不足 账户余额:0 1502547439892存进:100 账户余额:100 1502547440892存进:100 账户余额:200 1502547440892取出:100 账户余额:100
Note: Regarding the selection of Lock object and synchronized keyword:
a. It is best to use neither of them, and use a mechanism provided by the java.util.concurrent package to help users handle all lock-related code.
b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码。
c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。
使用局部变量实现线程同步
代码如下:
public class Bank { private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { // TODO Auto-generated method stub return 0; } }; // 存钱 public void addMoney(int money) { count.set(count.get()+money); System.out.println(System.currentTimeMillis() + "存进:" + money); } // 取钱 public void subMoney(int money) { if (count.get() - money < 0) { System.out.println("余额不足"); return; } count.set(count.get()- money); System.out.println(+System.currentTimeMillis() + "取出:" + money); } // 查询 public void lookMoney() { System.out.println("账户余额:" + count.get()); } }
运行结果如下:
复制代码 余额不足 账户余额:0 余额不足 1502547748383存进:100 账户余额:100 账户余额:0 余额不足 账户余额:0 1502547749383存进:100 账户余额:200
看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。
ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
注:ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式。
php中文网,大量的免费Java入门教程,欢迎在线学习!
The above is the detailed content of How to synchronize Java. For more information, please follow other related articles on the PHP Chinese website!