一、synchronized
1、synchronized取得的都是对象锁,而不是把一段代码或方法当做锁。
package com.amuro.studythread.chapter_2_concurrent_access;
public class SynchronizedBase
{
public static void main(String[] args)
{
SynchronizedObject obj = new SynchronizedObject();
Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
obj.test1();
}
});
t1.setName("t1");
t1.start();
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
obj.test2();
}
});
t2.setName("t2");
t2.start();
}
private static class SynchronizedObject
{
synchronized void test1()
{
System.out.println("test1 begin with thread " + Thread.currentThread().getName());
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("test1 end with thread " + Thread.currentThread().getName());
}
synchronized void test2()
{
System.out.println("test2 begin with thread " + Thread.currentThread().getName());
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("test2 end with thread " + Thread.currentThread().getName());
}
}
}
这个例子中,虽然两个线程访问的是同一个对象不同的方法,但因为两个方法都加了对象锁,所以必须等到先拿到锁的那个线程执行完成释放锁后,才能执行自己的方法。
2、脏读
package com.amuro.studythread.chapter_2_concurrent_access;
public class DirtyRead
{
public static void main(String[] args)
{
SynchronizedObject obj = new SynchronizedObject();
Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
obj.set("b", "bb");
}
});
t1.start();
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
System.out.println(obj.getPassword());
}
});
t2.start();
}
private static class SynchronizedObject
{
private String username = "a";
private String password = "aa";
synchronized void set(String username, String password)
{
this.username = username;
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
this.password = password;
}
public String getPassword()
{
return password;
}
}
}
打印结果是aa,也就是说线程1还没有来得及修改好所有变量的值,线程2已经去获取值了,赋值同步而取值不同步导致的数据错误就叫脏读,该实例的解决方法就是在getPassword方法前也加上synchronized关键字。
3、可重入锁:自己可以再次获取自己的内部锁。比如某个线程获得了某个对象的锁,此时这个对象锁还没有释放,当它再次要获得这个对象的锁时还是可以获取的。简单来说:
synchronized test1()
{
System.out.println("1");
test2();
}
synchronized test2()
{
System.out.printlin("2");
}
如果线程1拿到了对象锁,那方法1是可以顺利调用方法2的。所以synchronized是可重入锁。
PS:可重入锁同时适用于父类和子类。
4、当一个线程执行的代码出现异常(未捕获)时,其所持有的锁会自动释放。
5、synchronized没有继承性。
6、synchronized(this) 用同步代码块解决同步方法的弊端。
package com.amuro.studythread.chapter_2_concurrent_access;
public class SynchronizedCompare
{
public static void main(String[] args)
{
SyncMethod.invoke();
SyncThis.invoke();
}
private static class SyncMethod
{
private static long begin = 0;
private static long end = 0;
private static void invoke()
{
SyncMethod sm = new SyncMethod();
Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
begin = System.currentTimeMillis();
sm.test();
}
});
t1.start();
try
{
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
sm.test();
end = System.currentTimeMillis();
System.out.println("SyncMethod use time " + (end - begin));
}
});
t2.start();
}
int i = 0;
synchronized void test()
{
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
i++;
}
}
private static class SyncThis
{
private static long begin = 0;
private static long end = 0;
private static void invoke()
{
SyncThis sm = new SyncThis();
Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
begin = System.currentTimeMillis();
sm.test();
}
});
t1.start();
try
{
Thread.sleep(100);
} catch (InterruptedException e)
{
e.printStackTrace();
}
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
sm.test();
end = System.currentTimeMillis();
System.out.println("SyncThis use time " + (end - begin));
}
});
t2.start();
}
int i = 0;
void test()
{
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
synchronized (this)
{
i++;
}
}
}
}
程序输出:
SyncThis use time 3104
SyncMethod use time 6007
假设我们只需要保证对i操作的线程安全,那用syncMethod的时间消耗就比Sync块的消耗要大一倍,原因就是syncMethod是很蛮横的把类中所有加了synchronized的方法都锁了起来。
7、synchronized(anyObject)
一个类中如果有多个synchronized方法,当一个方法阻塞时会导致该类中所有的同步方法都阻塞,所以使用非this锁可以大大提高效率。
8、synchronized(anyObject)解决synchronized方法的脏读问题
package com.amuro.studythread.chapter_2_concurrent_access;
import java.util.ArrayList;
import java.util.List;
public class DirtyReadPro
{
public static void main(String[] args) throws Exception
{
MyOneList list = new MyOneList();
MyThread1 t1 = new MyThread1(list);
t1.setName("t1");
t1.start();
MyThread2 t2 = new MyThread2(list);
t2.setName("t2");
t2.start();
Thread.sleep(6000);
System.out.println(list.getSize());
}
private static class MyOneList
{
List<String> list = new ArrayList<>();
synchronized void add(String data)
{
System.out.println(Thread.currentThread() + " add data");
list.add(data);
}
synchronized int getSize()
{
System.out.println(Thread.currentThread() + " get Size");
return list.size();
}
}
private static class Service
{
MyOneList addServiceMethod(MyOneList list, String data)
{
try
{
if(list.getSize() < 1)
{
Thread.sleep(2000);
list.add(data);
}
}
catch(Exception e)
{
}
return list;
}
}
public static class MyThread1 extends Thread
{
MyOneList list;
public MyThread1(MyOneList list)
{
super();
this.list = list;
}
@Override
public void run()
{
Service service = new Service();
service.addServiceMethod(list, "A");
}
}
public static class MyThread2 extends Thread
{
MyOneList list;
public MyThread2(MyOneList list)
{
super();
this.list = list;
}
@Override
public void run()
{
Service service = new Service();
service.addServiceMethod(list, "B");
}
}
}
程序输出:
Thread[t1,5,main] get Size
Thread[t2,5,main] get Size
Thread[t1,5,main] add data
Thread[t2,5,main] add data
Thread[main,5,main] get Size
2
原因很简单,线程1调用getSize方法后sleep,此时MyOneList的锁已经释放,所以线程2调用getSize方法时直接可以进入,并且此时1的add方法还没来得及执行,导致1和2都可以执行add方法。
修正:
package com.amuro.studythread.chapter_2_concurrent_access;
import java.util.ArrayList;
import java.util.List;
public class DirtyReadProFix
{
public static void main(String[] args) throws Exception
{
MyOneList list = new MyOneList();
MyThread1 t1 = new MyThread1(list);
t1.setName("t1");
t1.start();
MyThread2 t2 = new MyThread2(list);
t2.setName("t2");
t2.start();
Thread.sleep(6000);
System.out.println(list.getSize());
}
private static class MyOneList
{
List<String> list = new ArrayList<>();
void add(String data)
{
System.out.println(Thread.currentThread() + " add data");
list.add(data);
}
int getSize()
{
System.out.println(Thread.currentThread() + " get Size");
return list.size();
}
}
private static class Service
{
MyOneList addServiceMethod(MyOneList list, String data)
{
try
{
synchronized (list)
{
if(list.getSize() < 1)
{
Thread.sleep(2000);
list.add(data);
}
}
}
catch(Exception e)
{
}
return list;
}
}
public static class MyThread1 extends Thread
{
MyOneList list;
public MyThread1(MyOneList list)
{
super();
this.list = list;
}
@Override
public void run()
{
Service service = new Service();
service.addServiceMethod(list, "A");
}
}
public static class MyThread2 extends Thread
{
MyOneList list;
public MyThread2(MyOneList list)
{
super();
this.list = list;
}
@Override
public void run()
{
Service service = new Service();
service.addServiceMethod(list, "B");
}
}
}
当把list作为锁时,因为线程1,2操作的都是同一个list对象,所以在线程1执行完add操作前,线程2无法进入。这样就能解决脏读的问题了。
9、synchronized方法用于静态方法时,锁的是当前类的class,所以对当前类的所有实例都有效,因为class是唯一的嘛~
10、常量池特性的类的坑
String a = "A";
String b = "A";
当用a和b做锁的时候,本质是一把锁,因为a == b。
11、经典死锁
package com.amuro.studythread.chapter_2_concurrent_access;
public class DeadLock1
{
public static void main(String[] args) throws Exception
{
DeadLockRunnable runnable = new DeadLockRunnable();
Thread t1 = new Thread(runnable);
t1.setName("t1");
t1.start();
Thread.sleep(100);
Thread t2 = new Thread(runnable);
t2.setName("t2");
t2.start();
}
private static class DeadLockRunnable implements Runnable
{
private Object lock1 = new Object();
private Object lock2 = new Object();
@Override
public void run()
{
try
{
if(Thread.currentThread().getName().equals("t1"))
{
synchronized (lock1)
{
System.out.println("t1 acquire lock1");
Thread.sleep(1000);
System.out.println("t1 try to acquire lock2");
synchronized (lock2)
{
System.out.println("t1 end");
}
}
}
else
{
synchronized (lock2)
{
System.out.println("t2 acquire lock2");
Thread.sleep(1000);
System.out.println("t2 try to acquire lock1");
synchronized (lock1)
{
System.out.println("t2 end");
}
}
}
}
catch(Exception e)
{
}
}
}
}
程序输出:
t1 acquire lock1
t2 acquire lock2
t1 try to acquire lock2
t2 try to acquire lock1
程序永远无法执行完成。线程1拿到锁1后,在等待时锁2已经被线程2获取,当等待结束时线程1尝试再去获取锁2就会阻塞除非锁2被释放,而线程2在等待后又去尝试目前被线程1占用的锁1,两个线程永远也无法获得对方持有的锁,于是就进入了死锁。
12、只要对象不变,修改对象的属性对锁没有影响,也就是说对synchronized来说,仍然是一把锁。
二、volatile
1、JVM被设置成-server模式时为了提升线程运行效率,线程会一直在私有堆栈中取得私有变量的值,此时调用该变量的set方法时,设置的却是该变量在公用堆栈中的值,从而导致设置失败。这个时候就要使用volatile关键字,它的主要作用就是让线程访问该私有变量时,强制从公共堆栈中取值。
不使用:
使用后:
(怎么感觉像减肥药广告……)
2、volatile提供变量多线程间的可见性,但不具备原子性。
3、实现i++操作时的线程安全,除了使用synchronized,也可以使用原子类(AtomicInteger)。底层都是使用了Java的Unsafe类,这个类可以把C能做Java不能做的事都做了,后面有机会专门写一篇聊聊。
4、synchronized可以同时保证原子性和可见性,它不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。