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

      3、加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。

  • 相关阅读:
    【LeetCode】Set Matrix Zeroes 解题报告
    CodeForces 14 E
    Linux守护进程的编程实现
    用fildder 查看loveuv 刷流量时通信的数据
    .NET MVC学习笔记(一)
    微价值:专訪《甜心爱消除》个人开发人员Lee,日入千元!
    JSP动作--JSP有三种凝视方式
    【Hibernate步步为营】--映射合集汇总
    阿里好的开源项目有哪些(善用工具)
    色彩搭配原理与技巧?
  • 原文地址:https://www.cnblogs.com/SacredOdysseyHD/p/8437187.html
Copyright © 2020-2023  润新知