Java基础—IO流(三)

IO流(三) File类 一、概述 File类用于将文件或文件夹封装成对象,方便对文件和文件夹的属性信息进行操作。该类可以作为参数传递给IO流的构造函数,弥补流对象在操作文件和文件夹上的缺陷。 二、File类的使用 1.构造方法1)File(String FileName)示例:File f1 = new File(“C:\\abc\\a.txt”); 2)File(Strng, parent, String FileName)示例:File f2 = new File(“C:\\abc”, “b.txt”);该构造方法的好处在于对文件的操作更加灵活,出现文件目录固定,文件名需要改变的时候,这个构造方法更好 3)File(File parent, String FileName)示例:File d = new File(“C:\\abc”); File f3 = new File(d, “c.txt”); 示例代码: 程序输出结果: 打印封装文件对象时的绝对路径或相对路径。 2.成员方法1)创建操作-boolean createNewFile():文件名不存在的情况下创建新文件-boolean mkdir():创建一级目录-boolean mkdirs():创建多级目录 2)删除操作-boolean delete():删除指定文件-void deleteOnExit():虚拟机退出的时候删除指定文件 3)判断操作-boolean canExecute():判断文件对象是否可执行-boolean canRead():判断文件对象是否可读-boolean canWrite():判断文件对象是否可写-boolean exists():判断文件对象是否存在-boolean isDirectory():判断文件对象是否是文件夹,判断之前,必须先判断该文件对象是否存在-boolean isFile():判断文件对象是否是文件,判断之前,必须先判断该文件对象是否存在-boolean isHidden():判断文件对象是否是隐藏文件-boolean isAbsolute():判断文件对象路径是否是绝对路径-int compareTo(File pathname):比较两个文件对象,以自然顺序排序 4)获取操作-String…

Java基础—IO流(二)

IO流(二) 字节流 一、OutputStream类 1.概述OutputStream类可以在硬盘上创建一个文件,并写入或添加数据。该类的子类还能实现写入过程中的不同功能。 2.FileOutputStream类FileOutputStream类用于在硬盘上创建文件,并以字节的形式写入数据。其使用方式和FileWriter类相似,只是以字节的形式操作数据。下面的代码在指定目录创建一个文件并写入自定义数据。 示例代码: 该程序在指定目录创建fos.txt并写入abcdefg 注意:字节输出流在直接使用的时候不需要调用flush()方法,与字符流不用。由于字符流底层操作的也是字节,同时用的是字节流的缓冲区;该缓冲区中有个数组,用于临时存储数据。要将数组中的数据写入目的地文件,字符流就需要调用flush()方法进行刷新。而字节输出流是对最小单位字节直接进行操作,没有使用具体缓冲区,所以不需要刷新,直接往目的地文件写入数据。 二、InputStream类 1.概述InputStream类以字节形式读取硬盘上的文件数据。该类的子类还能实现读取过程中的不同功能。 2.FileInputStream类FileInputStream类用于以字节形式读取文件数据。其特有的方法使创建字节数组有了明确的大小。该类的使用方式和FileReader相似。下面的代码读取文件中的数据。 示例代码: 程序输出结果: 注意:如果使用字节输入流读取的文件较大,建议使用1024字节整数倍方法读入数据,而不要使用创建available()方法返回值大小的数组;后者可能造成内存溢出 三、字节流练习 1.拷贝图片到指定目录 示例代码: 四、字节流缓冲区 1.缓冲区对应的类字节流缓冲区对应BufferedOuputStream类和BufferedInputStream类。 2.缓冲区应用1)用缓冲区拷贝一个Mp3文件 示例代码: 程序输出结果:136毫秒 2)自定义缓冲区 假设内存中字节数组的大小定义为1024字节;那么字节流缓冲区在工作时,首先由FileInputStream从硬盘抓取1024字节的数据存入字节数组,然后由BufferedInputStream的read()方法依次一个字节一个字节读取。缓冲区中有两个控制读取过程的变量,分别是数组的索引指针,和一个计数器。下标用于控制不断读取下一个字节,计数器用于控制下一次从硬盘抓数据存入缓冲区数组的时间。read()方法每读取一个字节,指针右移一位,计数器自减1;当计数器减至0的时候,意味着数组中已经没有字节可读,这时再由FileInputStream从硬盘抓取1024个字节存入数组,指针归零,计数器回到1024,再次进行以上步骤的循环,直至硬盘数据全部被抓取。 要自定义缓冲区,需要定义一个字节数组,两个变量(指针和计数器)。 示例代码: 问题:上面的代码运行结果只拷贝了8K到目的文件。 原因:是因为媒体文件在硬盘上的数据以二进制形式存在;read()方法在读取第一个字节的时候,有可能会读到:11111111;这样8个1的情况;而8个1的的二进制就是十进制的-1;程序中while循环的跳进等于-1时,循环停止;因此只复制了8K大小。 理解BufferedInputStream的read()方法:细看BufferedInputStream类的read()方法,其返回的是int类型。而方法中读到的字节都是byte类型;这样做的原因,就是为了解决读取字节读到8个1的情况。read()方法在返回byte类型字节数据的时候,将byte类型提升为int类型,存储位数由1个8位,变为4个8位。为了确保返回的数据与原数据相同而不产生-1的情况,read()方法在类型提升之后,补了3个8位的0在原byte数据前面。过程如下图: 在自定义缓冲区中,虽然方法返回了int类型,进行了数据类型提升,但是没有进行补0的操作,意味着当读到8个1组成的byte数据时,返回了一个由32个1组成的int类型数据,结果还是-1。那么,如果要完成相同功能,只需取32个1的最后8位即可。取最后8位,将原数据与上255。 将每个return ch;语句改为return ch & 255;即可。 五、键盘录入 1.System标准输出输入System类中对应的成员out和in分别是:System.out-标准输出流System.in-标准输入流 System.in用于读取键盘录入。 2.接收键盘录入从键盘接收输入,并打印在控制台。 示例代码: 程序输出结果: 3.练习接收键盘录入,当回车时打印整行内容;当输入over回车时,结束输入。 示例代码: 六、转换流 1.InputStreamReader类InputStreamReader类用于将字节流转换为字符流。该类可以将读取到的字节数据转换为字符数据。其使用的编码表可以由开发者指定,也可以使用系统默认。利用转换流将字节流转换为字符流,就意味着该字节流可以使用字符流的缓冲区技术,调用其readLine()方法,使键盘录入的读取过程更加高效便捷。 利用转换流修改键盘录入并打印的代码: 将字节流转换为字符流,相当于在字节流上套了两根管子;一根使字节流变成字符流;另一根使字节流可以使用字符流的缓冲技术。 2.OutputStreamWriter类OutputStreamWriter类用于将字符流转换为字节流。该类的编码表同样可以指定或使用系统默认。 用OutputStreamWriter类修改上面的代码: 可以将字节流转字符流的三个步骤简化为一行代码: 3.转换流的作用转换流可以指定读写时使用的字符编码集;如不指定,将使用系统默认的编码集,本机默认使用GBK。下面的代码演示了用UTF-8写入,用GBK读取会发生乱码的情况。 示例代码: 程序输出结果: 为了正常显示,用InputStreamReader指定读取时编码集为UTF-8即可 程序输出结果:…

Java基础—IO流(一)

IO流概述 一、概述 Java对数据的处理都是通过流的方式,称为IO(Input-Outpu)流。IO流用于处理设备上的数据传输,如硬盘上储存的数据,内存中驻留的数据。 二、IO流的区分 1.按流向分按流向分为:输入流和输出流。 2.按操作数据分流按操作数据分为两种:字节流与字符流。 小扩展:字符流的出现是为了方便处理文本字符。英语有英文字符集ASCII码;中文字符集由原来的GB2312,扩展为现在的GBK。之后,国际标准化组织计划将世界上所有国家的文字都进行编排,形成了国际标准码表,UNICODE;优化之后,形成了UTF-8字符编码表。那么,如果一个电脑用GBK编码存储文本,另一个电脑用UTF-8编码读取,就会出现乱码。Java就这个情况,在字节流的基础上,增加了字符流。字符流内部融合了编码表,并可以指定查询的编码表,处理文本字符更加方便,避免乱码。 三、IO流常用基类 1.字符流的抽象基类 Reader Writer 2.字节流的抽象基类 InputStream OutputStream IO流用于操作数据,数据的最常见体现形式是文件。那么以操作文件为主,从字符流开始,来演示IO流程序。 IO流(一) 字符流 一、Writer类 1.概述Writer类可以在硬盘上创建一个文件,并写入或添加数据。该类的子类还能实现写入过程中的不同功能。 2.FileWriter类FileWriter类专门用于操作文件。该对象中只有构造方法,并且没有空参数的构造方法,因为初始化的时候必须要有文件对象,才能进行写入操作。在示例代码中,将对每个环节做详细注释。 1)文件创建并写入 第一步:创建FileWriter对象,传入要操作的文件名 注意:-创建文件会报出异常,此处先抛出,后期会有专门处理异常的代码;-如果指定文件夹下有同名文件,该文件将被覆盖,所以要小心创建操作 第二步:写入自定义数据 注意:-writer()方法不是将文本内容直接写入文件,而是先写入流的缓冲区;-flush()方法将缓冲区中的内容写入到文件;每次调用完writer()方法,都要调用flush() 第三步:关闭流资源 注意:-由于每个系统创建文件写入内容的方式不同,Java需要调用系统底层的功能来实现文件的创建和写入;使用完系统资源的之后,一定要调用close()方法,关闭该资源;-close()方法与flush()方法的区别在于,close()方法刷新缓冲区之后,关闭了该输出流资源,之后不能再写入数据;而flush()之后,可以继续写入数据,流资源依然存在; 第四步:针对处理IO异常 注意:-用try catch捕捉并处理异常;-关闭资源的代码一定要放在finally中;-close()方法同样会抛出IO异常,同样需要用try catch捕捉并处理;-如果创建文件的路径有误(例如电脑上没有k盘,创建时却写fw = new FileWriter(“k:\”demo.txt”);),那么会报出FileNotFoundException文件无法找到异常,同时会报出NullPointerException空指针异常,因为路径找不到,fw对象没有建立,而finally中还要调用close()方法,会发生空指针异常;所以需要在finally中建立判断;-无论读、写数据还是关闭资源操作,都有可能发生IO异常 2)文件的续写在原有数据的基础上,续写自定义数据。 第一步:修改构造方法 第二步:写入自定义数据 二、Reader类 1.概述Reader类用于高效读取字符流。该类的子类可以实现读取过程中的不同功能。 2.FileReader类FileReader类是用于读取字符文件的便捷类。该类包含默认的字符编码和默认的字节缓冲区大小。 1)read()方法读取文件 第一步:创建FileReader对象 注意:创建FileReader对象时关联的读取文件必须已经存在,不然会发生FileNotFoundException 第二步:读入单个字符并关闭资源 注意:read()方法读到文件末尾没有字符的时候,会返回-1;可以利用这个机制,循环读取所有数据 第三步:一次性读取全部数据 2)read(ch[] ch)方法读取文件 第一步:定义一个字符数组用于储存读到的字符 第二步:读取字符并存入字符数组 注意:read(ch[] ch)方法返回的是读取了多少个字符;独到文件末尾,返回-1;可以利用这一机制,循环读取全部字符 第三步:一次性读取全部数据 注意:-缓冲区字符数组通常定义1024的整数倍长度;-read(ch[] ch)方法的效率优于read()方法;由于read()方法读一个字符,写一个字符,效率低下;而raead(ch[]…

Java基础—集合框架(四)

集合框架(四) 一、集合框架工具类 1.Collections工具类1)定义Collections工具类用于对集合进行各种操作。 2)常用方法1> 排序操作 格式:static void sort(List list):自然排序static void sort(List list, Comparator c):自定义排序 示例代码: 程序输出结果: [z, qq, zz, aaa, abcd, kkkkk] 2> 获取最大元素 格式:static max(Collection coll): 根据元素的自然顺序,返回指定collection中的最大元素static T max(Collection coll, Comparator comp):获取指定排序下的最大元素 示例代码(以上述代码为例): Collections.max(list);程序输出结果:zz Collections.max(list, new StrLenComp());程序输出结果:kkkkk 3> 二分搜索 格式:static int binarySearch(List<? extends Comparable<? super T>> list, T key):搜索指定元素static int binarySearch(List<? extends T> list,…

Java基础—集合框架(三)

集合框架(三) 一、Map集合 1.概述Map集合用于存储键值对,且它保证键的唯一性,并且每个键最多只能映射一个值。Map集合是Set集合的底层实现,其和Collection在集合框架中并列存在。 2.Map集合共性方法1)添加操作 -V put(K key, V value):添加指定的键值对-void putAll(Map<? extends K, ? extends V>, m):添加指定Map集合 注意:put()会返回该键对应的被覆盖的值第一次存储put(“01”, “zhangsan”);返回null;”01″键对应的被覆盖的值为null;第二次存储put(“01”, “wangwu”);返回”zhangsan”;”01″键对应的被覆盖的值为”zhangsan”; 2)删除操作 -void clear():清楚所有映射关系-V remove(Object key):根据键删除映射值 3)判断操作 -boolean containsKey(Object key):判断是否包含指定键的映射关系-boolean containsValue(Object value):判断是否包含指定值的映射关系-boolean isEmpty():判断是否为空 4)获取操作 -V get(Object Key):通过键获得值-int size():获取Map长度-Collection values():返回所有的映射的值-Set entrySet():返回包含的映射关系-Set keySet():返回包含的键 3.Map集合子类 Map HashTable HashMap TreeMap 1)HashTableHashTable底层是哈希表数据结构。此类实现一个哈希表,该哈希表将键映射到相应的值上。任何非null的对象都可以做键或值。为了成功在哈希表中存储和获取对象,用作键的对象必须实现hashCode()方法和equals()方法。HashTable类是线程同步的。 2)HashMapHashMap底层是哈希表数据结构。此类与HashTable大致相同,但允许使用null的对象作为键或值。HashMap不保证映射的顺序,特别是它不保证该顺序恒久不变。HashMap类是线程不同步的,效率高于HashTable。 3)TreeMapTreeMap底层是二叉树数据结构。此类可以实现为Map集合中的键排序。TreeMap线程不同步。 4.Map集合方法应用演示Map集合基本方法。 示例代码: 程序输出结果: 5.Map集合元素取出方式1)keySet()方法将Map中所有的key存入Set集合,再通过Set集合的迭代器将所有key取出,最后通过get()方法获取每个key的对应值。 示例代码: 程序输出结果: 2)entrySet()方法将Map集合中的映射关系存入到Set集合中。该关系的数据类型是Map.Entry。 示例代码:…

Java基础—集合框架(二)

集合框架(二) 一、TreeSet 1.TreeSet集合特点可以对Set集合中的元素进行自然排序。往TreeSet里面的对象,必须具有比较性,才可以实现排序。 2.TreeSet集合排序实现方式1)元素实现Comparable接口要获得元素的比较性,需要进行存储的对象可以实现Comparable接口,并复写compareTo()方法,自定义比较方式;这是通过让元素获得比较性的方法,完成TreeSet排序功能。 2)集合初始化Comparator当元素自身不具备比较性时,或者具备的比较性不是所需要的,这时可以让集合调用具备比较功能的构造方法,将比较器对象作为参数传给TreeSet集合的构造方法;这是让集合自身具备比较功能,完成TreeSet排序。 注意:-TreeSet集合保证元素唯一新的依据是comparteTo()方法的返回值为0;-当元素和集合都具备比较功能时,以集合的比较器为主 3.TreeSet集合运用1)继承Comparable接口的方法实现元素的可比性使用TreeSet集合存储自定义对象。 示例代码: 程序输出结果: 注意:排序时,当主要条件相同时,一定要判断次要条件 2)调用TreeSet具备比较器构造方法 示例代码: 程序输出结果: 4.二叉树数据结构1)定义 二叉树数据结构的原则是将小的元素依次放在左边,大的元素依次放在右边。在取数据时,二叉树从最小的元素开始,依次往上取到最大值。这样的数据结构,可以减少比较次数,提高运行效率。 2)二叉树运用使用二叉树原理实现数据存储和取出顺序一致。 示例代码: 要使存储和取出的顺序一致,只需在复写的compareTo()方法中,return 1;即可;存储与取出成倒序,只需return -1;即可 5.TreeSet练习1)按照字符串的长度排序 示例代码: 程序输出结果: 二、泛型 1.概述 由一个小例子引出泛型: 如果没有泛型的支持,后添加的整型数据4,在运行时会出现运行时期类型转换异常,int类型无法被转换为String类型,但是这个异常在编译时期,没有任何提示。为了避免这样的情况,需要程序在编译时期就给出异常,以便开发者解决问题,Java引入了泛型机制。 JDK5.0版本之后的新特性,用于解决集合存储的安全问题。 泛型格式(以储存String类型数据为例): ArrayList<String> arrayList = new ArrayList(); 声明一个ArrayList容器,存储String类型的元素。这样,上述程序在编译时期就会报出异常。容器声明加上了泛型,那么迭代器的声明也应加上泛型: Iterator<String> it = arrayList.iterator(); 2.泛型的优点1)将运行时期出现的ClassCastException转移到编译时期,方便开发者解决问题,更安全;2)避免了迭代时期类型强制转换的麻烦;3)泛型类的出现优化了程序设计 3.泛型的应用当在声明容器的时候指定了泛型,后期实现Comparator接口和Comparable接口的时候,可以指定Comparator接口的泛型;这样做,避免了比较时的类型转换。 Comparator接口泛型示例代码: Comparable接口泛型示例代码: 注意:复写equals()方法的时候,传的参数必须是Object obj;因为Object类没有泛型 4.泛型类1)定义泛型类是在一个类要操作对象,而对象类型又不明确时出现的。 2)泛型类格式声明泛型类,只需要在类名后加上<T>即可:class GenericClass<T> 3)泛型类应用当一个类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展。例如,当一个工具类中要操作不同的对象Worker和Student时,早期通过接收Object对象,然后通过类型转换类完成操作;现在只需定义泛型,无需类型转换。 早期实现: 如果现在要操作的是Worker对象,而在setObject()方法中误传入了Student对象,编译不会报错,但是运行时会出现类型转换异常。 泛型实现: 如果现在要操作的是Worker对象,而在setObject()方法中误传入了Student对象,编译无法通过;并且泛型实现无须进行类型转换,既简化了代码,又提升了安全性。5.泛型方法1)定义泛型类在使用的过程中有其局限性。例如一个泛型类被定义了只能操作String类型的对象,那么该类中需要接收参数的方法,都只能操作String对象;此时如果给该类中的方法传递了非String类型数据,编译就会出错。为了程序更加灵活,现在想实现一个类中不同的方法操作不同类型的对象,而要操作的对象不能确定,那么就可以使用泛型方法。 2)泛型方法格式要声明泛型方法,只需在方法修饰符后面,返回值的前面加上<T>即可:private <T>…

Java基础—集合框架(一)

集合框架(一) 一、概述 1.集合出现的原因Java中,数据被封装成对象;对象完成了对数据的存储。而集合的出现,完成了对对象的存储,方便对对象进行操作。 2.集合与数组的区别1)数组虽然可以存储对象,但是长度固定,不利于后期扩展;只能存储同一类型对象;2)集合的长度是可变的;并且能存储不同类型对象 3.集合的特点1)长度可变;2)可以存储不同类型的对象;3)集合只用于存储对象,不能存储基本数据类型 二、Collection 1.定义Collection是集合框架层次结构中的根接口。Collection提供了具体的自接口(如Set和List)实现。这个根接口用来传递collection,并提供共性方法,操作这些collection。 2.共性方法1)添加操作 -boolean add(E e):添加指定元素到集合中-boolean addAll(Collection<? extends E> c):添加指定集合中的所有元素到当前集合中 2)判断操作 -boolean contains(Object o):判断当前集合中是否包含指定元素-booean isEmpty():判断当前集合是否为空 3)删除操作 -void clear():清空当前集合-boolean remove(Object o):移除指定元素-boolean removeAll(Collection<?> c):指定一个集合,将该集合中的元素从当前集合中移除 4)获取操作 -int size():获取当前集合长度-booean retianAll(Collection<?> c):取当前集合与指定集合的交集-Iterator iterator():迭代器,用于取出集合中元素 3.共性方法演示演示Collection中的共性方法。 示例代码: 注意:-add()方法的参数类型是Object,以便于接受任意类型对象;-集合中存放的不是对象实体,存储的是对象内存地址 三、迭代器 1.定义迭代器是对Iterator接口的实现,用于取出集合中的对象,以便于对对象进行操作。 容器的取出操作,不足以用一个方法描述。每个容器的取出操作过程,都有所不同。因此,容器将取出操作封装成一个对象,对象中提供了多个功能以实现容器对象的取出操作。 每个容器类的内部,都定义了做取出操作的内部类(便于直接访问容器内部的元素),用于取出各容器不同数据结构中的对象。这个取出操作被称为迭代器。无论是什么容器,其迭代器的操作都会有判断和取出的操作,将这些操作提取之后形成了Iterator接口。每个容器通过调用iterator()方法,获得自身的迭代器。 2.迭代器的使用iterator()方法返回的是一个Iterator接口的子类对象1)获取iterator迭代器:Iterator it = arraylist.iterator();2)调用next()方法获取第一个元素并打印:System.out.println(it.next());3)调用hasNext()方法,循环取出所有元素 四、List 1.List集合的特点List集合中的元素是有序的,并可以重复。该集合体系有索引。 2.List集合特有方法凡是操作索引的方法都是该体系特有的方法。1)添加操作 -void add(int index, E element):添加指定元素-boolean addAll(int index, Collection<?…

Java基础—String类

一、String类 1.概述1)字符串解读String s1 = “abc”; s1是一个类类型变量,”abc”是一个对象。只要在””里的,都是对象。字符串最大的特点在于,一旦初始化,就不可以改变。字符串,既是对象,又是常量。 示例:String s1 = “abc”;s1 = “kk”;System.out.println(s1); 输出结果为:kk变化的不是”abc”这个对象,而是s1这个引用;重新赋值后,s1指向了”kk”这个对象。 2)字符串创建方式的区别 高能:String s1 = new “abc”;String s2 = new String(“abc”);s1和s2的区别在于,s1有一个对象,s2有两个对象。s2中,new是一个对象,”abc”是一个对象。 小扩展字符串在内存中有常量池,该常量池是一个数组,用于存储’a’,’b’,’c’这样的字符;字符串由字符组成,如果常量池中有’a’,’b’,’c’,’d’,’e’,’f’这些字符,下次创建String s3 = “abf”;的时候,虚拟机会到常量池中找到这三个字符,拼接起来。 3)String类的equals()方法String类复写了Object类中的equals()方法,该方法用于判断字符串是否相同。 示例:String s1 = new “abc”;String s2 = new String(“abc”);System.out.println(s1.equals(s2)); 输出:true 4)String的“==”操作符比较 示例代码: 输出结果为: 字符串储存在常量池当中,当s1创建的时候,”abc”对象已经在内存中存在了;那么,当s3创建的时候,如果常量池中已经有”abc”字符串的字符,就不会再创建对象,而直接让s3指向”abc”这个对象。2.String类中的方法String类是用于描述字符串这个事物。其常见的方法有:1)获取操作1> 获取字符串长度:int length()2> 获取指定位置上的字符:char charAt(int index)3> 获取指定字符的位置有如下方法: a.int indexOf(int ch):获取指定字符第一次出现的位置b.int indexOf(int ch, int fromIndex):获取指定字符从fromIndex位置开始,第一次出现的位置c.int…

Java基础—多线程(二)

多线程(二) 一、线程间通信 1.定义线程间通信就是多个线程操作同一资源,但是操作的动作不同。 2.等待唤醒机制等待唤醒机制,是由wait(),notify()或notifyAll()等方法组成。对于有些资源的操作,需要一个线程完成一步,进入等待状态,将CPU执行权交由另一个线程,让它完成下一步的操作,如此交替进行。这个过程中,一个线程需要在完成一步操作后,先通知(notify())另一个线程运行,再等待(wait()),进入冻结状态,以此类推。等待中的线程,都储存在系统线程池中,等待这被notify()唤醒。 以下代码,通过等待唤醒机制,实现了生产一个披萨,消费一个披萨: 程序运行部分结果如下: 3.Object类中的wait等方法wait()等多线程同步等待唤醒机制中的方法,被定义在Object类中是因为:首先,在等待唤醒机制中,无论是等待操作,还是唤醒操作,都必须标识出等待的这个线程和被唤醒的这个线程锁持有的锁;表现为代码是:锁.wait();锁.notify();而这个锁,由synchronized关键字格式可知,可以是任意对象;那么,可以被任意对象调用的方法,一定是定义在了Object类当中。wait(),notify(),notifyAll()这些方法都被定义在了Object类中,因为这些方法是要使用在多线程同步的等待唤醒机制当中,必须具备能被任意对象调用的特性。所以,这些方法要被定义在Object类中。 4、生产者消费者模型在实际生产时,会有多个线程负责生产,多个线程负责消费;那么在上述代码中启动新线程,来模拟多线程生产消费的情况。 示例代码: 用这样的方式,运行会出现如下结果: 生产了两个披萨,但只消费了一个。现在0,1线程负责生产,2,3线程负责消费,原因推断:1)当0线程生产完一个披萨,进入冻结;2)1线程判断有披萨,进入冻结;3)2线程消费一个披萨,唤醒0线程,进入冻结;4)3线程判断没披萨,进入冻结;5)现在出于运行状态的只有0线程,0线程生产一个披萨,唤醒1线程(1线程是线程池中第一个线程),进入冻结;6)1线程又生产了一个披萨 这导致了生产两个,只消费一个的问题。这个问题的发生是因为,第5步0线程唤醒1线程的时候,由于1线程的等待代码在if语句中,1线程醒了之后,不需要再判断flag的值所导致。如果1线程被唤醒,还要继续判断flag的值,就不会产生这个情况。因此,要将if判断,改为while循环,让线程被唤醒之后,再次判断flag的值。 示例代码: 每次被唤醒,都要判断flag的值。代码运行结果如下: 程序出现了无响应,因为使用while循环,可能会出现所有线程全部进入冻结状态的情况。要解决这个问题,必须用到另一个方法notifyAll();唤醒所有线程。由于用了while循环,所有线程被唤醒之后第一件事是判断flag的值,所以不会再出现多生产或多消费问题。至此,程序运行正常。 示例代码: 程序运行部分结果: 二、jdk5新特性 1.概述jdk5开始,提供了多线程同步的升级解决方案。将synchronized关键字,替换成Lock接口;将Object对象,替换为Condition对象;将wai(),notify(),notifyAll()方法,替换为await(),signal(),signalAll()方法。一个锁,可以对应多个Condition对象。这个特性的出现,可以让多线程在唤醒其他线程时,不必唤醒本方的线程,只唤醒对方线程。例如在生产者消费者模型中,使用Lock和Condition类,可以实现只唤醒消费者线程,或只唤醒生产者线程。 2.Lock接口和Condition接口1)Lock接口已知实现类中,有ReentrantLock类。这个子类可以用来实例化,创建ReentrantLock对象 ReentrantLock lock = new ReentrantLock(); 2)Condition接口的实例可以通过newCondition()方法获得 Condition conditon = Lock.newCondition(); 3)一个Lock对象可以对应多个Condition对象 Condition condition1 = Lock.newCondition();Condition condition2 = Lock.newCondition(); 3.新特性应用将此新特性应用在消费者生产者模型中,实现只唤醒对方线程。 修改之后的Pizza类代码如下: 分别创建的conditionPro和conditionCon对象,用于实现只唤醒对方线程,代码更优。 三、停止线程 1.线程停止原理stop()方法已经过时,停止的唯一标准就是run()方法结束。开启多线程运行,运行代码通常都是循环结构,只要控制住循环,就可以让run()方法结束,就可以让线程结束。 注意:当线程处于冻结状态,无法读取控制循环的标记,线程就不会结束。 2.interrupt()方法将处于冻结状态的线程,强制恢复到运行状态。interrupt()方法是在清除线程的冻结状态。 示例代码: 如果不调用t1和t2线程的interrupt()方法,程序会无响应,因为两个线程都处于冻结状态,无法继续运行。 上述程序运行结果: 四、Thread类其他方法 1.setDaemon()方法将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。守护线程可以理解为后台线程。后台线程开启后,会和前台线程(一般线程)一起抢夺CPU资源;当所有前台线程结束运行后,后台线程自动结束。可以理解为,后台线程依赖前台线程的运行。 示例代码: 在启动两个线程前,将两个线程设置为守护线程,其他代码不变;那么这两个线程依赖主线程运行;虽然这两个线程都处于冻结状态,但是当主线程运行完毕,这两个守护进程随之结束。 2.join()方法调用join()方法的线程,在申请CPU执行权。之前拥有CPU执行权的线程,将转入冻结状态,等调用join()方法的线程执行完毕,再转回运行状态。 示例代码: 程序在启动t1线程之后,主线程先等待t1线程打印完100个数;主线程再继续和t2线程交替打印100个数。…

Java基础—多线程(一)

多线程(一) 一、进程和线程 1.区别和联系区别:进程是一个正在进行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或一个控制单元。进程是系统进行资源分配和调度的一个独立单位。每一个应用程序启动的时候,都会被分配一定的内存空间。进程是用于标识这片内存空间,用于封装内存空间里的控制单元。 线程是进程中一个独立的控制单元,控制着进程的执行。它是CPU分配和调度其资源的基本单位。线程没有办法脱离进程独立运行,必须包含在进程中运行。多线程之间共享进程拥有的资源。 联系:进程和线程都是系统创建的。一个进程中至少有一个线程。同一个进程中的多个线程可以并发执行,提高程序运行效率。 示例代码: 用dos命令行编译上述代码时,会启动javac进程;运行上述代码时,会启动java进程,该进程中至少有一个线程在负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。 小扩展jvm启动的时候,不止一个线程,还有负责垃圾回收机制的进程。 二、多线程 1.多线程存在的意义多线程的出现能让程序产生同时运行的效果。 2.自定义线程Java提供了对线程这类事物的描述,封装为Thread类。创建线程有两种方式:1)继承Thread类,复写run()方法,调用线程的start()方法 示例代码: start()方法启动线程,调用run()方法;修改代码,分析打印结果。 示例代码: 程序运行部分结果: 运行结果每次都不同,因为多个线程都在获取CPU的执行权(资源);CPU执行哪个线程,就运行哪个线程里的代码;某一时刻,只有一个线程在运行(多核除外);CPU在做着快速切换,达到了看上去程序是同时运行的效果。这是多线程的随机性。如图: 程序开始运行,主线程启动,随后创建好的myThread线程启动,负责执行run()方法中的代码;主线程负责执行main()方法中的代码;CPU分配资源,交替执行两个线程,所以有如上运行结果。 小扩展1复写run()方法的原因Thread类用于描述线程,该类定义了一个功能用于存储线程要运行的代码,这个功能就是run()方法。 小扩展2直接调用子类中的run()方法,程序的运行结果示例代码: 程序运行结果为: 如果直接调用run()方法,该程序并没有启动新的线程,而整个程序是由主线程完成执行的。myThread线程虽然被创建,但是始终没有启动。如图: 主线程执行到run()方法,就将run()方法中的代码先执行完,再回到main方法中,执行main方法中的代码;主线程独享CPU资源;直接调用run()方法的效果,相当于对象调用方法,没有多线程执行的特点。 小练习1创建两个线程,和主线程交替运行。示例代码: 程序部分运行结果为: 小练习2用Thread类的getName()方法,打印线程名称示例代码: 程序部分运行结果为: Thread-0即为tt1线程名称。每个线程有自己默认的名称,即Thread-[编号]。标准通用的获取当前线程名称的方法是使用Thread类的currentThread()方法: Thread.currentThread().getName(); currentThread()方法为静态方法,获取当前线程对象,返回Thread类型。如果要自定义名称,使用Thread类的setName()方法或使用线程构造方法即可。 2)实现Runnable接口用多线程实现一个简单的多窗口卖票程序。 示例代码: 程序部分运行结果为: 上述程序声明了静态成员,由于静态成员声明周期过长,一般不建议使用静态属性。如果没有静态,则每个线程对象里有100张票,总共将卖出400张。要解决这个问题,就要使用线程的第二种创建方法:实现Runnable接口。 实现Runnable接口的步骤:1> 声明一个类,实现Runnable接口示例代码:class Ticket implements Runnable {} 2> 复写Runnable接口中的run()方法,将线程要运行的代码存放在run()方法中示例代码:class Ticket implements Runnable {public void run() {} } 3> 创建Thread线程对象,并将Runnable接口的子类对象作为实际参数传给Thread类的构造方法示例代码:Ticket ticket = new Ticket();Thread…