zoukankan      html  css  js  c++  java
  • 多线程学习笔记一

    Java1.5特性:静态导入---->import static java.*;
      进程:

        正在进行中的程序(动态)。占一定的内存空间。
        在内存中,一段程序加载进来以后,分为代码段和数据段,开始执行代码段,当需要数据的时候,根据所需数据的地址找到该数据。

      多线程:

        在一个进程中的多条不同的程序执行路径。(一个进程当中至少有一个主线程)

        开启多线程是为了同时执行多个任务(每个线程都有自己运行的内容,这个内容可以称为该线程的任务)。

      多线程的优势和弊端:

        优势:解决多个程序同时运行。

        弊端:当进程多了的时候,每个进程占用CPU的次数在某一时间段内会变短,就会出现“卡”的情况。

      JVM中多线程分析:

        虚拟机的启动本身依赖多条线程。例如:垃圾回收器的工作。

      补充:

        堆栈空间分配(操作系统)

        栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

        堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

      堆栈缓存方式

        栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。

        堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

      堆栈数据结构区别
        堆(数据结构):堆可以被看成是一棵,如:堆排序。

        栈(数据结构):一种先进后出的数据结构。

      堆栈(java)
        1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

        2.堆栈的优缺点

          栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。

          但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。

          堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。

          但缺点是,由于要在运行时动态分配内存,存取速度较慢。

        3.Java中的数据类型有两种。

          一种是基本类型

          这种类型的定义是通过诸如

          float a= 3.0;的形式来定义的,称为自动变量。

          值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。

          这里的a是一个指向float类型的引用,指向3.0这个字面值。

          这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

          另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

          int a=3;

          int b=3;

          编译器先处理int a= 3;

          首先它会在栈中创建一个变量为a的内存空间,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。

          接着处理int b= 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

          注意:这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。

          相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。

          如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。

          在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。

          因此a值的改变不会影响到b的值。

          另一种是包装类数据,如【Integer,String, Double】等将相应的基本数据类型包装起来的类。

          这些类数据全部存在于【堆】中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

        4.String是一个特殊的包装类数据。

          即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。

          而在JDK5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。

          前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。

          Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。

          其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。

          那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

          关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

            (1)先定义一个名为str的对String类的对象引用变量:String str;

            (2)【在【栈】中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。

            如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。】【注意:上文说数据时存放在堆中,此文说数据存放在栈中?因为此处不是通过new()创建的】

            (3)将str指向对象o的地址。

            值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

            为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

            

            String str1="abc";
    
          String str2="abc";
    
          System.out.println(str1==str2);//true 

            注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
            结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

            我们再来更进一步,将以上代码改成:

            String str1="abc";
    
            String str2="abc";
    
            str1="bcd";
    
            System.out.println(str1+","+str2);//bcd,abc
    
            System.out.println(str1==str2);//false

            这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。

            上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

            事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。

            这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

            再修改原来代码:

            String str1="abc";
            String str2="abc";
            str1="bcd";
            String str3=str1;
            System.out.println(str3);//bcd
            String str4="bcd";
            System.out.println(str1==str4);//true

            我们再接着看以下的代码。

              String str1 = new String("abc"); 
              String str2 = "abc"; 
              System.out.println(str1==str2); //false
              String str1 = "abc"; 
              String str2 = new String("abc"); 
              System.out.println(str1==str2); //false 

            创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

            重要:以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

        5. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

        6. 结论与建议:

          (1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。

          因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

          (2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。

          而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

          (3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

          (4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

    补充:垃圾回收
        对象怎么被回收,只有对象自己最清楚,所以每个对象都具备着能被被回收的方法。---->每个对象都具备的方法,应该定义在Object对象中。

        Object类中有一个方法-->protected void finalize();

        方法解释:当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

        子类重写 finalize 方法,以配置系统资源或执行其他清除。

        System类中有一个方法-->static void gc();

        方法解释: 运行垃圾回收器。

        调用 gc 方法暗示着 Java 虚拟机做了一些努力来回收未用对象,以便能够快速地重用这些对象当前占用的内存。

        当控制权从方法调用中返回时,虚拟机已经尽最大努力从所有丢弃的对象中回收了空间。

        调用 System.gc() 实际上等效于调用:Runtime.getRuntime().gc();

        内存中的垃圾何时回收?

        当堆内存中产生垃圾以后,并不是马上进行垃圾回收,因为垃圾回收是一个线程,调用垃圾回收方法会抢占CPU的使用权,会影响主函数的运行。

        何时回收?会判断堆内存的剩余空间大小,当到达警戒容量的时候启动垃圾回收。


      单线程在执行时的过程:
        当一个程序被加载,主函数进入栈内存,到栈底;调用第一个方法,执行第一个方法,第一个方法结束后出栈;调入第二个方法...

        当所有方法执行结束后,主函数完成任务,主函数出栈。

    创建新执行线程有两种方法。

      |--方法一:将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。

      |--方法二:是声明实现 Runnable 接口的类。该类然后实现 run 方法。

      然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。接下来可以分配并启动该子类的实例。

      多线程的创建方式一:

        线程的创建JVM是做不了的,要用JVM所在的操作系统完成。我们无法直接操作操作系统来创建新的线程。那么JVM提供了这样的对象来调用底层的方法来创建新的线程。

        java.lang包下面包含Thread类:

        线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

        每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。

        当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。

        当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。

        Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

        调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。

        非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。

        创建线程的目的是为了开辟一条新的路径,去运行指定代码,与其他的代码一起运行。运行指定代码,就是这个执行路径的任务。

        JVM创建的主线程的任务都定义在了主函数(main方法)内。

      那么自定义的线程的内容(任务)定义在哪里呢??

        Thread类用于描述线程,而线程是有内容(任务)的,所以Thread类要有对线程内容(任务)的描述。

        那么这个任务就是通过Thread类中的run();方法来体现。也就是说Thread类中的run();方法封装了新线程的内容(任务)。

        这就是为什么要继承Thread并且要重写run()方法,而不是直接new Thread();的原因。

        运行:不能直接调用run()方法,那样跟主函数调用一样,还是单线程。要用start()方法。

        void start() : 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

      1.获取线程的名称-->Thread-线程编号。(创建时产生)

      2.获取运行时线程名称-->static Thread currentThread() 返回对当前正在执行的线程对象的引用。

      重点:与单线程相比,多线程的每一个线程都相当于都占用一个单独的栈内存,进行各自方法的弹栈,压栈操作。

      在多线程中,当主线程结束的时候,并不意味这JVM结束,只要有 线程还活着,JVM就不会结束。

      多线程的四种状态(重点):
                            临时阻塞状态
                              |
                              |
    线程创建—————————start()———————>线程运行<——————————————sleep(time)——————————————>冻结状态。
                              | <———————————notify() / wait()———————————>冻结状态。
                              |
                              |
                          run()方法运行结束
                          线程运行结束或者
                              stop()
                              |
                              |
                              |
                            线程消亡
        CPU的执行资格:可以被CPU处理,在处理队列中

        CPU的执行权:正在被CPU处理。

        运行中的线程既具备执行资格,又有执行权。

        冻结状态释放执行权,并释放执行资格。

        假设现在有A B C D 四个线程,当A进入运行状态时候,那么B C D三个线程出于什么状态呢?他们有执行资格,但是却没有执行权,既不是运行状态也不是冻结状态

        那么我们称B C D这样的线程状态为临时阻塞状态。

        临时阻塞状态:具备执行资格,但不具备执行权,正在等待拿到执行权。

      多线程的创建方式二:
        如果当前任务要使用多线程来运行,而当前的类已经继承了一个父类,根据Java语言的特性,只能实现单继承,那么这个时候要怎么办?

        解决方法一:让该类的父类继承Thread类并覆盖run()方法(不得已而为之)。

        解决方法二:声明实现 Runnable 接口的类。该类然后实现 run 方法。 然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。接下来可以分配并启动该子类的实例。

        两种方法哪一种更好?

      实现Runnable更好:

        1.可将线程的任务从线程的子类中分离出来,进行了单独的封装,安装面向对象的思想将任务封装成对象。

        2.避免了Java单继承的局限性。

      多线程实例:卖票---->要求:开启四个线程,共同买100张票。

      方法一:100张票设置为静态方法,将其放入静态区域,而非堆中,共享资源。

      方法二:实现runnable接口,创建一个线程任务【对象】,创建四条线程,将任务对象传给四个线程。

      补充:this
        局部变量和成员变量命名冲突。 局部变量中的值在栈中,成员变量作为对象的属性被封装到堆中,现在要把栈中的局部变量赋值给堆中的局部变量。

        这里介绍关键字:this---->代表对象。代表哪个对象呢??代表当前对象

        this:代表当前方法所属对象的引用。

        this内存详解:

        情景一:执行:Person p = new Person("SNOOPY");

          1.首先,在栈中创建引用p

          2.在堆中创建地址为0x0056的Person对象并对其默认初始化。name=null;

          3.因为new的对象要传值,所以调用对应的构造函数,将其进栈执行。注意:该构造函数进栈的是被Person("SNOOPY")对象调用的,所以this=0x0056;

        所以对象中的name被赋值为SNOOPY。

          4.最后,将0x0056赋值给p。

          注意:当对象引用p调用该对象的其他方法时,当方法一进栈,this即被赋值为p代表的对象的内存地址-->0x0056。

        情景二:执行:Person p = new Person("SNOOPY",20);

          源码:

    Person(String name){
        this.name = name;
    }
    Person(String name,int age){
        this(name);//注意:不能写成this.Person(name);因为构造函数是对象初始化的,而this指代的是对象,对象都没有初始化所以不能调用这个方法。
        this.age = age;
    }

          1.首先,在栈中创建引用p

          2.在堆中创建地址为0x0089的Person对象并对其默认初始化。name=null;age=0;

          3.因为new的对象要传值,此时栈中的局部变量name=SNOOPY;age=20;所以调用对应的构造函数,将其进栈。this被赋值为0x0089

          4.将栈中的name和age的值赋值给堆中地址为0x0089的对象的成员变量。在此时发现this(name)。

          5.调用Person(String name)进栈,并进行赋值。结束后弹栈。

          6.结束后弹栈Person(String name,int age)。最后,将0x0089赋值给p。

        注意:不能用此方法重复的递归调用其它方法,如果一直有方法进栈而没有方法出栈,最后会导致栈内溢出。

      补充:什么时候用静态?

        |--静态变量

          当分析对象中所具备的成员变量的值都相同的时候,这个时候这个成员就可以被修饰成静态。

          只要数据在对象中不同,就是对象特有的数据,必须以非静态的方式存储。

          如果是相同的数据,对象不需要修改只需要使用即可,不需要存放在对象中,可以修饰成静态的。

        |--静态方法

          只有一点:就是该方法功能是否访问到对象中特有的数据。

          即:从源代码看,该方法功能是否需要访问对象中的非静态变量,如果需要的话,那么该功能就是非静态的,如果不需要,那就用静态修饰符进行修饰。

        |--静态块

          随着类加载而执行,而且只执行一次。

          代码块{//给所有对象初始化。}

      补充:JAVA类加载具体过程

        JVM栈由堆、栈、本地方法栈、方法区等部分组成。

        方法区:存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。理解为代码段,是用来存放代码的,包括静态区和非静态区。

        堆内存:所有通过new创建的对象的内存都在堆中分配。

        栈内存:每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

        本地方法栈:用于支持native方法的执行,存储了每个native方法调用的状态。

      当一个类要进行加载:

        1.代码首先加载到方法区:如果是静态的,就放到静态区,如果是非静态的,就放到非静态区,根据内容的不同,按照自上而下的顺序进行存放。

        2.当遇到要用到其他类的情况时候,同样按照顺序进行加载。

        3.当遇到静态方法调用的时候,先加载类,类加载接受后,静态方法进栈。注意:栈只是存放静态方法的局部变量,为其开辟空间,其它的语句只有在该方法执行的时候才依次为其开辟空间。当方法结束后,自动弹栈。

        4.当遇到创建对象时例如:Person p = new Person("java",20);此时mian()方法在栈底,添加一个变量p,并在堆中开辟空间地址为0x0096,将Person中的非静态变量初始化。

          然后再将构造函数Person(String name,int age)进栈,此构造函数被内存地址为0x0096的对象调用,所以this=0X0096;

          然后name=java,age=20;初始化完,将0X0096赋值给p。


    多线程安全问题:
      实例:

    class Ticket implements runnable{
    			private int num = 100;
    			public void run(){
    				while(ture){
    					if(num > 0){
    						System.out.println(Thread.currentThread.getName()+"===sale==="+num--);
    					}
    				}
    			}
    		}
    

      

      假如共有三条线程卖票,当num=1时,线程1的执行到了判断语句,刚执行完判断语句,CPU执行权被线程2抢去,此时的num还没有执行【--】的操作,判断条件依然成立。

      而线程2也刚刚执行完判断语句1,CPU执行器被线程3抢去,线程3刚刚执行完判断语句之后,CPU执行权又给了线程1,因为之前线程1已经判断过了,所以不需要在判断了,所以线程1卖了1号票。

      然后线程2卖了0号票,线程3卖了-1号票。

      上述现象为多线程的安全问题。

      多线程安全问题产生的原因:

        1.多个线程操作共享数据

        2.操作共享数据的线程代码有多条

      理解:当一条线程在执行操作共享数据的多条代码时,其他线程参与了运算,就会导致线程安全问题。

      如何解决?

        当一条线程在执行操作共享数据的多条代码时,【不要让】其他线程参与了运算,就不会导致线程安全问题。

        在Java中,用同步代码块(同步锁)可以解决这个问题。

        同步代码块格式:

        synchronized(对象){

          需要被同步的代码;

        }
      此synchronized相当于是一个开关,没有线程占用时候打开,当有一个线程操作该共享数据时该开关关闭。

      为什么锁里面要有对象?

        因为后期要对这个同步中的线程进行监视,而监视的方法在锁对象中。

      同步锁的优点与弊端:

        优点-->解决了线程安全问题。

        弊端-->相对降低了效率,因为同步外的线程都会判断同步锁。

      同步的前提:

        必须有多个线程并使用【同一个锁】。

      实例:

        两位储户,每个人都要到银行存钱,每次100,一共三次。

    public class ThreadTest {
            public static void main(String[] args) {
                Cus c = new Cus();
                Thread t1 = new Thread(c);
                Thread t2 = new Thread(c);
                t1.start();
                t2.start();
            }
        }
        class Bank{
            private double sum;
            public void add(double num){
                sum +=  num;
                System.out.println(Thread.currentThread().getName()+"----sum=="+sum);
            }
        }
        class Cus implements Runnable{
            private Bank b = new Bank();
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    b.add(100);
                }
            }
        }

    没有加锁前,输出结果:

    Thread-1----sum==200.0
    Thread-0----sum==200.0
    Thread-1----sum==300.0
    Thread-0----sum==400.0
    Thread-1----sum==500.0
    Thread-0----sum==600.0

      说明在add()方法中存在了线程安全问题。(只要在add()方法中输出前面加一条沉睡语句,即可出现线程安全问题)

      加锁后:(还可以在add()中添加锁)

    class Cus implements Runnable{
            private Bank b = new Bank();
            @Override
            public void run() {
                synchronized (b) {
                    for (int i = 0; i < 3; i++) {
                        b.add(100);
                    }
                }
            }
        }

    运行结果:

    Thread-0----sum==100.0
    Thread-0----sum==200.0
    Thread-0----sum==300.0
    Thread-1----sum==400.0
    Thread-1----sum==500.0
    Thread-1----sum==600.0

      同步函数:
      可以直接用synchronized关键字修饰函数

    class Cus implements Runnable{
                private Bank b = new Bank();
                @Override
                public synchronized void run() {
                    for (int i = 0; i < 3; i++) {
                        b.add(100);
                    }
                }
            }

      上面这段代码其实是有问题的,在run()方法上面加上同步锁,意味着只要第一个线程开启,这个线程就获得了执行权,只要该线程不自动让出执行权或者结束运行,
      该线程永远霸占该共享资源。
      解决:

    class Cus implements Runnable{
                private Bank b = new Bank();
                @Override
                public void run() {
                    for (int i = 0; i < 3; i++) {
                        operation();
                    }
                }
                public synchronized void operation(){
                    b.add(100);
                }
            }

      问题来了:那么同步函数到底是用的那个锁呢?

      验证同步函数的锁:

    public class ThreadTest {
                public static void main(String[] args) throws InterruptedException {
                    Cus c = new Cus();
                    Thread t1 = new Thread(c);
                    Thread t2 = new Thread(c);
                    t1.start();
                    c.flag = false;
                    t2.start();
                }
            }
            class Bank{
                private double sum;
                
                public double getSum() {
                    return sum;
                }
            
                public void setSum(double sum) {
                    this.sum = sum;
                }
            
                public void add(double num){
                    sum +=  num;
                    System.out.println(Thread.currentThread().getName()+"----sum=="+sum);
                }
            }
            
            class Cus implements Runnable{
                private Bank b = new Bank();
                public boolean flag = true;
                @Override
                public void run() {
                    if(flag){
                        while(true){
                            operation();
                        }                
                    }else{
                        while(true){
                            synchronized(new Object()){
                                if(b.getSum() < 10000){
                                    b.add(100);
                                    System.out.println("-----------------synchronized--------------");
                                }else{break;}
                            }
                        }
                    }
                }
                public synchronized void operation(){
                    if(b.getSum() < 10000){
                        b.add(100);
                        System.out.println("-------------obj-----------------");
                    }
                }
            }

      结果为:

    Thread-0----sum==100.0
    Thread-1----sum==100.0
    -----------------synchronized--------------
    -----------------synchronized--------------
    Thread-1----sum==200.0
    -----------------synchronized--------------
    Thread-0----sum==300.0
    -----------------synchronized--------------
    Thread-1----sum==400.0
    -----------------synchronized--------------
    ...


      上述结果表明:同步函数与同步块并不是一个对象。如果是一个对象就不会出现上述现象。

      使用this关键字。解释见前面补充this关键字。

      将new Object()修改为this即可。

      总结:同步函数使用的是this。

      同步函数和同步代码块的区别:

        同步函数锁是固定的this对象。

        同步代码块的锁匙任意的对象。

      验证静态同步函数的锁。

    public class ThreadTest {
                public static void main(String[] args) throws InterruptedException {
                    Cus c = new Cus();
                    Thread t1 = new Thread(c);
                    Thread t2 = new Thread(c);
                    t1.start();
                    Thread.sleep(1);
                    c.flag = false;
                    t2.start();
                }
            }
            class Bank{
                private double sum;
                
                public double getSum() {
                    return sum;
                }
            
                public void setSum(double sum) {
                    this.sum = sum;
                }
            
                public void add(double num){
                    sum +=  num;
                    System.out.print(Thread.currentThread().getName()+"----sum=="+sum);
                }
            }
            
            class Cus implements Runnable{
                private static Bank b = new Bank();
                public boolean flag = true;
                @Override
                public void run() {
                    if(flag){
                        while(true){
                            operation();
                        }                
                    }else{
                        while(true){
                            synchronized(this){
                                try {
                                    Thread.sleep(5);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                if(b.getSum() < 10000){
                                    b.add(100);
                                    System.out.println("-----------------synchronized--------------");
                                }else{break;}
                            }
                        }
                    }
                }
                public static synchronized void operation(){
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(b.getSum() < 10000){
                        b.add(100);
                        System.out.println("-------------obj-----------------");
                    }
                }
            }    

    运行结果:

    Thread-0----sum==100.0-------------obj-----------------
    Thread-1----sum==200.0-----------------synchronized--------------
    Thread-1----sum==400.0-----------------synchronized--------------
    Thread-0----sum==400.0-------------obj-----------------
    Thread-0----sum==500.0-------------obj-----------------
    Thread-1----sum==600.0-----------------synchronized--------------
    Thread-0----sum==800.0-------------obj-----------------
    Thread-1----sum==800.0-----------------synchronized--------------    

      从上述结果来看:静态同步函数锁用的对象不是this。因为静态方法中不能含有this。所以静态同步函数锁用对象是this.getClass();

      getClass():只要是同一个类产生的对象,getClass()都是相同的.

      解决方法:this.getClass();或者类名.class

      补充:在内存中,非静态区都会有一个this所属,因为非静态区数据只能被当前对象访问。

  • 相关阅读:
    Git_学习_05_ 解决冲突
    Git_学习_04_ 多人协作开发的过程
    PostgreSQL与Oracle对应的函数
    Mysql学习_02_mysql数据导入导出
    【SPOJ】1812. Longest Common Substring II(后缀自动机)
    【BZOJ】2555: SubString(后缀自动机)
    【BZOJ】3172: [Tjoi2013]单词(后缀自动机)
    【SPOJ】8222. Substrings(后缀自动机)
    【wikioi】3160 最长公共子串(后缀自动机)
    【BZOJ】1079: [SCOI2008]着色方案(dp+特殊的技巧)
  • 原文地址:https://www.cnblogs.com/snoopylovefiona/p/4682521.html
Copyright © 2011-2022 走看看