多线程—— synchronized 修饰符

Posted Tiger Expensive

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程—— synchronized 修饰符相关的知识,希望对你有一定的参考价值。

一、同步问题的引出

  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对应全局锁 (类锁)。 
  实例锁 – 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。 
  全局锁 – 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。 

以上是关于多线程—— synchronized 修饰符的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程学习篇synchronized

多线程高并发之Synchronized锁及其膨胀

详解Java多线程锁之synchronized

Java 面试参考指南 — 同步

线程安全问题-synchronized(方法)火车票卖票代码演示

Java synchronized 详解