Home  >  Article  >  Java  >  Example analysis of the synchronized keyword and thread safety issues in Java

Example analysis of the synchronized keyword and thread safety issues in Java

高洛峰
高洛峰Original
2017-01-05 14:47:481041browse

First, let’s review the basic use of synchronized:

synchronized code block, the modified code becomes a synchronized statement block, and its scope is to call the object of this code block. We are using the synchronized keyword When you can, narrow the scope of the code segment as much as possible. If you can add synchronization to the code segment, don't add synchronization to the entire method. This is called reducing the granularity of the lock and making the code more concurrent.

synchronized method, the modified method becomes a synchronized method, its scope is the entire method, and the target object is the object that calls this method.

Synchronized static method modifies a static static method. Its scope is the entire static method, and its target is all objects of this class.

The scope of the synchronized class is the part of synchronized(className.class) enclosed in parentheses after Synchronized, and the objects it acts on are all objects of this class.

synchronized() () is the locked object. synchronized(this) only locks the object itself. The synchronized method called by different objects of the same class will not be locked, and synchronized(className .class) implements the global lock function. All objects of this class that call this method are affected by the lock. In addition, a specific object can be added to () to lock a specific object.

synchronized (object) {
 //在同步代码块中对对象进行操作 
}

synchronized keyword and thread safety
I thought that using the synchronized keyword to wrap the code would make thread synchronization safe. Tested it. Found out to be completely wrong. synchronized must be used correctly to be truly thread safe. . . Although I know this way of writing, I always thought that I used the wrong method due to laziness.
It seems that the foundation has not been laid yet. Still need to review and strengthen! It is unforgivable to make this kind of mistake at work. You must know that wherever the synchronized keyword is used, the data is sensitive! Sweat a lot. . .
Post the code first:

package com; 
  
public class ThreadTest { 
 public static void main(String[] args) { 
  MyThread m1 = new MyThread(1); 
  MyThread m2 = new MyThread(2); 
  m1.start(); 
  m2.start(); 
 } 
} 
  
final class MyThread extends Thread { 
 private int val; 
  
 public MyThread(int v) { 
  val = v; 
 } 
 //这种做法其实是非线程安全的 
 public synchronized void print1(int v) { 
  for (int i = 0; i < 100; i++) { 
   System.out.print(v); 
  } 
 } 
  
 public void print2(int v) { 
  //线程安全 
  synchronized (MyThread.class) { 
   for (int i = 0; i < 100; i++) { 
    System.out.print(v); 
   } 
  } 
 } 
  
 public void run() { 
  print1(val); 
  // print2(val); 
 } 
}

Still just to be lazy and sweat it out. . . Programmers are always lazy. Write as little as you can. I wrote MyThread as an anonymous final inner class for easy calling. It uses the most direct inheritance of Thread to implement a thread class and defines the run() method that needs to be run.
First comment the print2() method to see what the result of print1() is. print1() is a method defined using the synchronized keyword. I always thought that this could also achieve thread safety. As everyone knows, I was wrong.
Let’s run the main() method directly. The console print result is as follows:

1212111121212121212121212121212121212121222222212121212。。。

is a series of cross-printed results of 1 and 2. In my main method, I run m1 first and then m2, which shows that thread synchronization is not achieved!

MyThread m1 = new MyThread(1); 
MyThread m2 = new MyThread(2); 
m1.start(); 
m2.start();

Next we comment out print1() in the run method and run print2();
The console prints as follows:

11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

The thread is indeed safe, I always thought I knew it This way of writing, but because this way of writing has a little more code, I didn’t think much about it. I only realized this mistake today. It seems that sometimes it pays to not be lazy. Laying a good foundation is important. Correcting a long-standing mistake.

Let’s take a look at the specific reasons.

The synchronized keyword can be used as a modifier of a function or as a statement within a function, which is what is usually called synchronization method and synchronization statement block. If classified further, synchronized can act on instance variables, object references (object references), static functions and class literals (class name literals).
Before elaborating further, we need to clarify a few points:
A. Regardless of whether the synchronized keyword is added to a method or an object, the lock it acquires is an object, rather than treating a piece of code or a function as a lock - and the synchronized method is likely to be accessed by objects from other threads.
B. Each object has only one lock associated with it.
C. Achieving synchronization requires a lot of system overhead and may even cause deadlock, so try to avoid unnecessary synchronization control.
Next, let’s discuss the impact of synchronized in different places on the code:
Assume that P1 and P2 are different objects of the same class. This class defines synchronization blocks or synchronization methods in the following situations, P1, P2 can call them all.
1. When synchronized is used as a function modifier, the sample code is as follows:

Public synchronized void methodAAA() 
{ 
//…. 
}

This is the synchronization method. So which object is synchronized locked at this time? What it locks is the object that calls this synchronized method. That is to say, when an object P1 executes this synchronization method in different threads, mutual exclusion will be formed between them to achieve a synchronization effect. However, another object P2 generated by the Class to which this object belongs can call this method with the synchronized keyword arbitrarily.
The sample code above is equivalent to the following code:

public void methodAAA() 
{ 
synchronized (this) // (1) 
{ 
//….. 
} 
}

What does this at (1) refer to? It refers to the object that calls this method, such as P1. It can be seen that the essence of the synchronization method is to apply synchronized to the object reference. ——Only the thread that has obtained the P1 object lock can call the synchronization method of P1. As far as P2 is concerned, the P1 lock has nothing to do with it. The program may also get rid of the control of the synchronization mechanism in this situation, resulting in data confusion!

2. Synchronized block, the sample code is as follows:

public void method3(SomeObject so) 
{ 
synchronized(so) 
{ 
//….. 
} 
}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable 
{ 
private byte[] lock = new byte[0]; // 特殊的instance变量 
Public void methodA() 
{ 
synchronized(lock) { //… } 
} 
//….. 
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:

Class Foo 
{ 
public synchronized static void methodAAA() // 同步的static 函数 
{ 
//…. 
} 
public void methodBBB() 
{ 
synchronized(Foo.class) // class literal(类名称字面常量) 
} 
}

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
小结如下:
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
还有一些技巧可以让我们对共享资源的同步访问更加安全:
1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。
2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。

总结一些synchronized注意事项:

当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。两个线程间是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。(两个线程使用的是同一个对象)

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞(同上,两个线程使用的是同一个对象)。

更多实例解析Java中的synchronized关键字与线程安全问题相关文章请关注PHP中文网!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn