一、同步问题的引出
1、问题
以卖火车票为例,如果现在要是想买大车票的话可以去火车站买或者去各个售票点,但是不管有多少个地方可以买火车票。最终一趟列车的车票数量是固定的, 如果把各个售票点理解为各个线程的话,则所有线程应该共同拥有同一份的票数。
代码实现如下:
class BuyTicketThread implements Runnable{
// 假设一共有5张票
private int ticket = 5 ;
public void run(){
//有这些人进行抢票
for(int i=0;i<100;i++){
//判断是否有票
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
ticket--;
//输出目前的票数
System.out.println("得到一张票,现在票数:ticket = " + ticket );
}
}
}
}
public class BuyTicket {
public static void main(String[] args) {
// 定义线程对象
BuyTicketThread mt = new BuyTicketThread() ;
// 定义Thread对象代表不同的售票点
Thread t1 = new Thread(mt) ;
Thread t2 = new Thread(mt) ;
Thread t3 = new Thread(mt) ;
t1.start() ;
t2.start() ;
t3.start() ;
}
}
输出结果每次运行都不一样,此处选取一种结果如下。
得到一张票,现在票数:ticket = 3
得到一张票,现在票数:ticket = 2
得到一张票,现在票数:ticket = 3
得到一张票,现在票数:ticket = 1
得到一张票,现在票数:ticket = -1
得到一张票,现在票数:ticket = 0
此时,我们将发现结果完全不符合实际情况,数据出现混乱。这是因为多个线程访问同一个共享变量并对其进行修改,发生数据争用。
如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一时间段内只有一个线程运行,其他线程要等待此线程完成之后才可以继续执行。
2、解决问题——使用同步代码块
在代码块上加上“” 关键字的话,则此代码块就被称为同步代码块。
同步代码块格式:
synchronized(同步对象){
}
同步的时候,必须指明同步的对象,一般情况下会将当前对象作为同步对象,使用this表示。
代码如下:
class BuyTicketThread implements Runnable{ // 假设一共有5张票 private int ticket = 5 ; public void run(){ //有这些人进行抢票 for(int i=0;i<100;i++){ //判断是否有票 synchronized (this) {// 要对当前对象进行同步 if(ticket>0){ // 还有票 try{ Thread.sleep(300) ; // 加入延迟 }catch(InterruptedException e){ e.printStackTrace() ; } ticket--; //输出目前的票数 System.out.println("得到一张票,现在票数:ticket = " + ticket ); } } } } } public class BuyTicket { public static void main(String[] args) { // 定义线程对象 BuyTicketThread mt = new BuyTicketThread() ; // 定义Thread对象代表不同的售票点 Thread t1 = new Thread(mt) ; Thread t2 = new Thread(mt) ; Thread t3 = new Thread(mt) ; t1.start() ; t2.start() ; t3.start() ; } }
得到一张票,现在票数:ticket = 4 得到一张票,现在票数:ticket = 3 得到一张票,现在票数:ticket = 2 得到一张票,现在票数:ticket = 1 得到一张票,现在票数:ticket = 0
从运行结果发现,加入同步代码块后,数据执行正常,但是明显感觉运行缓慢,执行效率低。
接下来将对synchronized的原理及使用进行说明。
二、同步和锁定
1、锁的原理
Java中每个对象都有一个内置锁。当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。当程序运行到 synchronized 同步方法或代码块时才该对象锁才起作用。
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。
使用synchronized同步的实现过程
1) 获得同步锁
2) 清空工作内存
3) 从主内存拷贝对象副本到工作内存
4) 执行代码
5) 刷新主内存
6) 释放同步锁
关于锁和同步,有一下几个要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放。
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。
2、同步代码块
格式:
public int MethodName(){
synchronized(同步对象){
}
}
3、同步方法
1) 同步普通方法
public synchronized int MethodName() {
return x++;
}
等价于
public int MethodName() {
synchronized( this ){
return x++;
}
}
2) 同步静态方法
public static synchronized int MethodName() {
return x++;
}
等价于
public static int MethodName() {
synchronized( this ){
return x++;
}
}
三、Synchronized 的详细使用
转下面连接进行具体了解:
’http://blog.csdn.net/luoweifu/article/details/46613015
http://zhh9106.iteye.com/blog/2151791
—— Java中Synchronized的用法
四、总结
1、每个类,每个对象都有一个锁,当在方法或者代码块中添加了synchronized关键字时,要访问这个方法或者这个代码块时就需要获取对应的锁,没有添加synchronized则不受影响。
2、synchronized对应实例锁(对象锁),static synchronized对应全局锁 (类锁)。
实例锁 – 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
全局锁 – 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
3、加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。