进行多线程编程的时候,须要考虑的是线程间的同步问题。对于共享的资源,须要进行互斥的访问。在Java中可使用一些手段来达到线程同步的目的:编程
1. synchronized 缓存
2. ThreadLocal,线程本地变量多线程
3. Java.util.concurrent.Lock并发
Java中,线程会共享堆上的实例变量以及方法区的类变量,而栈上的数据是私有的,没必要进行保护。synchronized方法或synchronized块将标记一块监视区域,线程在进入该区域时,须要得到对象锁或类锁,JVM将自动上锁。synchronized提供了两种主要特性:ide
1. 互斥。互斥是指一次只容许一个线程持有某个特定的锁,所以可以使用该特性实现对共享数据的并发访问,保证一次只有一个线程可以使用该共享数据。函数
2.可见性。确保释放锁以前对共享数据作出的更改对随后得到该锁的另外一个线程是可见的。若是不能保证可见性,也就没法保证数据正确性,这将引起严重问题。volitail关键字一样保证了这种可见性。this
在这里,咱们将探讨synchronized使用时的三种状况:spa
1. 在对象上使用synchronized线程
2. 在普通成员方法上使用synchronizedcode
3. 在静态成员方法上使用synchronized
这三种线程同步的表现有何不一样?
下面经过三段示例代码来演示这三种状况。这里模拟线程报数的场景。
状况一:在普通成员函数上使用synchronized
public class MyThread extends Thread { public static void main(String[] args) throws Exception { for (int i = 1; i < 100; i++) { MyThread t = new MyThread(); t.setName("Thread="+i); t.start(); Thread.sleep(100); } } @Override public synchronized void run() { for (int i = 1; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } }
对一个成员函数使用synchronized进行加锁,所获取的锁,是方法所在对象自己的对象锁。在这里,每一个线程都以自身的对象做为对象锁,要对线程进行同步,要求锁对象必须惟一,因此这里多个线程间同步失败。
状况二:在对象上使用synchronized
这里在类中增长一个成员变量lock,在该变量上使用synchronized:
public class MyThread1 extends Thread { private String lock; public MyThread1(String lock) { this.lock = lock; } public static void main(String[] args) throws Exception { String lock = new String("lock"); for (int i = 1; i < 100; i++) { Thread t = new MyThread1(lock); t.setName("Thread=" + i); t.start(); Thread.sleep(100); } } @Override public void run() { synchronized (lock) { for (int i = 1; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
100个线程在建立的时候,都传递了同一个lock对象(在main中建立的)去初始化线程类成员lock,所以,这100个线程都在同一个lock对象上进行synchronized同步。所以线程同步成功。
状况三:在静态成员函数上使用synchronized
public class MyThread2 extends Thread { public static void main(String[] args) throws Exception { for (int i = 1; i < 10; i++) { Thread t = new MyThread2(); t.setName("Thread=" + i); t.start(); Thread.sleep(10); } } public static synchronized void func() { for (int i = 1; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } @Override public void run() { func(); } }
这种状况下,线程得到的锁是对象锁,而对象锁是惟一的,所以多个进程间也能同步成功。
补充:
1. 慎用字符串常量作同步对象,由于JVM内部会把常量字符串转换成同一个对象,同理的,基本数据除了Float和Double外,也有缓存对象[-128,127].
2. synchronized方法继承问题:1. 子类会继承父类的synchronized方法。2. 若是子类重写了父类的synchronized方法,必须也加上synchronized关键字,不然子类中的方法将变成非同步的。 3. 同一个子类对象中,子类的synchronized方法父类的synchronized方法使用的是同一个临界区。
(完)