比较常用的参数:
-q:只显示pid,不显示class名称,jar文件名和传递给main 方法的参数
-m:输出传递给main 方法的参数,在嵌入式jvm上可能是null
-l:输出应用程序main class的完整package名 或者 应用程序的jar文件完整路径名
-v:输出传递给JVM的参数
知识点1:
1 String str1 = "abc";// 没有创建任何对象 2 String str2 = new String("abc");// 创建了2个对象 3 String str3 = new String("abc");// 创建了1个对象 4 System.out.println(str1 == str2);// 输出:false 5 System.out.println(str1 == str3);// 输出:false 6 System.out.println(str2 == str3);// 输出:false
分析:
- String str1 = "abc";执行此句时,首先还是在String Pool中查找有没有字符串常量”abc”,有则直接将s1作为String Pool中”abc”的一个引用,因此没有创建任何对象;
- String str2 = new String("abc");执行此句时,首先在String Pool(字符常量池)中查找有没有字符常量”abc”,没有则在String Pool中创建”abc”的对象;而当执行new String(“abc”)时则在java的堆中创建一个”abc”对象,而str2则是该对象的引用,因此共创建2个对象。
- String str3 = new String("abc");执行此句时,依旧在String Pool中查找有没有字符串常量”abc”,有则不进行再次创建,由于这里用了new关键字,所有便在java堆中又创建了一个”abc”对象(地址与上一句在堆中创建的地址不同),而str3则是这个对象的引用,因此执行此句时只创建了1个对象。
- 我们知道”==”是判断对象的,因此由于str1指向的则是String Pool中的”abc”对象,而str2指向的是java堆中的”abc”对象,所以输出false。 后两句判断同理。
知识点2:
Java编程如何避免内存溢出?
- 尽早释放无用对象的引用(XX = null;)
- 谨慎使用集合数据类型,如数组,树,图,链表等数据结构,这些数据结构对GC来说回收更复杂。
- 避免显式申请数组空间,不得不显式申请时,尽量准确估计其合理值。
- 尽量避免在类的默认构造器中创建、初始化大量的对象,防止在调用其自类的构造器时造成不必要的内存资源浪费
- 尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间
- 尽量做远程方法调用类应用开发时使用瞬间值变量,除非远程调用端需要获取该瞬间值变量的值。
- 尽量在合适的场景下使用对象池技术以提高系统性能
知识点3:
常用数据结构:
Hashtable:
- 继承自:public class Hashtable extends Dictionary implements Map;
- Hashtable 中的方法是同步的,即线程安全的,在多线程并发的环境下,可以直接使用Hashtable;
- Hashtable中,key和value都不允许出现null值;
- 在遍历方式的内部实现上,使用了 Iterator,但由于历史原因,Hashtable还使用了Enumeration的方式;
- 在使用哈希值时,HashTable直接使用对象的hashCode;
- 关于内部实现方式的数组的初始大小和扩容的方式,HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
HashMap:
- 继承自:public class HashMap extends AbstractMap implements Map
- HashMap中的方法在缺省情况下是非同步的,即非线程安全的,在多线程并发的环境下,要使用HashMap的话就要自己增加同步处理;
- 在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而且HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey。因为contains方法容易让人引起误解,因此应该用containsKey()方法来判断是否存在某个键;
- 在遍历方式的内部实现上,使用了 Iterator;
- 在使用哈希值时,HashMap重新计算hash值;
- 关于内部实现方式的数组的初始大小和扩容的方式,HashMap中hash数组的默认大小是16,而且一定是2的指数。
HashSet:
- HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的
- HashSet不能添加重复的元素,当调用add(Object)方法时候,首先会调用Object的hashCode方法判hashCode(因此要注意重写这两个方法)是否已经存在,如不存在则继续调用Object对象的equals方法,如果add方法返回为true则说明元素不存在,直接插入元素成功了;
- set接口是通过equals来判断是否重复的,hashset是一种加快判断效率的一种实现,先通过hashCode判断(hashcode通过运算求出数组下标,通过下标判断是否有对象存在),如果重复,才继续通过equals比较;
- 为了保证HashSet中的对象不会出现重复值,在被存放元素的类中必须要重写hashCode()和equals()这两个方法。
- 可以利用HashSet的这一特性,将数组相同的元素去掉,如先将数组转换为HashSet:set.addAll(Arrays.asList(array)),再调用set.toArray(T[] a)方法转换回数组
ArrayList:
- 基于动态数组的实现;
- 有频繁的随机访问操作,使用ArrayList;
- ArrayList在内存不够时默认是扩展50% + 1个。
LinkedList:
- 基于链表的实现;
- 有频繁的新增和删除操作,使用LinkedList;
Vector:
- 基于动态数组的实现;
- 支持线程的同步,即线程安全,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢;
- Vector在内存不够时默认扩展1倍;
- Vector提供indexOf(obj, start)方法。
知识点4:
字节流与和字符流:
所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节流使用较为广泛;在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。
从字节流转化为字符流 = byte[] 转化为String
从字符流转化为字节流 = String 转化为byte[]
对应关系如下:
Reader InputStream
├BufferedReader BufferedInputStream
│ └LineNumberReader LineNumberReader
├CharArrayReader ByteArrayInputStream
├InputStreamReader (none)
│ └FileReader FileInputStream
├FilterReader FilterInputStream
│ └PushbackReader PushbackInputStream
├PipedReader PipedInputStream
└StringReader StringBufferInputStream
Write OutputStream
├BufferedWriter BufferedOutputStream
├CharArrayWriter ByteArrayOutputStream
├OutputStreamWriter (none)
│ └FileWriter FileOutputStream
├FilterWriter FilterOutputStream
├PrintWriter PrintStream
├PipedWriter PipedOutputStream
└StringWriter (none)
字节流:
- 抽象类 – InputStream和OutputStream;
- 具体实现 — 如FileInputStream和FileOutputStream;
- 输出时直接操作文件,没有使用缓冲区;
- 字节流处理单元为1个字节,操作字节和字节数组;
- 如果是音频文件、图片、歌曲,用字节流比较好;
- 字节流可用于任何类型的对象,包括二进制对象;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符;
字符流:
- 抽象类 Reader和Writer;
- 具体实现 — 如FileReader和FileWriter;
- 在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据,再从缓存写入文件;
- 字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的;
- 如果是关系到中文(文本)或支持多国语言的,用字符流比较好;
- 字符流只能处理字符或者字符串;
- 字符流可以直接处理Unicode字符。
例子:使用字节流时,即使不关闭输出流,也不影响输出:
1 public class FileOutputStreamTest { 2 public static void main(String[] args) throws Exception { 3 File filePath = new File("d:" + File.separator + "test.txt"); 4 OutputStream out = new FileOutputStream(filePath); 5 String str = "Hello World!!!"; 6 byte bytes[] = str.getBytes();// 字符串转byte数组 7 out.write(bytes); 8 // 不关闭输出流 9 // out.close(); 10 } 11 }
例子:使用字符流时,不关闭输出流时,有必要进行强制刷新(flush),否则无法正确写入内容:
1 public class FileWriterTest { 2 public static void main(String[] args) throws Exception { 3 File filePath = new File("d:" + File.separator + "test.txt"); 4 Writer out = new FileWriter(filePath); 5 String str = "Hello World!!!"; 6 out.write(str); 7 // 强制性清空缓冲区中的内容 8 // out.flush(); 9 10 // 不关闭输出流 11 // out.close(); 12 } 13 }
知识点5:
基本数据类型 | 大小 | 最小值 | 最大值 |
boolean | —– | —– | —— |
char | 16-bit | Unicode 0 | Unicode 2^16-1 |
byte | 8-bit | -128 | +127 |
short | 16-bit | -2^15 | +2^15-1 |
int | 32-bit | -2^31 | +2^31-1 |
long | 64-bit | -2^63 | +2^63-1 |
float | 32-bit | IEEE754 | IEEE754 |
double | 64-bit | IEEE754 | IEEE754 |
1 public class BasicTypeTest { 2 public static void main(String[] args) { 3 // byte 4 System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE); 5 System.out.println("包装类:java.lang.Byte"); 6 System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE); 7 System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE); 8 System.out.println(); 9 10 // short 11 System.out.println("基本类型:short 二进制位数:" + Short.SIZE); 12 System.out.println("包装类:java.lang.Short"); 13 System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE); 14 System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE); 15 System.out.println(); 16 17 // int 18 System.out.println("基本类型:int 二进制位数:" + Integer.SIZE); 19 System.out.println("包装类:java.lang.Integer"); 20 System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE); 21 System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE); 22 System.out.println(); 23 24 // long 25 System.out.println("基本类型:long 二进制位数:" + Long.SIZE); 26 System.out.println("包装类:java.lang.Long"); 27 System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE); 28 System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE); 29 System.out.println(); 30 31 // float 32 System.out.println("基本类型:float 二进制位数:" + Float.SIZE); 33 System.out.println("包装类:java.lang.Float"); 34 System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE); 35 System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE); 36 System.out.println(); 37 38 // double 39 System.out.println("基本类型:double 二进制位数:" + Double.SIZE); 40 System.out.println("包装类:java.lang.Double"); 41 System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE); 42 System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE); 43 System.out.println(); 44 45 // char 46 System.out.println("基本类型:char 二进制位数:" + Character.SIZE); 47 System.out.println("包装类:java.lang.Character"); 48 System.out.println("最小值:Character.MIN_VALUE=" + (int) Character.MIN_VALUE);//以数值形式而不是字符形式输出 49 System.out.println("最大值:Character.MAX_VALUE=" + (int) Character.MAX_VALUE);//以数值形式而不是字符形式输出 50 } 51 }
知识点6:
常见的final类:
String、Math;Integer 、Long、Character 等包装类。
需要注意的是:
- 对于final修饰的基本类型和String类型,编译器会认为它是稳定态(Immutable Status),所以在编译时就直接把值编译到字节码中了,避免了在运行期引用(Run-time Reference),以提高代码的执行效率;
因此,单纯的修改静态变量是没用的,还要重新编译,不然改动不会生效。 - 对于非final修饰的类(即非基本类型),编译器认为它是不稳定态(Mutable Status),在编译时建立的则是引用关系(该类型也叫做Soft Final);
因此,如果引入的常量是一个类或实例,即使不重新编译也能使改动生效。
知识点7:
按特定的时间格式获取其毫秒数:
1 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 2 Date startDate = sdf.parse("2014-07-11 00:00:00"); 3 Date endDate = sdf.parse("2014-07-11 23:59:59"); 4 long startTime = startDate.getTime();//单位:毫秒 5 long endTime = endDate.getTime();//单位:毫秒
知识点8:
Java常见的两种解析XML的方式:
DOM-基于文档树结构的解析
例子:
1 private static void readXmlByDOM(String filePath)throws ParserConfigurationException, SAXException, IOException { 2 File file = new File(filePath); 3 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();// step 4 // 1:获得DOM解析器工厂,作用是创建具体的解析器 5 DocumentBuilder db = dbf.newDocumentBuilder();// step 2:获得具体的dom解析器 6 Document document = db.parse(file);// step 3:解析一个xml文档,获得Document对象(根节点) 7 // 根据标签名字访问节点 8 NodeList list = document.getElementsByTagName("test"); 9 System.out.println("list length: " + list.getLength()); 10 // 遍历每一个节点 11 for (int i = 0; i < list.getLength(); ++i) { 12 System.out.println("----------------------"); 13 Element element = (Element) list.item(i); 14 String test = element.getElementsByTagName("name").item(0) 15 .getNodeValue(); 16 System.out.println(test);// 此处输出:null 17 // 节点getNodeValue的值永远为null 18 // 解决方法:加上getFirstChild() 19 Node node1 = element.getElementsByTagName("name").item(0) 20 .getFirstChild(); 21 String content = node1.getNodeValue(); 22 System.out.println("name: " + content); 23 24 Node node2 = element.getElementsByTagName("sex").item(0) 25 .getFirstChild(); 26 content = node2.getNodeValue(); 27 System.out.println("author: " + content); 28 29 Node node3 = element.getElementsByTagName("age").item(0) 30 .getFirstChild(); 31 content = node3.getNodeValue(); 32 System.out.println("year: " + content); 33 } 34 }
SAX-基于事件流的解析
例子:
1 // 需导入dom4j包 2 private static void readXmlBySAX(String filePath) throws DocumentException { 3 SAXReader reader = new SAXReader(); 4 File file = new File(filePath); 5 Document doc = (Document) reader.read(file); 6 Element root = doc.getRootElement(); 7 Iterator iterator = root.elementIterator("test"); 8 while (iterator.hasNext()) { 9 Element element = (Element) iterator.next(); 10 String name = element.elementText("name"); 11 String sex = element.elementText("sex"); 12 Integer age = Integer.parseInt(element.elementText("age")); 13 System.out.println("----------------------"); 14 System.out.println("name: " + name); 15 System.out.println("sex: " + sex); 16 System.out.println("age: " + age); 17 } 18 }
知识点9:
Java的算术右移">>"和逻辑右移">>>":
- 算术右移">>"
使用最高位填充移位后左侧的空位,不改变原数的符号;
右移的结果为:每移一位,第一个操作数被2除一次,移动的次数由第二个操作数确定。
- 逻辑右移">>>"
或叫无符号右移,只对位进行操作,没有算术含义,用0填充左侧的空位;
无法保证不改变原数的符号。
例子:
1 System.out.println("=============算术右移 >> ==========="); 2 int i = 0xC0000000; 3 System.out.println("移位前:i= " + i + " = " + Integer.toBinaryString(i) + "(B)"); 4 i = i >> 28; 5 System.out.println("移位后:i= " + i + " = " + Integer.toBinaryString(i) + "(B)"); 6 7 System.out.println("---------------------------------"); 8 int j = 0x0C000000; 9 System.out.println("移位前:j= " + j + " = " + Integer.toBinaryString(j) + "(B)"); 10 j = j >> 24; 11 System.out.println("移位后:j= " + j + " = " + Integer.toBinaryString(j) + "(B)"); 12 13 System.out.println("==============逻辑右移 >>> ============="); 14 int m = 0xC0000000; 15 System.out.println("移位前:m= " + m + " = " + Integer.toBinaryString(m) + "(B)"); 16 m = m >>> 28; 17 System.out.println("移位后:m= " + m + " = " + Integer.toBinaryString(m) + "(B)"); 18 19 System.out.println("---------------------------------"); 20 int n = 0x0C000000; 21 System.out.println("移位前:n= " + n + " = " + Integer.toBinaryString(n) + "(B)"); 22 n = n >> 24; 23 System.out.println("移位后:n= " + n + " = " + Integer.toBinaryString(n) + "(B)"); 24 25 System.out.println("\n"); 26 System.out.println("==============移位符号的取模==============="); 27 int a = 0xCC000000; 28 System.out.println("移位前:a= " + a + " = " + Integer.toBinaryString(a) + "(B)"); 29 int a1 = a >> 32; 30 int a2 = a >>> 32; 31 System.out.println("算术右移32位:a1=" + a1 + " = " + Integer.toBinaryString(a1) + "(B)"); 32 System.out.println("逻辑右移32位:a2=" + a2 + " = " + Integer.toBinaryString(a2) + "(B)");
知识点10:
关于Java的eqauls与= =:
= = 是面向过程的操作符;equals是面向对象的操作符
= =不属于任何类,equals则是任何类(在Java中)都实现(或继承)了的一个方法;
因此,
如果要比较两个基本类型的值是否相等,请用= =;//注:基本类型无equals方法可调用
如果要比较两个对象的地址是不是一样,请用= =;
如果要比较两个对象(非基本类型)的值是否相等,请用equals;
知识点11:
特殊字符及其转义:
1.八进制转义序列:
\ + 1到3位5数字;范围'\000'~'\377'
\0:空字符
2.Unicode转义字符:
\u + 四个十六进制数字;0~65535
\u0000:空字符
3.特殊字符:3个
\":双引号
\':单引号
\\:反斜线
4.控制字符:5个
\' 单引号字符
\\ 反斜杠字符
\r 回车
\n 换行
\f 走纸换页
\t 横向跳格
\b 退格
点的转义:. ==> u002E
美元符号的转义:$ ==> u0024
乘方符号的转义:^ ==> u005E
左大括号的转义:{ ==> u007B
左方括号的转义:[ ==> u005B
左圆括号的转义:( ==> u0028
竖线的转义:| ==> u007C
右圆括号的转义:) ==> u0029
星号的转义:* ==> u002A
加号的转义:+ ==> u002B
问号的转义:? ==> u003F
反斜杠的转义: ==> u005C
知识点12:
对象锁:
- 所有对象都自动含有单一的锁;
- JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候,计数变为1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增;
- 只有首先获得锁的任务(线程)才能继续获取该对象上的多个锁;
- 一个线程可以多次对同一个对象上锁。对于每一个对象,都有一个独自的加锁计数器交由JVM维护,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了,此时别的任务就可以使用此资源。
类锁:
- 对于同步静态方法/静态变量互斥体,由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只由一份。
所以,一旦一个静态的方法被申明为synchronized,此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。 - 一旦一个静态变量被作为synchronized block的互斥体。进入此同步区域时,都要先获得此静态变量的对象锁;
- 其实系统中并不存在什么类锁。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
因此,当一个同步静态方法被调用时,系统获取的其实就是代表该类的类对象的对象锁。
若要同时获取两种锁,同时获取类锁和对象锁是允许的,并不会产生任何问题,
但使用类锁时一定要注意,一旦产生类锁的嵌套获取的话,就会产生死锁,因为每个class在内存中都只能生成一个Class实例对象。
知识点13:
JDK常用的系统全局变量:
1 public class Test { 2 public static void main(String[] args) { 3 System.out.println("Java运行时环境版本:"+System.getProperty("java.version")); 4 5 System.out.println("Java 运行时环境供应商:"+System.getProperty("java.vendor")); 6 7 System.out.println("Java 供应商的URL:"+System.getProperty("java.vendor.url")); 8 9 System.out.println("Java安装目录:"+System.getProperty("java.home")); 10 11 System.out.println("Java 虚拟机规范本:"+System.getProperty("java.vm.specification.version")); 12 13 System.out.println("Java 类格式版本号:"+System.getProperty("java.class.version")); 14 15 System.out.println("Java类路径:"+System.getProperty("java.class.path")); 16 17 System.out.println("加载库时搜索的路径列表:"+System.getProperty("java.library.path")); 18 19 System.out.println("默认的临时文件路径:"+System.getProperty("java.io.tmpdir")); 20 21 System.out.println("要使用的 JIT 编译器的名称:"+System.getProperty("java.compiler")); 22 23 System.out.println("一个或多个扩展目录的路径:"+System.getProperty("java.ext.dirs")); 24 25 System.out.println("操作系统的名称:"+System.getProperty("os.name")); 26 27 System.out.println("操作系统的架构:"+System.getProperty("os.arch")); 28 29 System.out.println("操作系统的版本:"+System.getProperty("os.version")); 30 31 System.out.println("文件分隔符(在 UNIX 系统“/”):"+System.getProperty("file.separator")); 32 33 System.out.println("路径分隔符(在 UNIX 系统中是“:”):"+System.getProperty("path.separator")); 34 35 System.out.println("行分隔符(在 UNIX 系统中是“/n”):"+System.getProperty("line.separator")); 36 37 System.out.println("用户的账户名称:"+System.getProperty("user.name")); 38 39 System.out.println("用户的主目录:"+System.getProperty("user.home")); 40 41 System.out.println("用户的当前工作目录:"+System.getProperty("user.dir")); 42 } 43 }
知识点14:
1 重载和覆盖的方法名称都相同,但重载要求参数列表不同,而覆盖要求参数列表完全相同。
2 重载对于方法前面的修饰符没有限制,而覆盖则对这些修饰符的使用有限制
3 重载时编译器在编译期间就可以确定调用那一个方法,而覆盖则有可能在运行期间才能确定。
知识点15:
![](https://i-blog.csdnimg.cn/blog_migrate/38de19fdfc9db785a1e80c670c6a05aa.png)
知识点16:
关于java.util.Arrays.asList(T... a)方法:
1 public class Test { 2 public static void main(String[] args) { 3 int[] data = {1, 2, 3, 4, 5}; 4 Integer[] data2 = {1, 2, 3, 4, 5}; 5 List list = Arrays.asList(data); 6 System.out.println("元素类型:" + list.get(0).getClass()); 7 System.out.println("前后是否相等: " + data.equals(list.get(0))); 8 } 9 }
知识点17:
知识点18:
1 @Override 2 public String toString() { 3 return ToStringBuilder.reflectionToString(this); 4 } 5 6 @Override 7 public int hashCode() { 8 return new HashCodeBuilder().append("test").toHashCode(); 9 } 10 11 @SuppressWarnings("unchecked") 12 public static <T extends Serializable> T clone(T obj) throws ClassNotFoundException, IOException { 13 return (T) SerializableUtils.deepCopy(obj); 14 }
知识点19:
关于不可变对象:
知识点20:
知识点21:
知识点22:
知识点23:
关于通道与流:知识点24:
方法调用过程:
- 编译器查看对象的声明类型和方法名(对象变量的声明类型),通过声明类型找到方法列表;
- 编译器查看调用方法时提供的参数类型;
- 如果方法是private、static、final或者构造方法,编译器就可以确定调用那个方法。这是静态绑定;
- 如果不是上述情况,就要使用运行时(动态)绑定。在程序运行时,采用动态绑定意味着:虚拟机将调用对象实际类型所限定的方法。
知识点25:
关于死锁:
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁产生的条件:
- 互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。
- 不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。
- 占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。
- 循环等待条件。存在一个进程等待序列{P1,P2,...,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,......,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。
上面我们提到的这四个条件在死锁时会同时发生。也就是说,只要有一个必要条件不满足,则死锁就可以排除。
知识点26: