- 学习内容
- 多线程
- 进程
- 线程
- 线程与进程
- 并发和并行
- 多线程编程
- java 中的线程
- 线程调度
- 分时调度
- 抢占式调度
- 创建新线程
- 第一种方式,利用 `Thread` 类
- 第二种方式,实现 `Runnable` 接口
- 第三种方式:匿名内部类
- 线程的优先级
- 同步
- 同步代码块
- 同步方法
- Lock 锁机制
- 线程状态
- I/0
- File 类
- 概述
- File 类中的静态成员变量
- 路径
- 构造方法
- 常用方法
- 判断功能的方法
- 增删的方法
- 目录的遍历
- 字节流
- 字节输出流
- 常用方法
- FileOutputStream 类
- 常用方法:
- 字节输入流 InputStream
- FileInputStream 类
- 读取字节数据
- 字符流
- 与字节流区别
- 任务内容
- 任务0
- 任务1 熟悉线程生命周期方法
- 1-1
- 1-2
- 任务2
- Runnable 类
- Thread 类:
- 任务3
- 任务4
- 任务5
- 任务6
在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。
线程某些进程内部还需要同时执行多个子任务,这些子任务就是线程。
例如:用播放器播放视频时,程序输出视频画面是一个进程、音频是一个进程、字幕是一个进程、显示视频进度是一个进程。
线程是 *** 作系统调度的最小任务单位。
线程与进程进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由单进程内的多线程实现,还可以混合多进程+多线程。
具体采用哪种方式,要考虑到进程和线程的特点。
和多线程相比,多进程的缺点在于:
- 创建进程比创建线程开销大,尤其是在Windows系统上;
- 进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。
而多进程的优点在于:
- 多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。
进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。
并发和并行并行:在同一时刻,在多个指令在多个cpu同时进行。
并发:在同一时刻,在多个指令在单个cpu交替执行。
多线程编程Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部,我们又可以启动多个线程。对于大多数Java程序来说,我们说多任务,实际上是说如何使用多线程实现多任务。
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。
例如,播放视频时时,就必须由三个线程分别播放视频、播放音频、呈现字母,三个线程需要协调运行,否则就会出现如音画不同步、字母不同步等情况。因此,多线程编程的复杂度高,调度更困难。
Java多线程编程的特点又在于:
- 多线程模型是Java程序最基本的并发模型;
- 后续读写网络、数据库、Web开发等都依赖Java多线程模型。
因此,必须掌握Java多线程编程才能继续深入学习其他内容。
java 中的线程 线程调度 分时调度所有线程轮流使用 CPU 的使用权,平均分配每个线程所占用的时间片。
抢占式调度优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对较多一些。
创建新线程Java用Thread对象表示一个线程。
第一种方式,利用 Thread 类-
创建 Thread 类的子类
-
在这个子类中重写 Thread 类的 run() 方法,设置线程任务。
-
创建子类的实例对象。
-
调用 Thread 类中的 start() 方法,开启这个线程,调用 run() 方法。
注意:一个线程对象只能调用一次 `start()` 方法; 线程的执行代码写在 `run()` 方法中; 线程调度由 *** 作系统决定,程序本身无法决定调度顺序;第二种方式,实现 Runnable 接口
- 定义 Runnable 接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
的线程对象。 - 调用线程对象的start()方法来启动线程。
Thread.sleep()可以把当前线程暂停一段时间,传入的参数是毫秒。可以通过传参来调整暂停时间的大小。
第三种方式:匿名内部类 线程的优先级可以对线程设定优先级,设定优先级的方法是:
Thread.setPriority(int n) // 1~10, 默认值5
优先级高的线程被 *** 作系统调度的优先级较高, *** 作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
同步 同步代码块synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){ 需要同步 *** 作的代码 }
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
- 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程进入阻塞状态等待(BLOCKED)。
使用 synchronized 修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着,进入阻塞状态。
格式:
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步方法中的同步锁:
- 对于非static方法,同步锁就是this。
- 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定 *** 作,同步代码块 / 同步方法具有的功能Lock都有,还有更强大的功能。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
使用步骤:
-
先创建Lock对象,
Lock lock =new ReentrantLock();
-
在可能出现同步安全问题的代码前调用Lock接口中的方法lock() 获取锁
-
在可能出现同步安全问题的代码后调用Lock接口中的方法unlock() 释放锁
-
新建(NEW):新创建了一个线程,但还没调用线程的 start 方法;
-
运行(RUNNABLE),又包括:
就绪(ready):运行线程的 start 方法启动后,线程位于可运行线程池中,等待被调度;
运行中(RUNNING):就绪的线程获得 CPU 的时间片就变为运行中。
-
阻塞(BLOCKED):线程等待获得锁;
-
等待(WAITING):接收事件通知后或系统中断后进入等待,进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
-
超时(TIMED_WAITING):等待指定时间后会自行返回;
-
终止(TERMINATED):线程已执行完毕;
注意:阻塞(BLOCKED)和等待(WAITING)不用刻意区分,这两者都会暂停线程。
I/0 File 类 概述文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等 *** 作。
File 类中的静态成员变量static String pathSeparator :与系统有关的路径分隔符,用来分隔多个路径,在 windows 中是分号 ; 。
static String separator :与系统有关的默认名称分隔符,在 widows 中是反斜杠 。
路径绝对路径:从盘符开始的路径,这是一个完整的路径。
相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
构造方法public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。
常用方法public String getAbsolutePath() :返回此File的绝对路径名字符串。
public String getPath() :将此File转换为路径名字符串。
public String getName() :返回由此File表示的文件或目录的名称。
public long length() :返回由此File表示的文件的长度。
判断功能的方法public boolean exists() :此File表示的文件或目录是否实际存在。
public boolean isDirectory() :此File表示的是否为目录。
public boolean isFile() :此File表示的是否为文件。
增删的方法public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录。
public boolean mkdir() :创建由此File表示的目录。
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
目录的遍历public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
字节流 字节输出流java.io.OutputStream 类是一个抽象类,表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字
节输出流的基本共性功能方法。
public void close() :关闭此输出流并释放与此流相关联的任何系统资源。当完成流的 *** 作时,必须调用此方法,释放系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) :将指定的字节输出流。
FileOutputStream 类是 OutputStream 类的子类,它是文件输出流,用于将数据输出到文件。
常用方法:public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) :将指定的字节输出流。
字节输入流 InputStreamjava.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。当完成流的 *** 作时,必须调用此方法,释放系统资源。
public abstract int read() : 从输入流读取数据的下一个字节。
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
FileInputStream 类该类是文件输入流,从文件中读取字节。
读取字节数据- 读取字节: read 方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1 。
- 使用字节数组读取: read(byte[] b) ,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读
取到末尾时,返回 -1。
内容跟字节流基本差不多,就不复制粘贴了。
要点:输入流的抽象类是Reader,其读取字符文件的子类是FileReader。
输出流的抽象类是Writer,其读取字符文件的子类是FileWrite。
读取、输出的时候,把字节流里的byte[]换成char[]即可。
与字节流区别字符流,只能 *** 作文本文件,不能 *** 作图片,视频等非文本文件。
当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
任务内容 任务0运行后可看看效果,然后尝试着解释为什么结果是这样的。
package com.xxm.advanced_camp.mission10_multithreading; public class test1 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized ("锁") { System.out.println("t1 start"); try { // t1 释放锁 "锁".wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 end"); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized ("锁") { System.out.println("t2 start"); try { // 通知 t1 进入等待队列 "锁".notify(); } catch (Exception e) { e.printStackTrace(); } System.out.println("t2 end"); } } }); t1.start(); t2.start(); } }
答:运行结果
t1 start t2 start t2 end t1 end
答:
尝试解释:
JVM先调用主方法 → 调用 t1 线程的 run() 方法 → 创造一个“同步锁” → 执行输出语句,输出 “t1 start” → 进入等待状态,JVM继续执行主方法 → 调用 t2 线程的 run() 方法 → 执行输出语句,输出 “t2 start” → 通知所有“同步锁”的成员,等待中的线程可以继续执行了 → 执行输出语句,输出 “t2 end” →继续执行“同步锁”成员中的等待成员后面的语句,也就是 t1 线程中后面的语句,也就是执行输出语句,输出"t1 end"。
任务1 熟悉线程生命周期方法 1-1开启四个线程,两个线程调用锁的 wait 方法,另外两个调用锁的 notify 方法,观察执行结果并解释原因。
答:代码如下 :
package com.xxm.advanced_camp.mission10_multithreading.task1; public class Task1 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { synchronized ("锁") { System.out.println("t1 线程开始"); try { "锁".wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 线程结束"); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { synchronized ("lock1") { System.out.println("t2 线程开始"); try { "lock1".wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2 线程结束"); } } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { synchronized ("锁") { System.out.println("t3 线程开始"); try { "锁".notify(); } catch (Exception e) { e.printStackTrace(); } System.out.println("t3 线程结束"); } } }); Thread t4 = new Thread(new Runnable() { @Override public void run() { synchronized ("lock1") { System.out.println("t4 线程开始"); try { "lock1".notify(); } catch (Exception e) { e.printStackTrace(); } System.out.println("t4 线程结束"); } } }); t1.start(); t2.start(); t3.start(); t4.start(); } }
执行结果并不唯一,有多种情况
//第一次执行 t1 线程开始 t2 线程开始 t3 线程开始 t4 线程开始 t3 线程结束 t4 线程结束 t1 线程结束 t2 线程结束 //第二次执行 t1 线程开始 t2 线程开始 t3 线程开始 t3 线程结束 t1 线程结束 t4 线程开始 t4 线程结束 t2 线程结束 //第三次执行 t2 线程开始 t1 线程开始 t4 线程开始 t3 线程开始 t4 线程结束 t3 线程结束 t1 线程结束 t2 线程结束
原因:
线程的执行顺序是不确定的。调用Thread的start()方法启动线程时,线程的执行顺序是不确定的。
也就是说,在同一个方法中,连续创建多个线程后,调用线程的start()方法的顺序并不能决定线程的执行顺序。
但是利用wait()和notify()方法后,可以保证一定的顺序。
所以在上面的结果中, t1,t2 结束一定不会都在 t3、t4 开始的上面。即:只有当调用了线程 t3 或 t4 中的notify()方法,线程 t1 和 t2 中wait()方法后的语句才会被执行。
1-2在 main 函数中开启一个子线程,如果想让 main 主线程在子线程执行完之后才继续执行,代码该怎么写?
任务2
使用 synchronized 实现抢票程序:某商场做活动,有 100 部 iPhone 可以抽奖兑换。
现在在三个柜台同时兑换,要求所有柜台已兑换的 iPhone 数量加起来刚好是 100,既不能多换,也不能少换。
要求:
用 Thread 类实现;
用 Runnable 接口实现。
Runnable 类package com.xxm.advanced_camp.mission10_multithreading; public class SellPhones { private static int iphones = 100; public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { while (true) { synchronized ("lock") { if (iphones > 0) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "柜台正在兑换第 " + iphones + " 台 iPhone"); iphones--; } } } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { while (true) { synchronized ("lock") { if (iphones > 0) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "柜台正在兑换第 " + iphones + " 台 iPhone"); iphones--; } } } } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { while (true) { synchronized ("lock") { if (iphones > 0) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "柜台正在兑换第 " + iphones + " 台 iPhone"); iphones--; } } } } }); t1.start(); t2.start(); t3.start(); } }Thread 类:
1、先实现 Thread 接口,覆写run()方法
package com.xxm.advanced_camp.mission10_multithreading.task2; import com.xxm.advanced_camp.mission10_multithreading.task2.SellPhones2; public class MyThread extends Thread{ @Override public void run(){ while (true) { synchronized ("lock") { if (SellPhones2.iphones > 0) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "柜台正在兑换第 " + SellPhones2.iphones + " 台 iPhone"); SellPhones2.iphones--; } } } } }
2、创建MyThread类的实例对象,直接start,完成。
package com.xxm.advanced_camp.mission10_multithreading.task2; public class SellPhones2 { public static int iphones = 100; public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); t1.start(); t2.start(); t3.start(); } }任务3
使用 newScheduledThreadPool 线程池实现每隔 1 分钟打印一条消息。
答:代码如下:
package com.xxm.advanced_camp.mission10_multithreading.task3; import java.util.concurrent.*; public class Task3 { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); pool.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("一分钟过去了"); } }, 0, 1, TimeUnit.MINUTES); } }任务4
(1)在 Windows 系统中,以递归方式读取 C 盘中所有的目录和文件,并打印出每个文件的大小和每个目中文件的数量。
答:思路:
1、创建File 对象,
2、创建一个打印的方法printAll(File f)。该方法中,使用listFiles方法创建File[]数组,遍历获取f对象下的所有文件及文件夹。
3、对数组中的所有对象,进行判断:是文件,则打印其绝对路径;是文件夹,则打印其绝对路径,并让这个文件夹调用printAll方法,实现递归。
package com.xxm.advanced_camp.Mission11_IO; import java.io.File; public class Task1 { public static void main(String[] args) { //1、创建File对象 File f = new File("C:"); printAll(f); } //2、创建遍历打印的方法 public static void printAll(File file) { File[] files = file.listFiles(); for (File f : files) { //判断是否为文件或文件夹 if (f.isFile()) { System.out.println(f.getAbsolutePath()); } else if (f.isDirectory()) { System.out.println(f.getAbsolutePath()); printAll(f); } } } }package com.xxm.advanced_camp.Mission11_IO; import java.io.File; public class Task1 { public static void main(String[] args) { //1、创建File对象 File f = new File("C:"); printAll(f); } //2、创建遍历打印的方法 public static void printAll(File file) { File[] files = file.listFiles(); for (File f : files) { //判断是否为文件或文件夹 if (f.isFile()) { System.out.println(f.getAbsolutePath()); } else if (f.isDirectory()) { System.out.println(f.getAbsolutePath()); printAll(f); } } } }任务5
在网络上下载一个大文件(任何文件都可以,可以是 exe 文件,也可以是日志、电影或其它类型的大文件),然后使用 Java 标准 I/O 模型读取并复制成另一份文件:
用字节流实现;
答:
package com.xxm.advanced_camp.Mission11_IO; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; public class Task2 { public static void main(String[] args) throws Exception { File file = new File("E:\xxm_C1\计算机通识-1.mp4"); //1.指定数据源 FileInputStream fis = new FileInputStream(file); //2.指定输出位置 FileOutputStream fos = new FileOutputStream("E:\xxm_C1\计算机通识的分身1号.mp4"); //3.读取数据 byte[] b = new byte[1024]; int len; while ((len = fis.read(b)) != -1) { fos.write(b, 0, len); } //4.释放资源 fis.close(); fos.close(); } }
用字符流实现:
package com.xxm.advanced_camp.Mission11_IO; import java.io.FileReader; import java.io.FileWriter; public class Task2_2 { public static void main(String[] args) throws Exception { //1.指定输入源 FileReader fr = new FileReader("C:\Users\yyq\Desktop\日报月25日.md"); //2.指定输出位置 FileWriter fw = new FileWriter("C:\Users\yyq\Desktop\日报月25日的日报的分身.md"); //3.读取数据 char[] c = new char[1024]; int len; while ((len = fr.read(c)) != -1) { fw.write(c, 0, len); fw.flush(); } //4.释放资源 fr.close(); fw.close(); } }任务6
针对练习 2,用 NIO 重新实现,然后比较一下执行效率。
答:
package com.xxm.advanced_camp.Mission11_IO; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.channels.FileChannel; public class Task3 { public static void main(String[] args) throws Exception { FileChannel in = new FileInputStream("C:\Users\yyq\Desktop\日报\微型 ORM 框架.md").getChannel(), out = new FileOutputStream("C:\Users\yyq\Desktop\日报\微型 ORM 框架的 NIO 分身.md").getChannel(); in.transferTo(0, in.size(), out); } }
复制同一个文本文件时,字符流和 NIO 执行时间差不多,但有可能是因为文本文件太小,导致产生了天花板效应。
复制同一个视频文件(1.58 G)时,字节流耗时约为 37520 毫秒;NIO 方法约为 36902 毫秒,好像差不多的样子。。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)