Java中的各种区别

文章目录

    • 十 heap和stack有什么区别
      • 1.定义
    • 十五 JAVA锁有哪些种类,以及区别
        • 公平锁/非公平锁
        • 可重入锁
        • 独享锁/共享锁
        • 互斥锁/读写锁
        • 乐观锁/悲观锁
        • 分段锁
        • 偏向锁/轻量级锁/重量级锁
    • 十六 编译与解释
    • 十七 synchronized 和concurrent
        • 一.Java多线程可以通过:
        • 二.synchronized关键字加锁的缺陷:
        • 三.Lock和synchronized的选择:

##一 i++ 和 ++I的区别
它们都等价于i=i+1,区别在于i++是先给表达式赋值,再给i自加;++i先自加,再赋值

##二 &&和&区别:
###1 短路
&&具有短路的功能,如果第一个表达式为false,则不再计算第二个表达式
###2 位运算
&可以用作位运算符,当&操作符两边的表达式不是布尔类型时,&表示按位与操作
##三 方法重写和方法重载的区别
###1 概念
####override(重写,覆盖)
如果在子类中定义某方法与其父类某方法有相同的名称和参数,我们说父类中该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了.
####overload(重载,过载)
如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型或有不同的参数次序,则称为方法的重载(Overloading)。不能通过访问权限、返回类型、抛出的异常进行重载.
###2 多态性
方法的重写(Overriding)和重载(Overloading)是Java方法多态性的不同表现。重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。
###3 语法特点:
####Override 特点

  • 1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
  • 2、覆盖的方法的返回值必须和被覆盖的方法的返回一致;
  • 3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
  • 4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
    ####Overload 特点
  • 1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int, float), 但是不能为fun(int, int));
  • 2、不能通过访问权限、返回类型、抛出的异常进行重载;
  • 3、方法的异常类型和数目不会对重载造成影响;
  • 4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,不会达到重载的效果。
    ###4 总结
    ####override(重写,覆盖)
  • 1、方法名、参数、返回值相同。
  • 2、子类方法不能缩小父类方法的访问权限。
  • 3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
  • 4、存在于父类和子类之间。
  • 5、方法被定义为final不能被重写。
    ####overload(重载,过载)
  • 1、参数类型、个数、顺序至少有一个不相同。
  • 2、不能重载只有返回值不同的方法名。
  • 3、存在于父类和子类、同类中。

##四 抽象类和接口的区别
###1 概念
####抽象类
含有abstract修饰符的类即为抽象类,抽象类不能实例化。含有抽象方法的类必须定义为抽象类,抽象类中的方法不必是抽象的。抽象类中定义的抽象方法必须在具体子类中实现,所以,不能有抽象构造方法或抽象静态方法。
####接口
接口可以说成是抽象类中的一种特例,接口中的所有方法都必须是抽象的。接口中的方法默认为public abstract类型,接口中的成员变量默认为public static final类型。
###2 语法方面:

  • 1.抽象类可以有构造方法、普通成员变量、非抽象的普通方法、静态方法,接口中不能有构造方法。
  • 2 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
  • 3 抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
    1. 一个类可以实现多个接口,但只能继承一个抽象类。
      ###3 在应用上:
      接口是在系统架构设计方面发挥作用,用于定义模块之间的通信契约。而抽象类是在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式

##五 arraylist和linkedlist区别 (接口,实现形式,性能)
ArrayList实现了List接口,基于线性数组实现,随机访问性能出色,由于长度改变是需要需要反复调整数组空间大小做增删操作时的时间消耗较大;
LinkedList实现了List和Deque接口,内部以链表的形式保存,随机访问性能较差,插入、删除性能出色
##六 arraylist和vector区别(线程安全,容量增加)
Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
##七 hashmap和hashtable(线程安全,null)
HashTable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比HashTable的性能高一点;但如果多个线程访问同一个Map对象时,使用HashTable实现类会更好
HashTable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发NullPointerException异常,但HashMap可以使用null作为key或value
##八 转发和重定向的区别

  • 1.重定向时浏览器上的网址改变,转发是浏览器上的网址不变
  • 2.重定向实际上产生了两次请求,转发只有一次请求
    重定向:发送请求 ->服务器运行->响应请求,返回给浏览器一个新的地址与响应码–>浏览器根据响应码,判定该响应为重定向,自动发送一个新的请求给服务器,请求地址为之前返回的地址–>服务器运行–>响应请求给浏览器
    转发:发送请求->服务器运行->进行请求的重新设置,例如通过request.setAttribute(name,value)–>根据转发的地址,获取该地址的网页–>响应请求给浏览器
  • 3.重定向时的网址可以是任何网址,转发的网址必须是本站点的网址
  • 4 携带的数据
    重定向:以前的request中存放的变量全部失效,并进入一个新的request作用域。
    转发:以前的request中存放的变量不会失效,就像把两个页面拼到了一起。
    ##九 session和cookie的区别
  • 1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
  • 2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
  • 3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
  • 4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

十 heap和stack有什么区别

    java的内存分为两类,一类是栈内存,一类是堆内存。
    栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
    堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。

##十一 sleep()和 wait()有什么区别?

###1 简述
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
###2 详述
sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。
###3 面试提示
对于wait的讲解一定要配合例子代码来说明,才显得自己真明白。
##十二 String、StringBuffer和StringBuilder的区别
###1 概念
####1.String
字符串常量,字符串长度不可变。Java中String是immutable(不可变)的。用于存放字符的数组被声明为final的,因此只能赋值一次,不可再更改。
####2 StringBuffer
字符串变量(Synchronized,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer,如果想转成String类型,可以调用StringBuffer的toString()方法。
Java.lang.StringBuffer线程安全的可变字符序列。在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。可将字符串缓冲区安全地用于多个线程。
StringBuffer上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append(“le”) 会使字符串缓冲区包含“startle”,而 z.insert(4, “le”) 将更改字符串缓冲区,使之包含“starlet”。
####3. StringBuilder
StringBuilder:字符串变量(非线程安全)。在内部,StringBuilder对象被当作是一个包含字符序列的变长数组。
java.lang.StringBuilder是一个可变的字符序列,是JDK5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。
其构造方法如下:

构造方法 描述
StringBuilder() 创建一个容量为16的StringBuilder对象(16个空元素)
StringBuilder(CharSequence cs) 创建一个包含cs的StringBuilder对象,末尾附加16个空元素
StringBuilder(int initCapacity) 创建一个容量为initCapacity的StringBuilder对象
StringBuilder(String s) 创建一个包含s的StringBuilder对象,末尾附加16个空元素
在大部分情况下,效率:StringBuilder > StringBuffer。这主要是由于前者不需要考虑线程安全。

###2 三者区别

####String类型和StringBuffer的主要性能区别:
String是不可变的对象, 因此在每次对String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。
使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下。
在某些特别情况下, String 对象的字符串拼接其实是被 Java Compiler 编译成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,例如:

String s1 = “This is only a” + “ simple” + “ test”; 
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”); 
	生成 String s1对象的速度并不比 StringBuffer慢。其实在Java Compiler里,自动做了如下转换:
	Java Compiler直接把上述第一条语句编译为:
String s1 = “This is only a simple test”; 
	所以速度很快。但要注意的是,如果拼接的字符串来自另外的String对象的话,Java Compiler就不会自动转换了,速度也就没那么快了,例如:
String s2 = “This is only a”; 
String s3 = “ simple”; 
String s4 = “ test”; 
String s1 = s2 + s3 + s4; 
	这时候,Java Compiler会规规矩矩的按照原来的方式去做,String的concatenation(即+)操作利用了StringBuilder(或StringBuffer)的append方法实现,此时,对于上述情况,若s2,s3,s4采用String定义,拼接时需要额外创建一个StringBuffer(或StringBuilder),之后将StringBuffer转换为String;若采用StringBuffer(或StringBuilder),则不需额外创建StringBuffer。

###3 使用策略

(1)**基本原则**:如果要操作少量的数据,用String ;单线程操作大量数据,用StringBuilder ;多线程操作大量数据,用StringBuffer。

(2)**不要使用String类的"+"来进行频繁的拼接**,因为那样的性能极差的,应该使用StringBuffer或StringBuilder类,这在Java的优化上是一条比较重要的原则.

(3)为了获得更好的性能,在构造 StringBuffer 或 StringBuilder 时应 **尽可能指定它们的容量** 。当然,如果你操作的字符串长度(length)不超过 16 个字符就不用了,当不指定容量(capacity)时默认构造一个容量为16的对象。不指定容量会显著降低性能。

(5)相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。而在现实的模块化编程中,负责某一模块的程序员不一定能清晰地判断该模块是否会放入多线程的环境中运行,因此:除非确定系统的瓶颈是在 StringBuffer 上,并且**确定你的模块不会运行在多线程模式下**,才可以采用StringBuilder;否则还是用StringBuffer。

(6) String覆盖了equals方法和hashCode方法,而其他两个没有,因此放入java集合中会出现问题

##十三、Iterator和ListIterator的区别

  1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能

  2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

  3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

  4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
    ##十四、线程和进程的区别

1.定义

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

###2.关系
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

###3.区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  1. 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
  2. 线程的划分尺度小于进程,使得多线程程序的并发性高。
  3. 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  4. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  5. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
    ###4.优缺点
    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
    ##十四、Callable和Runnable的区别

编写多线程程序一般有三种方法,Thread,Runnable,Callable.

###Runnable和Callable的区别是,
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会跑出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future 形式类型、并返回 null 作为底层任务的结果。Future接口的定义如下:

###Future模式
Future模式在请求发生时,会先产生一个Future凭证给发出请求的客户,它的作用就像是Proxy物件,同时,由一个新的执行线程持续进行目标物件的生成(Thread-Per-Message),真正的目标物件生成之后,将之设定至Future之中,而当客户端真正需要目标物件时,目标物件也已经准备好,可以让客户提取使用。
结合JDK的Future来看,就是你run线程后,你可以把线程的返回值赋给Future并返回一个Future对象。这时你可以立即拿到这个对象,然后进行下面的逻辑。但是如果你要get这个Future中的线程结果,就会被阻塞直到线程结束。
就相当于现在的期房,你把手续和钱都交上去了,就可以马上拿到合同,但只有合同没有房子。这个时候你已经是有房一族了,你可以先去买家电买装修(走下面的其他逻辑)。但是你要把家电和装修放进去,就必须等到房子完工(阻塞)。

###网上的例子


import java.util.concurrent.*;  
  
public class Test {   
    public static void main(String[] args) throws InterruptedException,  
            ExecutionException {  
        final ExecutorService exec = Executors.newFixedThreadPool(5);  
        Callable<String> call = new Callable<String>() {  
            public String call() throws Exception {  
                Thread.sleep(1000 * 10);//休眠指定的时间,此处表示该操作比较耗时  
                return "Other less important but longtime things.";  
            }  
        };  
        Future<String> task = exec.submit(call);  
        //重要的事情  
        System.out.println("Let's do important things. start");  
        Thread.sleep(1000 * 3);  
        System.out.println("Let's do important things. end");  
  
        //不重要的事情  
        while(! task.isDone()){  
            System.out.println("still waiting....");  
            Thread.sleep(1000 * 1);  
        }  
        System.out.println("get sth....");  
        String obj = task.get();  
        System.out.println(obj);  
        //关闭线程池  
        exec.shutdown();  
    }  
}  

十五 JAVA锁有哪些种类,以及区别

在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:

  • 公平锁/非公平锁
  • 可重入锁
  • 独享锁/共享锁
  • 互斥锁/读写锁
  • 乐观锁/悲观锁
  • 分段锁
  • 偏向锁/轻量级锁/重量级锁
  • 自旋锁

上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

公平锁/非公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

synchronized void setA() throws Exception{
    Thread.sleep(1000);
    setB();
}

synchronized void setB() throws Exception{
    Thread.sleep(1000);
}

上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

独享锁/共享锁

独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。

对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。

互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentrantLock
读写锁在Java中的具体实现就是ReadWriteLock

乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
悲观锁在Java中的使用,就是利用各种锁。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

偏向锁/轻量级锁/重量级锁

这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

十六 编译与解释

编译是将源程序翻译成可执行的目标代码,翻译与执行是分开的;而解释是对源程序的翻译与执行一次性完成,不生成可存储的目标代码。这只是表象,二者背后的最大区别是:对解释执行而言,程序运行时的控制权在解释器而不在用户程序;对编译执行而言,运行时的控制权在用户程序。

解释具有良好的动态特性和可移植性,比如在解释执行时可以动态改变变量的类型、对程序进行修改以及在程序中插入良好的调试诊断信息等,而将解释器移植到不同的系统上,则程序不用改动就可以在移植了解释器的系统上运行。同时解释器也有很大的缺点,比如执行效率低,占用空间大,因为不仅要给用户程序分配空间,解释器本身也占用了宝贵的系统资源。

编译器是把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快;
而解释器则是只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的.

Java很特殊,Java程序也需要编译,但是没有直接编译称为机器语言,而是编译称为字节码,然后在Java虚拟机上用解释方式执行字节码。

十七 synchronized 和concurrent

一.Java多线程可以通过:

  1. synchronized关键字
  2. Java.util.concurrent包中的lock接口和ReentrantLock实现类

这两种方式实现加锁。

二.synchronized关键字加锁的缺陷:

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

2)线程执行发生异常,此时JVM会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

但是采用synchronized关键字来实现同步的话,就会导致一个问题:

如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

对比:
 1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问,以下是ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

c)tryLock(long timeout,TimeUnit unit),   如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

三.Lock和synchronized的选择:

总结来说,Lock和synchronized有以下几点不同:

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;(I/O和Synchronized都能相应中断,即不需要处理interruptionException异常)

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,如果是线程并发量不大的情况下,那么Synchronized因为自旋锁,偏向锁,轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比ReentrantLock高效,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。Synchronized和ReentrantLock他们的开销差距是在释放锁时唤醒线程的数量,Synchronized是唤醒锁池里所有的线程+刚好来访问的线程,而ReentrantLock则是当前线程后进来的第一个线程+刚好来访问的线程。所以说,在具体使用时要根据适当情况选择。

你可能感兴趣的:(Java基础,linkedlist,arraylist,java,区别,进程线程)