preface
In multithreading development, synchronous control is an essential means. The implementation of synchronization requires locks, and Java provides two basic types of locks: synchronized and Lock. Both types of locks are very common, but they have their pros and cons.
Synchronized usage
Synchronized is a Java keyword and one of the most widely used synchronization tools. When it is used to modify a method or a block of code, it ensures that at most one thread is executing the code at a time, and it is worth noting that it relies on the JVM for synchronization at the software level.
The usage of synchronized is very simple. It can be directly used to modify code blocks. Generally, it can be used to modify methods and code blocks.
Modification methods
Synchronized modification methods are divided into two cases:
- Modifier instance method, used to lock the current instance, before entering the synchronization code to obtain the current instance lock.
- Modifies a static method that locks the current class object before entering the synchronized code.
Decorated instance method
As the name implies, it modifiers the instance method in the class, and the default is the current object as the lock object, and an object has only one lock, so only one thread can execute the synchronized method at a time, until the thread is finished executing the method, other threads can continue to execute the synchronized method. Example code is as follows:
Public implements Runnable{// static int TEST_INT = 0; // Synchronized void public synchronized voidincrease(){
TEST_INT++;
}
@Override
public void run() {
for(int i=1; i<=100000; i++){ increase(); Public static void main(String[] args) throws InterruptedException {// Instantiate the object SyncTest instance = new SyncTest(); Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(TEST_INT); }}Copy the code
If you run the above program, the result is 200000, because only one SyncTest object is instantiated in the main function. Therefore, only one thread can acquire the lock of the object. When one thread obtains the lock of the object, the other thread cannot acquire the lock of the object. Therefore, other synchronized instance methods of the object cannot be accessed, although non-synchronized methods of the object can still be accessed by other threads.
If there are more than one instance of an object, it is not safe to modify the main function. If there are more than one instance of an object, the main function can be modified.
Public static void main(String[] args) throws InterruptedException {// Each Thread instantiates a SyncTest object Thread T1 =new Thread(new) SyncTest()); Thread t2=new Thread(new SyncTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(TEST_INT); }Copy the code
After running the program, it will be found that the result is always less than 200000, indicating that synchronized does not play the role of synchronization, indicating that the modified instance method can only act on instance objects, not on class objects.
Decorated static methods
To synchronized to the class object itself, you can use it to modify static methods in a class. Change the increase method to static and create two new threads in main:
// Synchronized static void public synchronized static voidincrease(){ TEST_INT++; } public static void main(String[] args) throws InterruptedException {// Each Thread instantiates a SyncTest object Thread T1 =new Thread(new) SyncTest()); Thread t2=new Thread(new SyncTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(TEST_INT); }Copy the code
Synchronized is applied to the class object itself, and its lock object is the class object of the current class. Therefore, no matter how many object instances are instantiated, the synchronized method can only be executed by one thread at a time.
Synchronized code block
Besides synchronous instance method and static method, you can also use synchronized synchronized code block, and in some cases, we may need to synchronize only one piece of code, assume code method dimension is too large, synchronizing the entire method will directly affect the operating efficiency of program, this kind of circumstance synchronized code block is very appropriate, example code is as follows:
public class SyncTest implements Runnable{ public static SyncTest instance = new SyncTest(); Public static int TEST_INT = 0; @Override public voidrun() {
synchronized (instance) {
for(int i = 1; i <= 100000; i++) { TEST_INT++; }}} public static void main(String[] args) throws InterruptedException {Thread t1=new if each Thread instantiates a SyncTest object Thread(new SyncTest()); Thread t2=new Thread(new SyncTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(TEST_INT); }}Copy the code
In the above code, instance is synchronized in the run() method, and the output is 200,000. The effect of synchronization is achieved because each time a thread enters a synchronized code block, the current thread is required to hold the instance lock, and the other threads must wait, ensuring that only one thread executes the synchronized code block at a time.
Lead to the Lock
Although synchronized is relatively simple to use and effective, it still has some drawbacks, such as the release of locks.
When a thread executes a synchronized program, it acquires the corresponding lock. Other threads have to wait until the thread releases the corresponding lock, and the thread releases the lock in either of the following ways:
- The thread completes the block and releases the lock.
- When an exception occurs during thread execution, the JVM causes the thread to release the lock automatically.
Because synchronized is implemented by the JDK, no programmer needs to write code to control locking and releasing. The releasing mechanism has a lot of disadvantages, for example, if there are time consuming to get to the threads of the lock procedures, such as waiting for IO or be blocked, then no timely release the lock, then other threads must wait, wasted a lot of time, the result is obviously not we want to see, and what method can solve?
This is where Lock comes in handy. Lock is an interface provided under the Java Concurrency Toolkit that also enables synchronous access.
Different from synchronized, Lock requires the programmer to manually control the Lock and release. It does not automatically release the Lock. If the Lock is not released manually, the thread will always occupy the Lock, which may cause deadlock.
The Lock usage
Lock is an interface. If you open the source code, you can see that the code defines these methods:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
void unlock();
Condition newCondition();
}
Copy the code
Lock(), lockInterruptibly(), tryLock(), unlock(), unlock(), unlock(), unlock(), unlock(), and newCondition() return a Condition interface. The Condition interface replaces the use of Object monitor methods by acting as object.wait () and Object.notify() for thread waiting and notification.
Lock (); Lock (); Lock (); Lock ();
Lock lock = new ReentrantLock(); lock.lock(); try { //............. Executive procedure.......... } finally { lock.unlock(); }Copy the code
Calling the Lock release method by catching an exception ensures that the Lock will be released successfully even if an exception occurs in the program.
It’s worth noting that Lock is just an interface, and to use it as a synchronization tool, you must first instantiate a subclass of it, and ReentrantLock in the code is a subclass of Lock.
Subclasses: already
ReentrantLock is a very powerful subclass of Lock. It stands for “ReentrantLock.” So what does ReentrantLock mean? More on this later, showing you how to use ReentrantLock.
public class LockTest implements Runnable {
public Lock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for(int j = 0; j<100000; j++){ lock.lock(); try { i++; } finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { LockTest lt = new LockTest(); Thread t1 = new Thread(lt); Thread t2 = new Thread(lt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}Copy the code
We add ReentrantLock to run() of LockTest to protect the critical section resource I, to ensure the safety of multithreading operations on the critical section resource, execute main method, you can see the result successfully output 200000. ReentrantLock does play the role of synchronization.
Returning to the subject of reentrant locks, so called because they can be entered repeatedly, for example, modify the code in the run() method:
@Override
public void run() {
for (int j = 0;j<100000;j++){
lock.lock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
lock.unlock();
}
}
}
Copy the code
Run the main method, the normal output of the code 200000. That lock can be continuous use, because if you can’t be continuous use, so when the second acquiring a lock, will not released because the first lock and have been waiting for, at the same time the release of the second lock and order must be two lock acquisition and implement i++ program to realize, this is equivalent to the thread deadlock with his. Note, of course, that the thread must acquire the lock as many times as it releases it, or it will throw an exception.
Read/write separation lock: ReadWriteLock
In addition to the Lock interface, the Java API provides another read-write separation Lock called ReadWriteLock. ReadWriteLock was introduced after JDK1.5. As a read/write separation lock, it helps reduce lock contention and improve system performance.
Using a lock separation mechanism to improve performance is easy to understand. For example, three threads A1, A2, and A3 perform write operations, and three threads B1, B2, and B3 perform read operations. With reentrant or synchronized, all reads, reads, and writes are, in theory, serial. When B1 reads, B2 and B3 must wait. Since the read operation does not damage the integrity of the data, this wait is unreasonable. This is where read-write separation locks come in handy, allowing multiple read operations to be executed in parallel.
Note that read/write separation locks are only for reading and writing parallelism, and are still mutually exclusive between read/write and write.
- Read – Read not mutually exclusive: do not block between reads;
- Read-write mutex: Read blocks write, and write blocks read;
- Write – write mutex: write blocks;
That’s the concept, and here’s how it works. ReadWriteLock:
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
Copy the code
ReadWriteLock is an interface that provides only two methods: readLock and writeLock, which return the Lock interface.
ReadWriteLock is an interface class that needs to be instantiated. ReadWriteLock subclass is ReentrantReadWriteLock. ReadWriteLock is an interface class that needs to be instantiated.
public class ReadWriteLockDemo {
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock writeLock = readWriteLock.readLock();
private static Lock readLock = readWriteLock.readLock(); private int i; Public int ReadValue(Lock Lock) throws Exception {try {lock. Lock (); Thread.sleep(1000);returni; } finally { lock.unlock(); }} // Write the method public voidsetValue(Lock lock, int value) throws Exception {
try {
lock.lock();
Thread.sleep(1000);
i = value;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readRunnable = new Runnable() {
@Override
public void run() {
try {
demo.ReadValue(readLock); } catch (Exception e) { e.printStackTrace(); }}}; Runnable writeRunnable = newRunnable() {
@Override
public void run() { try { demo.setValue(writeLock, new Random().nextInt()); } catch (Exception e) { e.printStackTrace(); }}};for (int i = 0; i < 20; i++) {
new Thread(readRunnable).start();
}
for(int j = 0; j < 2; j++) { new Thread(writeRunnable).start(); }}}Copy the code
The ReadWriteLockDemo class defines an instance of ReentrantReadWriteLock and an instance of readLock. The readLock class defines an instance of ReentrantReadWriteLock and an instance of readLock. The time-consuming operation is simulated with Thread.sleep, corresponding to read and write time respectively. The main function defines the reader thread and the writer thread, and uses the for loop to open 20 reader threads and 2 write threads.
The above code is a simple read and write separation operation, after normal operation, the program ended in more than two seconds, which shows that the reading threads are parallel, and the writing threads will block each other, which also confirms the previous conclusion.
So much for read/write separation locks. There are a lot of great uses for ReentrantReadWriteLock itself that I won’t expand on here.
Lock and synchronized
Finally, a comparison between Lock and synchronized is a cliche.
Lock is an interface, synchronized is a Java keyword, and synchronized is a built-in language implementation.
2, synchronized automatically release the Lock by the program, and Lock needs to be manually released by the programmer, to avoid deadlock;
3. Lock can make the thread waiting for the Lock respond to the interrupt, but synchronized cannot. When synchronized is used, the waiting thread will wait forever and cannot respond to the interrupt.
4. Lock can tell whether a Lock is successfully acquired, but synchronized cannot.
5. Lock supports reentrant locking, but synchronized does not;
6. The scope of a synchronized lock is the entire method or code block; Lock is the method invocation method, more flexibility;
7. ReadWriteLock can improve the read efficiency of multiple threads, while synchronized cannot.
In addition, since JDK1.6, the performance of synchronized has been greatly optimized. If the resource competition is not fierce, that is, there are not many threads, the performance of synchronized and Lock is about the same. If the resource competition is fierce, The performance of using Lock is much better than that of synchronized.
So, again, it’s best to choose the right technology for each scenario.