小强哥博客

小强哥,小强哥博客,技术大咖

java中synchronized几种常见现象,总结一下

class与this的几种情况:

  • synchronized(class)
  • synchronized(this)

线程各自获取monitor,不会有等待。

  • synchronized(this)
  • synchronized(this)

如果不同线程监视同一个实例对象,就会等待;如果不同的实例,不会等待。

  • synchronized(class)
  • synchronized(class)

如果不同线程监视同一个实例或者不同的实例对象,都会等待

synchronized括号中的参数可以设置this、class、对象,

设置this

  • synchronized(this)
  • synchronized(this)

如果不同线程监视同一个实例对象,就会等待;如果不同的实例,不会等待。

现象一

当多个线程并发访问同一个对象中的synchronized块时,同一时间只有一个线程执行synchronized块,其余的线程会处于资源抢夺状态,必须当前线程执行完毕以后才能继续执行,如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		for (int i = 0; i < 5; i++) {
			int id = i;
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.saveUser(id);
				}
			});
		}
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		log.info(">> {} 进来了", id);
		synchronized (this) {
			log.info(">> {} 休眠", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

如下执行日志,从日志中可以看到一开始所有的线程都进入了saveUser方法中,但是只有一个线程进入了synchronized块中,从时间列表可以看出,在等待了3秒的休眠以后第二个线程抢到了资源开始执行。

14:43:57.173 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 进来了
14:43:57.178 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 休眠
14:43:57.179 [pool-2-thread-3] INFO  com.eju.ess.User - >> 2 进来了
14:43:57.179 [pool-2-thread-4] INFO  com.eju.ess.User - >> 3 进来了
14:43:57.179 [pool-2-thread-2] INFO  com.eju.ess.User - >> 1 进来了
14:43:57.179 [pool-2-thread-5] INFO  com.eju.ess.User - >> 4 进来了
14:44:00.179 [pool-2-thread-5] INFO  com.eju.ess.User - >> 4 休眠
14:44:03.179 [pool-2-thread-2] INFO  com.eju.ess.User - >> 1 休眠
14:44:06.179 [pool-2-thread-4] INFO  com.eju.ess.User - >> 3 休眠
14:44:09.179 [pool-2-thread-3] INFO  com.eju.ess.User - >> 2 休眠
所有的子线程都结束了!

现象二

当一个线程访问对象中的synchronized块时,其余线程仍然可以继续访问该对象中非synchronized块中的代码,如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		for (int i = 0; i < 5; i++) {
			int id = i;
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.saveUser(id);
				}
			});
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.getUser(id);
				}
			});
		}
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		log.info(">> {} 进来了", id);
		synchronized (this) {
			log.info(">> {} 休眠", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void getUser(Integer id){
		log.info(">> {} 获得用户", id);
	}
}

如下执行日志,从日志中可以看到synchronized块在被线程占用的同时,其余线程依然可以执行对象中非synchronized的getUser函数。

14:51:52.557 [pool-2-thread-4] INFO  com.eju.ess.User - >> 1 获得用户
14:51:52.563 [pool-2-thread-8] INFO  com.eju.ess.User - >> 3 获得用户
14:51:52.617 [pool-2-thread-7] INFO  com.eju.ess.User - >> 3 进来了
14:51:52.617 [pool-2-thread-7] INFO  com.eju.ess.User - >> 3 休眠
14:51:52.617 [pool-2-thread-3] INFO  com.eju.ess.User - >> 1 进来了
14:51:52.617 [pool-2-thread-2] INFO  com.eju.ess.User - >> 0 获得用户
14:51:52.618 [pool-2-thread-10] INFO  com.eju.ess.User - >> 4 获得用户
14:51:52.675 [pool-2-thread-9] INFO  com.eju.ess.User - >> 4 进来了
14:51:52.675 [pool-2-thread-5] INFO  com.eju.ess.User - >> 2 进来了
14:51:52.676 [pool-2-thread-6] INFO  com.eju.ess.User - >> 2 获得用户
14:51:52.676 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 进来了
14:51:55.618 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 休眠
14:51:58.618 [pool-2-thread-5] INFO  com.eju.ess.User - >> 2 休眠
14:52:01.618 [pool-2-thread-9] INFO  com.eju.ess.User - >> 4 休眠
14:52:04.618 [pool-2-thread-3] INFO  com.eju.ess.User - >> 1 休眠
所有的子线程都结束了!

现象三

当一个线程访问对象的synchronized块时,其余线程访问对象中其它synchronized块时将被阻塞(很霸道的行为),如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		for (int i = 0; i < 5; i++) {
			int id = i;
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.saveUser(id);
				}
			});
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.getUser(id);
				}
			});
		}
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		log.info(">> {} 进来了", id);
		synchronized (this) {
			log.info(">> {} 休眠", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void getUser(Integer id){
		synchronized (this) {
			log.info(">> {} 获得用户", id);
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

如下执行日志

14:59:54.432 [pool-2-thread-4] INFO  com.eju.ess.User - >> 1 获得用户
14:59:54.436 [pool-2-thread-5] INFO  com.eju.ess.User - >> 2 进来了
14:59:54.436 [pool-2-thread-9] INFO  com.eju.ess.User - >> 4 进来了
14:59:54.441 [pool-2-thread-7] INFO  com.eju.ess.User - >> 3 进来了
14:59:54.442 [pool-2-thread-3] INFO  com.eju.ess.User - >> 1 进来了
14:59:54.443 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 进来了
14:59:56.441 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 休眠
14:59:59.441 [pool-2-thread-3] INFO  com.eju.ess.User - >> 1 休眠
15:00:02.441 [pool-2-thread-7] INFO  com.eju.ess.User - >> 3 休眠
15:00:05.441 [pool-2-thread-5] INFO  com.eju.ess.User - >> 2 休眠
15:00:08.441 [pool-2-thread-9] INFO  com.eju.ess.User - >> 4 休眠
15:00:11.441 [pool-2-thread-8] INFO  com.eju.ess.User - >> 3 获得用户
15:00:13.441 [pool-2-thread-2] INFO  com.eju.ess.User - >> 0 获得用户
15:00:15.441 [pool-2-thread-6] INFO  com.eju.ess.User - >> 2 获得用户
15:00:17.441 [pool-2-thread-10] INFO  com.eju.ess.User - >> 4 获得用户
所有的子线程都结束了!

现象四

将synchronized加载函数上和现象三一样,如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		for (int i = 0; i < 3; i++) {
			int id = i;
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.saveUser(id);
				}
			});
			executor.execute(new Runnable() {
				@Override
				public void run() {
					user.getUser(id);
				}
			});
		}
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		log.info(">> {} 进来了", id);
		synchronized (this) {
			log.info(">> {} 休眠", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public synchronized void getUser(Integer id) {
		log.info(">> {} 获得用户", id);
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

执行日志如下,也就是说,当一个线程访问对象的一个synchronized同步代码块时,它就获得了这个对象的锁。结果,其它线程对该对象所有同步代码部分的访问都被暂时阻塞。

15:06:36.106 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 进来了
15:06:36.111 [pool-2-thread-1] INFO  com.eju.ess.User - >> 0 休眠
15:06:38.111 [pool-2-thread-9] INFO  com.eju.ess.User - >> 4 进来了
15:06:38.111 [pool-2-thread-9] INFO  com.eju.ess.User - >> 4 休眠
15:06:40.116 [pool-2-thread-5] INFO  com.eju.ess.User - >> 2 进来了
15:06:40.116 [pool-2-thread-5] INFO  com.eju.ess.User - >> 2 休眠
15:06:42.116 [pool-2-thread-10] INFO  com.eju.ess.User - >> 4 获得用户
15:06:44.116 [pool-2-thread-6] INFO  com.eju.ess.User - >> 2 获得用户
15:06:46.116 [pool-2-thread-2] INFO  com.eju.ess.User - >> 0 获得用户
15:06:48.116 [pool-2-thread-7] INFO  com.eju.ess.User - >> 3 进来了
15:06:48.116 [pool-2-thread-7] INFO  com.eju.ess.User - >> 3 休眠
15:06:50.116 [pool-2-thread-3] INFO  com.eju.ess.User - >> 1 进来了
15:06:50.116 [pool-2-thread-3] INFO  com.eju.ess.User - >> 1 休眠
15:06:52.116 [pool-2-thread-8] INFO  com.eju.ess.User - >> 3 获得用户
15:06:54.116 [pool-2-thread-4] INFO  com.eju.ess.User - >> 1 获得用户
所有的子线程都结束了!

设置对象

现象一

synchronized()中不仅可以设置this,同时也可以设置一个对象,对象不能是基础数据类型,可以是自定义对象或者包装类。当设置对象时现象和synchronized(this)时的现象一样,如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		Integer id = 10;
		executor.execute(new Runnable() {
			@Override
			public void run() {
				user.saveUser(id);
			}
		});
		executor.execute(new Runnable() {
			@Override
			public void run() {
				user.getUser(id);
			}
		});
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		synchronized (id) {
			log.info(">> {} 休眠1", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void getUser(Integer id) {
		synchronized (id) {
			log.info(">> {} 休眠2", id);
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

此时synchronized的监听是Integer对象,不再是this指向的User,Integer是一个包装类、复杂数据类型,如果Integer的值相同则认为synchronized监听的是同一个对象,synchronized之间就会产生阻塞;如果Integer的值不同,则认为synchronized监听的不是同一个对象,synchronized之间就不会产生阻塞。

执行日志如下,从日志中可以看出由于saveUser中的synchronized和getUser中的synchronized监听的是同一个对象,因此执行的时候会对Integer资源产生阻塞。

15:58:01.690 [pool-2-thread-1] INFO  com.eju.ess.User - >> 10 休眠1
15:58:04.697 [pool-2-thread-2] INFO  com.eju.ess.User - >> 10 休眠2
所有的子线程都结束了!

现象二

如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		Integer id = 10;
		executor.execute(new Runnable() {
			@Override
			public void run() {
				user.saveUser(id);
			}
		});
		executor.execute(new Runnable() {
			@Override
			public void run() {
				user.getUser(id*10);
			}
		});
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		synchronized (id) {
			log.info(">> {} 休眠1", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void getUser(Integer id) {
		synchronized (id) {
			log.info(">> {} 休眠2", id);
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

执行日志如下,从日志中可以看出,由于synchronized监听的对象不一样,因此synchronized之间不会产生生阻塞。

16:20:14.334 [pool-2-thread-2] INFO  com.eju.ess.User - >> 100 休眠2
16:20:14.351 [pool-2-thread-1] INFO  com.eju.ess.User - >> 10 休眠1
所有的子线程都结束了!

设置class

  • synchronized(class)
  • synchronized(class)

如果不同线程监视同一个实例或者不同的实例对象,都会等待

现象一

如下代码,

package com.eju.ess;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Singleton {
	public static void main(String[] srgs) {
		ExecutorService executor = Executors.newCachedThreadPool();
		User user = new User();
		Integer id = 10;
		executor.execute(new Runnable() {
			@Override
			public void run() {
				user.saveUser(id);
			}
		});
		executor.execute(new Runnable() {
			@Override
			public void run() {
				user.getUser(id*10);
			}
		});
		executor.shutdown();
		while (true) {
			if (executor.isTerminated()) {
				System.out.println("所有的子线程都结束了!");
				break;
			}
		}
	}
}

@Slf4j
class User {
	public void saveUser(Integer id) {
		synchronized (User.class) {
			log.info(">> {} 休眠1", id);
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void getUser(Integer id) {
		synchronized (User.class) {
			log.info(">> {} 休眠2", id);
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

执行日志如下,synchronized (User.class) 表示同一时间只能有一个线程访问User实例,这种方式的限制范围更广,synchronized监听的是整个User.class文件实例。

16:23:54.955 [pool-2-thread-1] INFO  com.eju.ess.User - >> 10 休眠1
16:23:57.960 [pool-2-thread-2] INFO  com.eju.ess.User - >> 100 休眠2
所有的子线程都结束了!

参考文章

http://www.cnblogs.com/oracleDBA/archive/2010/05/22/1741642.html

http://www.jb51.net/article/74566.htm

http://blog.csdn.net/liangyihuai/article/details/48970825

http://blog.csdn.net/yanlinwang/article/details/44339113