贴一下关于synchronized的帖子:https://www.jianshu.com/p/d53bf830fa09
PS:个人觉得写的非常不错,非常推荐阅读,有助于对多线程以及JMM的理解。
下面是我对synchronized的使用测试:
说明:synchronized用来同步自身对象
这个用法估计是很多初学者经常看到的用法(说的我自己好像不是初学者似的)。
下面就来猜下以下程序的执行结果吧。
执行类MainTest:
public class MainTest {
public static void main(String[] args) throws InterruptedException {
// 创建对象o1,o2
TestObject o1 = new TestObject();
TestObject o2 = new TestObject();
// 创建线程t1,t2
ThreadTest t1 = new ThreadTest(o1);
ThreadTest t2 = new ThreadTest(o2); // TODO 将o2替换成o1试试看
t1.start();
t2.start();
// 等待线程t1,t2执行完成
t1.join();
t2.join();
// t1,t2都执行完成之后,最终输出结果
System.out.println(TestObject.i);
}
}
测试对象类ObjectTest:
public class ObjectTest {
public static int i = 0;
/**
* this
*/
public void add() {
synchronized (this) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
}
线程实现类ThreadTest:
public class ThreadTest extends Thread {
private ObjectTest objectTest;
public ThreadTest(ObjectTest objectTest) {
this.objectTest = objectTest;
}
@Override
public void run() {
// this
objectTest.add();
}
}
运行结果:多次运行执行类之后,发现最终的结果值始终<200000;按照TODO里操作后,最终值都=200000。
说明:synchronized用来声明方法。
执行类和线程实现类不动,只修改测试对象类,继续猜猜执行结果吧。
测试对象类ObjectTest:
public class ObjectTest {
public static int i = 0;
// TODO 按照一里的TODO修改试试看
public synchronized void add() {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
运行结果:多次运行之后,最终结果依然是<200000。按照TODO修改之后,最终值都=200000。
说明:synchronized用来同步类的对象类,即Class
执行类和线程实现类不动,只修改测试对象类,继续猜猜执行结果吧。
测试对象类ObjectTest:
public class ObjectTest {
public static int i = 0;
public void add() {
synchronized (ObjectTest.class) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
}
运行结果:多次运行之后,运行结果都=200000。
说明:静态方法中使用synchronized代码块
执行类和线程实现类不动,只修改测试对象类。
测试对象类ObjectTest:
public class ObjectTest {
public static int i = 0;
public static void add() {
synchronized (ObjectTest.class) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
}
运行结果:多次运行之后,运行结果都=200000。
说明:synchronized用来声明静态方法
执行类和线程实现类不动,只修改测试对象类。
测试对象类ObjectTest:
public class ObjectTest {
public static int i = 0;
public static synchronized void add6() {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
运行结果:多次运行之后,运行结果都=200000。
说明:synchronized同步对象,其实这个本质和一是一样的,但是同步对象有区别。我下面例子直接用String来替代。
线程实现类不动,修改测试对象类,执行类。
测试对象类ObjectTest:
public class ObjectTest {
public static int i = 0;
private String key;
/**
* 锁key
*/
public void add() {
String key = this.key;
synchronized (key) {
for (int k = 0; k < 100000; k++) {
i++;
}
}
}
// Getters And Setters
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
执行类MainTest:
public class MainTest {
public static void main(String[] args) throws InterruptedException {
// 创建对象o1,o2
ObjectTest o1 = new ObjectTest();
o1.setKey("A");
ObjectTest o2 = new ObjectTest();
o2.setKey("A"); // TODO 修改B试试看
// 创建线程t1,t2
ThreadTest t1 = new ThreadTest(o1);
ThreadTest t2 = new ThreadTest(o2);
t1.start();
t2.start();
// 等待线程t1,t2执行完成
t1.join();
t2.join();
// t1,t2都执行完成之后,最终输出结果
System.out.println(ObjectTest.i);
}
}
运行结果:多次运行之后,运行结果都=200000;按照TODO修改之后,多次运行结果都<200000。
一句话总结:修改同一对象时,多个线程访问同一方法,并且取得相同的锁,这时候是线程安全的。
【一】里面两个线程虽然访问同一个方法,但是取得的锁却不是同一个,因为o1,o2不是同一个对象,所以这时候线程是不安全的。改成TODO里的写法之后,取得的锁是同一对象,所以这时候线程是安全的。
【二】、【六】的道理和【一】是一样的。
【三】两个线程访问同一个方法,获取的锁是类的类对象,一个类对象就是对类本身的描述,这个肯定是相同的,所以也是线程安全的。
【四】、【五】的道理和【三】一样。
最后,如果多线程访问通过一个对象,并修改该对象,要保证线程安全,其中一种实现手法是:对该对象加锁,即当一个线程拿到该对象锁之后,其它线程要等待该线程释放锁之后,才能继续操作。这个实现手法延伸到分布式开发,也是一样的,只不过这个时候需要协调程序来进行协调了,比如zookeeper的分布式锁。还有一种实现手法是:CAS,ConcurrentHashMap里,进行扩容的时候,好像就有用的这种实现手法。对于线程安全,我知道的就这两种实现手法,而且我只是大概的了解它们,并没有实际的去实现过,哪怕是简单的实现也没有,之后会尝试简单的去实现它们。
注:上面的纯属个人理解,包括那句总结的话。理解不对或有疑问的地方,请多多指教。