zoukankan      html  css  js  c++  java
  • java多线程学习笔记(五)

    补充一个synchronized关键字的结论:

    1. 线程A先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
    2. A线程现持有object对象的Lock锁,B线程如果这个时候调用object对象中的synchronized类型的方法则需要等待,也就是同步
    3. 当线程A调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronize关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁之后才可以调用。这时A已经执行了一个完整的任务,也就是变量已经完成了变化,不存在脏读的基本环境。(X方法和非X方法均处在同一个类下面,是同一个对象的不同方法)
    4. 关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以在前面例子中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问同一个对象
    5. 如果多个线程访问多个对象,则JVM会创建多个锁。会以异步的方式运行

    同步的单词为synchronized 异步的单词为asynchronized

    当一个线程出现异常时,锁会自动释放。

    同步不具有继承性,即:父类方法中加了synchronized关键字,子类调用父类的方法时,这个被继承来的方法不具备同步的特性。
    public class Main {
        synchronized public void serviceMethod() {
            try {
                System.out.println("int main 下一步 sleep begin threadName="
                        + Thread.currentThread().getName() + " time="
                        + System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("int main 下一步  sleep end threadName="
                        + Thread.currentThread().getName() + " time="
                        + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    //-----------------------------------------------------------------------------
    public class Sub extends Main {
        @Override
        public void serviceMethod() {
            try {
                System.out.println("int sub 下一步  sleep begin threadName="
                        + Thread.currentThread().getName() + " time="
                        + System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("int sub 下一步  sleep end threadName="
                        + Thread.currentThread().getName() + " time="
                        + System.currentTimeMillis());
                super.serviceMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    //-----------------------------------------------------------------------------
    public class MyThreadA extends Thread {
        private Sub sub;
     
        public MyThreadA(Sub sub) {
            super();
            this.sub = sub;
        }
     
        @Override
        public void run() {
            sub.serviceMethod();
        }public class MyThreadB extends Thread {
        private Sub sub;
     
        public MyThreadB(Sub sub) {
            super();
            this.sub = sub;
        }
     
        @Override
        public void run() {
            sub.serviceMethod();
        }
    }//-----------------------------------------------------------------------------
    public class Test {
        public static void main(String[] args) {
            Sub subRef = new Sub();
            MyThreadA a = new MyThreadA(subRef);
            a.setName("A");
            a.start();
            MyThreadB b = new MyThreadB(subRef);
            b.setName("B");
            b.start();
        }
    }

    synchronized同步语句块

    用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待较长的时间,在这种情况下,可以使用synchronized同步语句块来解决。

    public class Task {
        private String getData1;
        private String getData2;
    
        public synchronized void doLongTimeTask(){
            try {
                System.out.println("begin task");
                Thread.sleep(3000);
                getData1="长时间处理任务后从远程返回的值1 threadName="
                        +Thread.currentThread().getName();
                getData2="长时间处理任务后从远程返回的值2 threadName="
                        +Thread.currentThread().getName();
                System.out.println(getData1);
                System.out.println(getData2);
                System.out.println("end task");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    
    
    public class CommonUtils {
        public static long beginTime1;
        public static long endTime1;
        public static long beginTime2;
        public static long endTime2;
    }
    
    
    public class ThreadA extends Thread{
    
        private Task task;
    
        public ThreadA(Task task){
            super();
            this.task=task;
        }
    
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime1=System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime1=System.currentTimeMillis();
        }
    }
    
    
    public class ThreadB extends Thread{
    
        private Task task;
    
        public ThreadB(Task task){
            super();
            this.task=task;
        }
    
        @Override
        public void run() {
            super.run();
            CommonUtils.beginTime2=System.currentTimeMillis();
            task.doLongTimeTask();
            CommonUtils.endTime2=System.currentTimeMillis();
        }
    }
    
    
    public class Run {
    
        public static void main(String[] args){
            Task task=new Task();
            ThreadA thread1=new ThreadA(task);
            thread1.start();
            ThreadB thread2=new ThreadB(task);
            thread1.start();
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            long beginTime=CommonUtils.beginTime1;
            if(CommonUtils.beginTime2<CommonUtils.beginTime1){
                beginTime=CommonUtils.beginTime2;
            }
    
            long endTime=CommonUtils.endTime1;
            if (CommonUtils.endTime2>CommonUtils.endTime1){
                endTime=CommonUtils.endTime2;
            }
    
            System.out.println("耗时:"+(endTime-beginTime)/1000);
        }
    }

    程序运行大约6秒后结束。其中synchronized修饰的方法dolongtimetask同步执行耗时很长。

    结论写在前面:不在synchronized块中就是异步执行,在synchronized块中就是同步执行

    使用同步代码块解决同步方法的弊端:

     1 public class Task {
     2     private String getData1;
     3     private String getData2;
     4 
     5     public synchronized void doLongTimeTask(){
     6         try {
     7             System.out.println("begin task");
     8             Thread.sleep(3000);
     9             getData1="长时间处理任务后从远程返回的值1 threadName="
    10                     +Thread.currentThread().getName();
    11             getData2="长时间处理任务后从远程返回的值2 threadName="
    12                     +Thread.currentThread().getName();
    13             synchronized (this){
    14                 System.out.println(getData1);
    15                 System.out.println(getData2);
    16             }
    17             System.out.println("end task");
    18         }catch (InterruptedException e){
    19             e.printStackTrace();
    20         }
    21     }
    22 }    

    第13-16行代码被修改了,他们被放入了同步代码块中,显然不在同步代码块中的内容被异步执行,程序运行结束后,耗时为3秒。

    在使用同步代码块的时候需要注意,当一个线程访问object的一个synchrnized同步代码块时,其他线程对同一个object中所有其他synchronized同步代码块的访问将被阻塞,这说明synchronized使用的对象监视器同一个。

    问题来了,什么是对象监视器?

    在JVM的规范中,有这么一些话:  
    “在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的”  
    “为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁”  
    “锁住了一个对象,就是获得对象相关联的监视器”  
       
    从这些话,看出监视器和对象锁好像是一回事,那为何要定义两个东西,若不一样,他们的关系如何?监视器好比一做建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,进入这个建筑叫做"进入监视器",进入建筑中的那个特别的房间叫做"获得监视器",占据房间叫做"持有监视器",离开房间叫做"释放监视器",离开建筑叫做"退出监视器".  而一个锁就像一种任何时候只允许一个线程拥有的特权.   一个线程可以允许多次对同一对象上锁.对于每一个对象来说,java虚拟机维护一个计数器,记录对象被加了多少次锁,没被锁的对象的计数器是0,线程每加锁一次,计数器就加1,每释放一次,计数器就减1.当计数器跳到0的时候,锁就被完全释放了.       

    java虚拟机中的一个线程在它到达监视区域开始处的时候请求一个锁.JAVA程序中每一个监视区域都和一个对象引用相关联.  

    监视器:monitor  
      锁:lock(JVM里只有一种独占方式的lock)  
      进入监视器:entermonitor  
      离开/释放监视器:leavemonitor  
      (entermonitor和leavemonitor是JVM的指令)  
      拥有者:owner  
       
      在JVM里,monitor就是实现lock的方式。  
      entermonitor就是获得某个对象的lock(owner是当前线程)  
      leavemonitor就是释放某个对象的lock  
    ------------------------->这些内容都是JVM的内容,下一部博文准备写JVM

    同步代码块锁非this对象

    这里引出新的问题this关键字

     (1)this调用本类中的属性,也就是类中的成员变量;
     (2)this调用本类中的其他方法;
     (3)this调用本类中的其他构造方法,调用时要放在构造方法的首行。
    this是一个引用,它指向自身的这个对象。
    结论写在前面:一.在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象X)同步代码块中的代码
    二.当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象X)同步代码块中的代码
    public class Service {
        private String usernameParam;
        private String passwordParam;
        private String anString = new String();
        public void setUsernamePassword(String username,String password){
            try{
                synchronized (anString){
                    System.out.println("线程名称是:"+Thread.currentThread().getName()
                    +"在" + System.currentTimeMillis()+"进入同步块");
                    usernameParam = username;
                    Thread.sleep(3000);
                    passwordParam = password;
                    System.out.println("线程名称是:"+Thread.currentThread().getName()
                            +"在" + System.currentTimeMillis()+"离开同步块");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class ThreadA extends Thread{
        private Service service;
        public ThreadA(Service service){
            this.service = service;
        }
        @Override
        public void run(){
            service.setUsernamePassword("a","apsssss");
        }
    }
    
    public class ThreadB extends Thread{
        private Service service;
        public ThreadB(Service service){
            this.service = service;
        }
        @Override
        public void run(){
            service.setUsernamePassword("BBB","ISSSSBBB");
        }
    }
    
    public class Run {
        public static void main(String[] args) {
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            a.setName("A线程");
            a.start();
            ThreadB b = new ThreadB(service);
            b.setName("B线程");
            b.start();
    
        }
    }

    上面的代码运行结果为:

    线程名称是:A线程在1574247406143进入同步块
    线程名称是:A线程在1574247409171离开同步块
    线程名称是:B线程在1574247409172进入同步块
    线程名称是:B线程在1574247412174离开同步块
    结论:锁非this对象具有一定的优点,如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以会影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this对象)代码块中的程序与同步方法是异步的,不与其他锁this方法争抢this锁,则可大大提高运行效率。
    public class Service {
        private String usernameParam;
        private String passwordParam;
    
        public void setUsernamePassword(String username,String password){
            try{
                String anString = new String();
                synchronized (anString){
                    System.out.println("线程名称是:"+Thread.currentThread().getName()
                    +"在" + System.currentTimeMillis()+"进入同步块");
                    usernameParam = username;
                    Thread.sleep(3000);
                    passwordParam = password;
                    System.out.println("线程名称是:"+Thread.currentThread().getName()
                            +"在" + System.currentTimeMillis()+"离开同步块");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    使用synchronized(非this对象X)同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用,就会交叉运行。因为上面代码中声明 anString 的位置发生了变化,可以实现同步的是在对象属性声明的部位创建anString,导致异步结果的事下面这个在方法内声明变量的操作。

    三个结论!

    synchronized(非this对象X)格式的写法是将x对象本身作为对象监视器,这样就可以得到下面3个结论

    1. 当多线程同时执行synchronized(x){}同步代码块时,呈同步效果
    2. 当其他线程执行x对象中synchronized同步方法时,呈同步效果
    3. 当其他线程执行x对象方法里面的synchronized(this)代码块时,也呈同步效果

    但需要注意的是,如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

  • 相关阅读:
    iOS 应用内付费(IAP)开发步骤
    国内银行CNAPS CODE 查询 苹果开发者,应用内购,需要填写税务相关信息必须的
    untiy 插件工具: 游戏中 策划数据Excel 导出到项目中
    大陆 Google play 开发者注册(2016)
    unity UGUI动态字体显示模糊
    Vue--webpack实时重新加载
    Vue--webpack打包css、image资源
    Vue--webpack打包js文件
    Vue--axios
    Vue--生命周期
  • 原文地址:https://www.cnblogs.com/samanian/p/11898875.html
Copyright © 2011-2022 走看看