首先synchronized(this)锁住的是当前的类对象实例,如果synchronized(this)出现在的是service层,那么锁的是该service实例对象,spring的IOC中默认单例模式,当有多个线程执行时也是去竞争同一个service实例对象,所以不会有线程同步问题。但是如果是IOC是多例模式,那么synchronized(this)并不能保证线程安全。下面是简单的例子:
synchronized(this)线程同步例子:
public class ThreadTest implements Runnable{
private volatile static Integer i=0;
@Override
public void run() {
try{
for (int j=0; j < 50; j++) {
Thread.sleep(100);
synchronized (this){
i++;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread thread1 = new Thread(threadTest);
Thread thread2 = new Thread(threadTest);
thread1.start();
thread2.start();
}
}
public class ThreadTest implements Runnable{
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
Thread thread1 = new Thread(threadTest);
Thread thread2 = new Thread(threadTest2);
thread1.start();
thread2.start();
}
}
当有两个threadTest实例对象时,并不能保证线程同步,因为两个线程synchronized(this)都能获得各自需要的锁。thread1的锁是threadTest对象,thread2的锁是threadTest2对象。所以结果也是不对的。
但是如果是synchronized(obj)时,无论是多少个threadTest实例对象都能保证线程同步。如下:
public class ThreadTest implements Runnable{
private volatile static Integer i=0;
private static Object lock=new Object();
@Override
public void run() {
try{
for (int j=0; j < 50; j++) {
Thread.sleep(100);
synchronized (lock){
i++;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
Thread thread1 = new Thread(threadTest);
Thread thread2 = new Thread(threadTest2);
thread1.start();
thread2.start();
}
}
当然synchronized也并不是所有对象都可以当作锁的,比如Integer对象和String对象。大家可以用上面的例子试试锁住对象i,结果肯定是线程不同步。
因为一方面i是在不停的在做++运算的,本身的值就在改变,不是同一个对象,另一方面
当synchronized锁的是String时,这就涉及到JVM对字符串的处理。保证线程同步核心思想是锁的是同一个对象
public class ThreadTest implements Runnable{
@Override
public void run(){
String threadName = new String("dd");
synchronized (threadName) {
//线程进入
System.out.println(threadName + " thread start");
try {
//进入后睡眠
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程结束
System.out.println(threadName + " thread end");
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new ThreadTest());
thread.start();
}
}
}
运行结果并没有线程同步
dd thread start
dd thread start
dd thread start
dd thread end
dd thread end
dd thread end
可见,虽然threadName的值都是aa,但是threadName并不是同一个对象。
当然也有解决办法,那就是使用intern()方法,取字符串常量池中的对象,这样每次锁的都是同样一个对象
synchronized (threadName.intern())
运行结果:
aa thread start
aa thread end
aa thread start
aa thread end
aa thread start
aa thread end
通过上面的结果可以看出,字符串常量和字符串对象的值相等,地址不同。通过new的对象是在堆栈中,字符串常量是存放在常量池中,通过intern()把字符串对象放入常量池中,则地址是同一个。
使用intern()也有一定的缺点,在数据量很大的情况下,将所有字符串都放入常量池是不合理的,常量池大小依赖服务器内存,且只有等待fullGC,极端情况下会导致频繁fullGC。并且在数据量很大的情况下,将字符串放入常量是存在性能问题。
可以用google的guava包的interner类,
public class test{
private static Interner<String> lock = Interners.newWeakInterner();
public void test() {
synchronized (lock.intern(id.toString())){
//do...
}
}
}
Interner是通过MapMaker构造ConcurrentMap来实现弱引用,ConcurrentMap用分段的方式保证安全。这里个人觉得比常量池的优点就在于这里是弱引用的方式,便于map的回收,常量池只能依赖于fullGC, 这里的回收在不使用或内存不够用条件下即可被回收(Minor GC阶段)。
个人小站:什么是快乐