参考转载:http://rainyear.iteye.com/blog/1734311
http://turandot.iteye.com/blog/1704027
http://www.cnblogs.com/fguozhu/articles/2657904.html
http://lavasoft.blog.51cto.com/62575/99155
1.线程的内存模型
Java作为平台无关性语言,JLS(Java语言规范)定义了一个统一的内存管理模型JMM(Java Memory Model),JMM屏蔽了底层平台内存管理细节,在多线程环境中必须解决可见性和有序性的问题。JMM规定了jvm有主内存(Main Memory)和工作内存(Working Memory) ,主内存存放程序中所有的类实例、静态数据等变量,是多个线程共享的,而工作内存存放的是该线程从主内存中拷贝过来的变量以及访问方法所取得的局部变量,是每个线程私有的其他线程不能访问,每个线程对变量的操作都是以先从主内存将其拷贝到工作内存再对其进行操作的方式进行,多个线程之间不能直接互相传递数据通信,只能通过共享变量来进行。
重要的图片看三遍,从三个内存模型的文章中摘出的图片含义是一致的。即:
1.所有线程共享主内存
2.每个线程有自己的工作内存
需要注意的是,首先你得明白什么是主内存,主内存就是我们平时所说的内存。那么哪些变量是共享的?答类变量(静态变量),实例变量(成员变量)共享,是不安全的。而局部变量即方法体内的变量是不共享的,局部变量是安全的。
为什么会线程不安全?从上面的介绍可以看出每个线程从主内存里拿数据,改变了数据后放回主内存。当多个线程都改变主内存里的变量,这个变量的值就不确定了。再准确的说,线程1只想变量a加1,第二次取出a的时候发现a并不是自己想要的。这就是不安全!
2.什么是多线程
上一节已经学习了线程,多线程就是多个运行的线程。看起来解释很搞笑,但我觉得多线程并没有那么复杂,不要以为安全问题就头大,多线程不一定是线程不安全的。上面已经说到,多个线程共享同一个变量就会出现线程安全问题,相反的,不出现共享变量的情况下就没问题了。我有一次测试多线程没问题,后来发现我测试中没有共享变量,每个线程的主体都是新建的对象,于是不存在安全问题。然而,平时用到的多是共享的。即,多个线程的参数是同一个实例。
3.同步上锁
3.1什么是上锁
想要同步就必须要上锁,只有锁住以后,别人才不可以访问我用的东西,我释放了锁后别人才可以用,这样就保证了我使用范围内的变量的绝对控制,即线程安全,也就是同步。那么什么是锁?
Java中每个对象都有一个内置锁。当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
看完介绍,明白:
- 对象有个锁,通过synchronize获取;
- 对象只有一个锁;
- 对象锁住后别的线程不能访问synchronize代码块;
- 锁是针对对象的;
- this表示当前对象
3.2方法上锁
下面是容易理解和看到的例子,就是在方法头加上关键字synchronized
public synchronized void setName(String name){ this.name = name; }
3.3对象上锁
对象上锁用this,this代表当前对象。
public synchronized int getX() { return x++; } 与 public int getX() { synchronized (this) { return x; } } 效果是完全一样的。
3.4静态方法上锁
要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。 例如: public static synchronized int setName(String name){ Xxx.name = name; } 等价于 public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; } }
3.5如果线程得不到锁会怎样
如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。
当考虑阻塞时,一定要注意哪个对象正被用于锁定:
1、调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
2、调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
3、静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
看介绍明白:上锁一定是对象的锁。
3.6死锁
死锁,很熟悉的名字。死锁是线程互相等待,a需要b的资源,但b的资源被b持有没有释放,a阻塞等待;b需要a的资源,但a的资源被a持有没有释放,b阻塞等待。就是我等你,你等我,死循环。实例:
package com.test.java.thread; /** * 死锁 * Created by mrf on 2016/2/26. */ public class DeadLockRisk { private static class Resource { public int value; } private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public int read() { synchronized (resourceA) { synchronized (resourceB) { System.out.println("read"); return resourceB.value + resourceA.value; } } } public void write(int a, int b) { synchronized (resourceB) { synchronized (resourceA) { System.out.println("write"); resourceA.value = a; resourceB.value = b; } } } } class DeadLockRun implements Runnable{ DeadLockRisk deadLockRisk = new DeadLockRisk(); @Override public void run() { deadLockRisk.write(1,2); deadLockRisk.read(); } public static void main(String[] args) { DeadLockRun deadLockRun = new DeadLockRun(); Thread t1 = new Thread(deadLockRun); Thread t2 = new Thread(deadLockRun); t1.start(); t2.start(); } }
这个实例中,要注意到两个线程的声明过程,都是针对同一个对象的,所以才有资源争抢行为。
4小结
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
还有,同步通过上锁来实现,即原子操作互不影响;上锁是针对对象的,类对象或者实例对象。