JAVA 面试题 合辑(一)

本文汇总了JAVA面试中的基础、集合、多线程、JVM、HTTP及Servlet等多个领域的核心知识点,包括==与equals()的区别、String与StringBuilder、线程安全、线程池、垃圾回收机制、HTTP原理等,旨在帮助Java开发者全面准备面试。

JAVA 面试题 合辑(一)https://blog.csdn.net/haponchang/article/details/92741553

JAVA 面试题 合辑(二)JAVA 面试题 合辑(二)_haponchang的博客-CSDN博客

JAVA 面试题 合辑(三)https://blog.csdn.net/haponchang/article/details/92833016

目录

JAVA 基础

==与equals()方法的区别?为什么重写equals()方法就必须重写hashCode()方法?

String、StringBuilder、StringBuffer区别

String类能被继承吗?被final修饰为什么不可被继承?

int和Integer的区别?

实例化顺序

Object类常用的方法有哪些

深拷贝和浅拷贝区别

抽象类和接口的区别

什么是泛型、为什么要使用以及泛型擦除

Error 和 Exception 的区别 ?CheckedException和RuntimeException 的区别

System.arraycopy() 和 Arrays.copyOf()两者之间的区别?

反射的原理

JAVA 集合

Collection和Collections的区别

Java中的集合类及关系

fail-fast 与 fail-safe 机制有什么区别

ArrayList底层实现原理

LinkeList底层实现原理

ArrayList和LinkeList区别

ArrayList和Vector区别

LinkedList的get(i)方法是怎样实现的?

HashMap实现原理

HashMap为什么用红黑树?用其它树不行么?

HashTable实现原理

HashMap和HashTable区别

Iterator

ConcurrentHashMap实现

多线程

进程和线程的区别

多线程的实现方式?start()后线程立即启动么?

什么是阻塞?阻塞的情况?

如何停止一个线程?

什么是线程安全与非线程安全?如何保证线程安全?

线程生命周期

守护线程

产生死锁

死锁的预防

Java中活锁和死锁有什么区别、死锁与饥饿的区别

怎么检测一个线程是否拥有锁

如何实现分布式锁

java 内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等

volatile关键字

synchronized关键字

Synchronized锁是怎么实现的,放在字节码的什么位置

Lock和Synchronized区别?

CAS ?

通过CAS的实现Atomic类库

可中断锁

公平锁

可重入锁

读写锁

锁的升级降级

多线程如何互相通信?

sleep和wait的区别(考察的方向是是否会释放锁)

wait与notify

Condition

同步工具的使用

CountdownLatch

(屏障)CyclicBarrier

(计数信号量)Semaphore

什么是Copy-On-Write容器?

Semaphore和LockSupport的作用?

ThreadLocal

Java并发容器

Java并发包分析

阻塞队列 

在日常的应用开发中如何进行选择队列实现

线程池优势?常见线程池?

创建多少线程数才合适?

如何拿到子线程的返回结果?

Java中的ThreadPoolExecutor类构造器

ThreadPoolExecutor线程池的使用

fork-Join框架

JVM

Java虚拟机基本概念说明

局部变量、实例变量的线程安全性?

jvm 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的jvm 参数。

GC的一些机制,每种GC的区别

ClassLoader加载机制?双亲委托加载模型的好处?

Java会出现内存泄漏吗?怎样预防和处理OOM?

垃圾回收算法

强引用,软饮用、弱引用、幻象引用

jstack,jstat,jmap,jconsole怎么用

32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?32 位和 64 位的 JVM,int 类型变量的长度是多数?

jvm线上调优

HTTP、servlet

HTTP工作原理

说说你知道的几种 HTTP 响应码,比如 200, 302, 404。

http1.0 和 http1.1 有什么区别

简述 Http 请求 get 和 post 的区别以及数据包格式。  ​

HTTPS 的加密方式是什么,讲讲整个加密解密流程

TIME_WAIT 和 CLOSE_WAIT 的区别。

TCP 三次握手和四次挥手的流程,为什么断开连接要 4 次,如果握手只有两次,会出现什么。

TCP与UDP区别?

当你用浏览器打开一个链接的时候,计算机做了哪些工作步骤。

如何避免浏览器缓存

讲解JSP中的四种作用域

Servlet 生命周期

Filter和Listener

cookie和session

转发(forward)和重定向(redirect)的区别? 

常见的缓存策略有哪些,你们项目中用到了什么缓存系统,如何设计的

Spring

SpringMVC工作原理

SpringIOC如何实现

SpringAOP 事物隔离级别

SpringAOP

SpringAOP 注解

Spring boot启动过程

SpringBoot内置web容器及配置

BeanFactory 和 ApplicationContext 有什么区别


JAVA 基础

==equals()方法的区别?为什么重写equals()方法就必须重写hashCode()方法?

1. ==在比较基本数据类型时比较的是值,比较两个对象时比较的是地址值。 equals()方法存在于Object类中,Object类中equals()方法底层依赖的是==操作,在所有没有重写equals()的类中,调用equals()其实和使用==的效果一样,也是比较的地址值。String重写了equals(),底层比较的是两个String对应位置的char字符是否==。

2. Object.hashCode()方法是一个本地native方法,返回的是对象引用中存储的对象的内存地址;

基于散列的集合(HashSet、HashMap和Hashtable)存放key时,调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中存储的位置;

所以如果equals方法返回true,那么两个对象的hasCode()返回值必须一样;

StringStringBuilderStringBuffer区别

不可变:String,底层是 final char value[]   。final类型char数组

subString返回新对象: return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);

可变:StringBuffer、StringBuilder,底层是char[] value;  一个char类型的数组,非final类型,这一点与String类不同

构造函数(StringBuffer、StringBuilder相同):

public StringBuffer() {

    super(16);//创建一个默认大小为16的char型数组

}

public StringBuffer(int capacity) {

    super(capacity);//自定义创建大小为capacity的char型数组

}

线程安全:StringBuffer

线程不安全:StringBuilder

append方法和insert方法的代码,StringBuilder和StringBuffer的方法实现基本上一致,

append,insert,delete方法最根本上都是调用System.arraycopy()这个方法来达到目的

不同的是StringBuffer类的方法前多了个synchronized关键字,即StringBuffer是线程安全的

String类能被继承吗?被final修饰为什么不可被继承?

不能,String被final修饰,不可被继承。原因如下:

1. 字符串常量池的需要:字符串常量池是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;

2. 允许String对象缓存HashCode:Java中String对象的哈希码被频繁地使用,字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存。这也是一种性能优化手段,意味着不必每次都去计算新的哈希码;

3. 安全性:String常被用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

intInteger的区别?

Integer是int的包装类。Java 5 中,引入了自动装箱和自动拆箱功能(boxing/unboxing)。

装箱:将基本类型用它们对应的引用类型包装起来;

拆箱:将包装类型转换为基本数据类型;

在 Java 5 中新增了静态工厂方法 valueOf,在调用它的时候会利用一个缓存机制,带来了明显的性能改进。按照 Javadoc,这个值默认缓存是 -128 到 127 之间。

查看Integer类源码,发现里面有一个私有的静态内部类IntegerCache,而如果直接将一个基本数据类型的值赋给Integer对象,则会发生自动装箱,其原理就是通过调用Integer类的public static Integer valueOf(将int类型的值包装到一个对象中 ,就是在Integer类中有一个静态内部类IntegerCache,在IntegerCache类中有一个Integer数组,用以缓存当数值范围为-128~127时的Integer对象。

注意:可以通过下面这个参数设置最大值:-XX:AutoBoxCacheMax(JDK5)

属性是在使用Oracle/Sun JDK 6,在server模式下,使用:

-XX:AutoBoxCacheMax=NNN

参数即可将Integer的自动缓存区间设置为[-128,NNN]。(-128不可改)

实例化顺序

类加载器实例化时进行的操作步骤:加载–>连接->初始化。

父类静态代变量、父类静态代码块、

子类静态变量、子类静态代码块、

父类非静态变量(父类实例成员变量)、父类构造函数、

子类非静态变量(子类实例成员变量)、子类构造函数。

Object类常用的方法有哪些

1. equals()、hashCode()、getClass()、toString()--默认字符串:类名+哈希编码;

2. clone():实现对象的浅复制(当改变其中一个对象的引用类型属性实例的属性时,另一个对象相应的引用类型的属性实例中的属性也会发生变化),只有实现了Cloneable接口才可以调用该方法。否则抛出CloneNotSupportedException;深复制:引用类型属性也要实现clone()方法并显式调用;

3. finalize():用于JVM对象收集;

4. wait():使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断;

5. notify():唤醒在该对象上等待的某个线程;

6. notifyAll():唤醒在该对象上等待的所有线程;

深拷贝和浅拷贝区别

浅拷贝的问题就是两个对象并非独立的,浅拷贝复制引用。如果你修改了其中一个 Person 对象的 Name 对象,那么这次修改也会影响奥另外一个 Person 对象。

public class Person {

    private Name name;

    private Address address;

    public Person(Person originalPerson) {

         this.name = originalPerson.name;

         this.address = originalPerson.address;

    }

[…]

}

不同于浅拷贝,深拷贝是一个整个独立的对象拷贝如果我们对整个 Person对象进行深拷贝,我们会对整个对象的结构都进行拷贝。

public class Person {

    private Name name;

    private Address address;

    public Person(Person otherPerson) {

         this.name    =  new Name(otherPerson.name);

         this.address =  new Address(otherPerson.address);

    }

[…]

}

抽象类和接口的区别

语法层次抽象类和接口分别给出了不同的语法定义。

设计层次

抽象层次不同,抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的

跨域不同

抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在”is-a"关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的,仅仅是实现了接口定义的契约而已,"like-a"的关系。

什么是泛型、为什么要使用以及泛型擦除

语法泛型,即“参数化类型”。

创建集合时就指定集合元素的类型,该集合只能保存其指定类型的元素,避免使用强制类型转换。

Java编译器生成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程即类型擦除。泛型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。

类型擦除的主要过程如下:

1)将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。

2)移除所有的类型参数。

Error Exception 的区别 CheckedExceptionRuntimeException 的区别

Throwable是所有异常的根,java.lang.Throwable

Error是错误,java.lang.Error;Error由Java虚拟机生成并抛出,包括动态链接失败,虚拟机错误等。程序对其不做处理

Exception是异常,java.lang.Exception 。Exception一般分为Checked异常和Runtime异常,所有RuntimeException类及其子类的实例被称为Runtime异常,不属于该范畴的异常则被称为CheckedException。

CheckedException,Java认为Checked异常都是可以被处理的异常,所以Java程序必须显示处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误无法编译。方法有两种:

1 当前方法知道如何处理该异常,则用try...catch块来处理该异常。

2 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。

RuntimeException,Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。

System.arraycopy() Arrays.copyOf()两者之间的区别?

两者的区别在于,Arrays.copyOf()不仅仅只是拷贝数组中的元素,在拷贝元素时,会创建一个新的数组对象。而System.arrayCopy只拷贝已经存在数组元素。
如果我们看过Arrays.copyOf()的源码就会知道,该方法的底层还是调用了System.arrayCopyOf()方法。

一些关于Java优化的资料里也推荐使用System.arraycopy来批量处理数组,其本质就是让处理器利用一条指令处理一个数组中的多条记录,只需指定头指针然后就开始循环即可,执行一次指令,指针就后移一个位置。要操作多少个数据就循环多少次即可。

System.arraycopy为 JVM 内部固有方法,它通过手工编写汇编或其他优化方法来进行 Java 数组拷贝,这种方式比起直接在 Java 上进行 for 循环或 clone 是更加高效的。数组越大体现地越明显。

反射的原理

反射机制:所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。

Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。

使用反射机制,导入java.lang.relfect 包,遵循三个步骤:
第一步是获得你想操作的类的 java.lang.Class 对象
第二步是调用诸如 getDeclaredMethods 的方法
第三步使用 反射API 来操作这些信息

JAVA 集合

CollectionCollections的区别

java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

Java中的集合类及关系

List和Set继承自Collection接口。

Set无序不允许元素重复,只能插入一个null值。HashSet和TreeSet是主要的实现类。

List有序且允许元素重复,可插入null。ArrayList/LinkedList/Vector是主要的实现类。

Map也属于集合系统,但和Collection接口没关系。Map是key对value的映射集合,其中key列就是一个集合。key不能重复,但是value可以重复。HashMap、TreeMap(TreeMap有序原因:实现了SortedMap接口)和Hashtable是三个主要的实现类。

SortedSet和SortedMap接口对元素按指定规则排序,SortedMap是对key列进行排序。

fail-fast 与 fail-safe 机制有什么区别

fail-fast(快速失败):快速失败机制在遍历一个集合时,如果集合内容被修改,会抛出ConcurrentModificationException异常。 
fail-safe(安全失败):安全失败机制对集合的任何修改都会在一个复制的集合上进行,因此不会抛出异常。

ArrayList底层实现原理

Arraylist底层基于数组实现,Arraylist底层默认数组初始化大小为10个object数组,添加元素后大于当前数组的长度,则进行扩容,将数组的长度增加原来数组的一半。

elementData = Arrays.copyOf(elementData, newCapacity);

LinkeList底层实现原理

LinkedList 和 ArrayList 一样,都实现了 List 接口,但其内部的数据结构有本质的不同。LinkedList 是基于链表实现的(通过名字也能区分开来),所以它的插入和删除操作比 ArrayList 更加高效。但也是由于其为基于链表的,所以随机访问的效率要比 ArrayList 差。

LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据。

LinkedList还实现了Queue接口,所以他还提供了offer(),peek(), poll()等方法。

ArrayList和LinkeList区别

内存存储区别

数组从栈中分配空间, 对于程序员方便快速,但自由度小。

链表从堆中分配空间, 自由度大但申请管理比较麻烦. 

逻辑结构区别

数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。 

链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项) 

总结

1、存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取; 

2、存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定; 

3、存储空间上,链表由于带有指针域,存储密度不如数组大; 

4、按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n); 

5、按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn); 

6、插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可; 

7、空间分配方面:

  数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;

链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;

ArrayListVector区别

ArrayList和Vector都实现了List接口,都是通过数组实现的。

Vector是线程安全的,而ArrayList是非线程安全的。

ArrayList,Vector主要区别为以下几点:

(1):Vector是线程安全的,源码中有很多的synchronized可以看出,而ArrayList不是。导致Vector效率无法和ArrayList相比;

(2):ArrayList和Vector都采用线性连续存储空间,当存储空间不足的时候,ArrayList默认增加为原来的50%,Vector默认增加为原来的一倍;

(3):Vector可以设置capacityIncrement,而ArrayList不可以,从字面理解就是capacity容量,Increment增加,容量增长的参数。

LinkedListget(i)方法是怎样实现的?

LinkedList,链表里边的get(i)方法里有一个很有意思的设计模式,二分查找法。有先判断index与size>>1的大小,如果小于size的2分之一,则从前面开始查找,如果大于size的2分之一,则从后面开始查找,减少了一半的时间。

HashMap实现原理

底层:

HashMap底层实现还是数组,只是数组的一个元素可能是一个单链表(哈希冲突时才是链表)。

HashMap的底层结构是由数组+链表构成的。

初始容量默认为16,负载因子默认为0.75

初始容量:当构造一个hashmap时,初始容量设为不小于指定容量的2的次方的一个数(new HashMap(5), 指定容量为5,那么实际初始容量为8,2^3=8>5),且最大值不能超过2的30次方。

扩容:当哈希数组中的条目数超出了加载因子与初始容量的乘积时,则要对该哈希数组进行扩容操作(即resize)。

负载因子越小,容易扩容,浪费空间,但查找效率高 链表特别短 减少hash冲突

负载因子越大,不易扩容,对空间的利用更加充分,查找效率低(链表拉长)hash冲突比较多,链表比较长

HashMap在扩容时,新数组的容量将是原来的2倍,由于容量发生变化,原有的每个元素需要重新计算数组索引Index,再存放到新数组中去,这就是所谓的rehash。

1. HashMap只允许一个为null的key。

2. HashMap的扩容:当前table数组的两倍

3. HashMap实际能存储的元素个数: capacity * loadFactor

4. HashMap在扩容的时候,会重新计算hash值,并对hash的位置进行重新排列, 因此,为了效率,尽量给HashMap指定合适的容量,避免多次扩容

当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。

针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

在Java 8 中,如果一个桶中的元素个数超过 TREEIFY_THRESHOLD(默认是 8 ),就使用红黑树来替换链表,从而提高速度。这个替换的方法叫 treeifyBin() 即树形化。

插入:

put过程是先计算hash然后通过hash与table.length取摸计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时(哈希冲突),会在table[index]位置形成一个链表,将新添加的元素放在table[index],原来的元素通过Entry的next进行链接(新值链头,原值后移)--哈希冲突的解决方案;

获取:

先根据key的hash值得到这个元素在数组中的位置,然后通过key的equals()在链表中找到key对应的Entry节点;

HashMap为什么用红黑树?用其它树不行么?

红黑树牺牲了一些查找性能 但其本身并不是完全平衡的二叉树。因此插入删除操作效率略高于AVL树;  AVL树用于自平衡的计算牺牲了插入删除性能,但是因为最多只有一层的高度差,查询效率会高一些。实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。

HashTable实现原理

和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。

HashMapHashTable区别

  1. HashTable的方法前面都有synchronized来同步,是线程安全的;HashMap未经同步,是非线程安全的。
  2. HashTable不允许null值(key和value都不可以) ;HashMap允许nul l值(key和value都可以)。
  3. HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
  4. HashTable使用Enumeration进行遍历;HashMap使用Iterator进行遍历
  5. HashTable中hash数组默认大小是11,增加的方式是old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。
  6. 哈希值的使用不同,HashTable直接使用对象的hashCode; HashMap重新计算hash值,而且用与代替求模。

Iterator

Iterator的API

关于Iterator主要有三个方法:hasNext()、next()、remove()。

hasNext:没有指针下移操作,只是判断是否存在下一个元素

next:指针下移,返回该指针所指向的元素

remove:删除当前指针所指向的元素,一般和next方法一起用,这时候的作用就是删除next方法返回的元素

Java中的Iterator是一种fail-fast的设计。

当Iterator迭代一个容器的时候,如果此时有别的方法在更改Collection(容器)的内容,那么Iterator就会抛出ConcurrentModificationException 。

ConcurrentHashMap实现

要实现线程安全,就需要加锁, ConcurrentMap的做法简单来说, 就是把哈希表分成若干段, 对其中某一段操作时, 只锁住这一段, 其他段可以不受影响。

实现:

a. 整个ConcurrentMap由一个segment数组组成(即segments),数组中每一个segment是一张哈希表, 哈希表中存放的是一张hashentry链表。Segment继承ReentrantLock用来充当锁的角色。

b. 最终存储key,value时是对segment操作, 因此只要对需要插入键值对的segment上锁就可以保证线程安全。

JDK1.8之后的优化:

1. 取消segment字段,直接采用transient volatile HashEntry<K,V> table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率;transient修饰的变量其内容在序列化后无法获得访问;

2. 将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构;

多线程

进程和线程的区别

(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元;

(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源;

(3)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源;

多线程的实现方式?start()后线程立即启动么?

继承Thread类、实现Runnable接口、线程池(Cache、Fixed、Single、Schedule)、实现Callable接口通过FutureTask包装器来创建Thread线程;使用ExecutorService、Callable、Future实现有返回结果的多线程。

不是立刻启动:执行start()之后,线程等待CPU调度,当调度成功时,调用run()才启动线程;

什么是阻塞?阻塞的情况?

阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu时间片,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu时间片转到运行(running)状态。

a. 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

b. 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

c. 其他阻塞:运行(running)的线程执行Thread.sleep(long ms),JVM会把该线程置为阻塞状态。当sleep()状态超时,线程重新转入可运行(runnable)状态。)

如何停止一个线程?

java中有以下3种方法可以终止正在运行的线程:

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止,stop()方法停止线程则是非常暴力的,不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
  3. 使用interrupt方法中断线程。interrupt()方法是在当前线程中打了一个停止标志,并不是真的停止线程。Thread.java类中提供了两种方法:this.interrupted(): 测试当前线程是否已经中断;this.isInterrupted(): 测试线程是否已经中断;

什么是线程安全与非线程安全?如何保证线程安全?

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染;线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据;

线程安全特性:

  • 原子性:相关操作不会被其他线程所打扰,一般通过同步机制实现。
  • 可见性:一个线程修改了某个共享变量,其状态能够立即被其他线程知晓。通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的。
  • 有序性:保证线程内的串行语义,避免指令重排等。

保证线程安全:

需要对非安全的代码进行加锁控制;

使用线程安全的类;

多线程并发情况下,线程共享的变量改为方法级的局部变量。

线程生命周期

新建(new Thread):当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。t = new Thread();

就绪(runnable):线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。t.start();

运行(running):线程获得CPU资源正在执行任务(run()方法);

死亡(dead):当线程执行完毕、发生异常或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值