Java网络编程—(4)线程下
2017-09-01 00:27
387 查看
本来打算简单的说一说java线程就算的,无奈线程实在是博大精深,一篇文章连基础都说不完,所以又写了个下篇来说一说线程的问题。上一节说了说线程的基本概念以及其基本的使用方法,也说明了线程存在的问题,这篇文章主要就线程会出现的问题来做出解决办法。
可以看出来程序并没有按照顺序输出,这就是线程异步造成的结果,为了让程序顺序输出得到我们想要的结果我们可以使用synchronize来操作。使用synchronize把输出的代码包围起来就会对system.out同步了。
这个时候我们可以看到就是顺序输出的结果了。一旦线程开始打印这些值,所有其他线程在打印它们的值之前都必须停止来等待这个线程结束。同步要求同一对象的所有代码必须连续的执行,而不能并行执行(如果其他的类中的其他代码块也对此对象执行了同步,那么这个对象也是不能并行执行的)。Java没有提供任何房啊来阻止其他线程使用共享资源,它只能防止对同一对象的其他线程使用这个资源。只有多个线程共享资源的时候才会考虑同步。
我们不仅可以对对象使用同步,还可以对方法使用同步。但是使用同步并不是一一劳永逸的方法,因为这会是VM的性能下降,并且增加了死锁的可能性。
以上就是一个非常经典的死锁的例子:
从结果里就可以看出来程序一直在运行无法停止。
避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。例如,这里是另一个运行中没有嵌套封锁的run()方法,而且程序运行没有死锁局面,运行得很成功。
只对有请求的进行封锁:你应当只想你要运行的资源获取封锁,比如在上述程序中我在封锁的完全的对象资源。但是如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。
避免无限期的等待:如果两个线程正在等待对象结束,无限期的使用线程加入,如果你的线程必须要等待另一个线程的结束,若是等待进程的结束加入最好准备最长时间。
不过,一般情况加不要使用太高优先级,因为可能会造成其他线程饥饿状态。
interrrupt()
notify()
notifyAll()
我们使用GZIPOutputStream过滤流来对当前目录的每一个文件进行压缩,一方面这是一个大量的I/O操作,另一方面数据压缩是一个CPU密集度很高的操作,所以我们不能用太多线程,这是使用线程池的大好机会,客户端线程将压缩文件,主程序告诉线程压缩什么文件,在这里主程序会远远快于线程,所以我们需要填充线程池,然后启动线程池的中线程。主程序构造 了一个线程池并填充了4个线程,以迭代的方式列出所有的文件和目录,这些文件以及这些目录中的文件会用来构建ExcutorsDemo,然后交到线程池,最后由着4个线程之一处理。一旦将所有的文件增加到这个线程池,就可以调用pool.shutDown,这个方法不会终止等待的工作,他只是通知线程池在没有更多的任务增加到内部队列,而且一旦完成所有的等待的工作就应当关闭。
这里我给出一个操作I/O经典不算复杂的例子:
这个列子使用字节流完整的复制文件夹以及文件里面的文件,对于理解I/O会有帮助。当然更复杂的运算,我们还可以使用多线程甚至线程池来复制完整的文件以及文件夹,就有点类似于某雷了。
同步
上一节已经说过线程的异步有时候会出现问题,所以为了解决有时候会出现的问题就要用同步来解决。事实上只要有多个线程共享资源,都必须要考虑同步。这些线程可能是Thread的继承类也可能实现了Runnable接口,也可能是完全不同的类的实例。关键在于这些线程所共享的资源,而不是这些线程是哪个类。synchronized关键字
/** * @author Aaron * @创建日期:2017年8月31日下午11:01:33 * @修改日期 */ public class SynchronizedDemo implements Runnable { @Override public void run() { FileInputStream fis; try { fis = new FileInputStream("D:/test.txt"); MessageDigest sha = MessageDigest.getInstance("SHA-256"); DigestInputStream din = new DigestInputStream(fis, sha); while (din.read() != -1) {//讀取整個文件 byte[] digest = sha.digest(); System.out.println(fis+":"); System.out.println(DatatypeConverter.printHexBinary(digest)); System.out.println(); din.close(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args) { for (int i = 0; i < 4; i++) { SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); Thread thread = new Thread(synchronizedDemo); thread.start(); } } }
可以看出来程序并没有按照顺序输出,这就是线程异步造成的结果,为了让程序顺序输出得到我们想要的结果我们可以使用synchronize来操作。使用synchronize把输出的代码包围起来就会对system.out同步了。
synchronized (System.out) { System.out.println(fis+":"); System.out.println(DatatypeConverter.printHexBinary(digest)); System.out.println(); din.close(); }
这个时候我们可以看到就是顺序输出的结果了。一旦线程开始打印这些值,所有其他线程在打印它们的值之前都必须停止来等待这个线程结束。同步要求同一对象的所有代码必须连续的执行,而不能并行执行(如果其他的类中的其他代码块也对此对象执行了同步,那么这个对象也是不能并行执行的)。Java没有提供任何房啊来阻止其他线程使用共享资源,它只能防止对同一对象的其他线程使用这个资源。只有多个线程共享资源的时候才会考虑同步。
我们不仅可以对对象使用同步,还可以对方法使用同步。但是使用同步并不是一一劳永逸的方法,因为这会是VM的性能下降,并且增加了死锁的可能性。
死锁
假如A先生和B先生需要分别借两本一样的书来作为参考资料来写论文,但是A先生先寄走了A书,B先生又借走了B书,这样两个人就都写不成论文了。所以就造成了死锁。/** * @author Aaron * @创建日期:2017年8月31日下午11:01:33 * @修改日期 */ public abstract class SynchronizedDemo implements Runnable { public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); Object obj2 = new Object(); Object obj3 = new Object(); Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1"); Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2"); Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3"); t1.start(); Thread.sleep(5000); t2.start(); Thread.sleep(5000); t3.start(); } } class SyncThread implements Runnable { private Object obj1; private Object obj2; public SyncThread(Object o1, Object o2) { this.obj1 = o1; this.obj2 = o2; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiring lock on " + obj1); synchronized (obj1) { System.out.println(name + " acquired lock on " + obj1); work(); System.out.println(name + " acquiring lock on " + obj2); synchronized (obj2) { System.out.println(name + " acquired lock on " + obj2); work(); } System.out.println(name + " released lock on " + obj2); } System.out.println(name + " released lock on " + obj1); System.out.println(name + " finished execution."); } private void work() { try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } } }
以上就是一个非常经典的死锁的例子:
从结果里就可以看出来程序一直在运行无法停止。
多个线程切换执行
public static void main(String[] args) { Print print = new Print(); Number number = new Number(print); Letter letter = new Letter(print); Thread thread = new Thread(number); Thread thread2 = new Thread(letter); thread.start(); thread2.start(); } private Print print; /** * 带参数的构造方法传入 print对象 确保两个线程类操作的是同一个对象 * * @param print */ public Number(Print print) { super(); this.print = print; } @Override public void run() { if (print.getTemp()) {//如果符合条件就往下执行 synchronized (print) {//该对象获得锁 for (int i = 1; i <= 52; i++) { System.out.println(i); if (i % 2 == 0) { try { print.setTemp(false);//首先把标志更改 print.notifyAll();//唤醒其他所有正在等待的线程 print.wait();//并把此线程处于等待状态 print.notify();//唤醒此线程,防止程序执行到最后仍处于未停止状态 } catch (InterruptedException e1) { e1.printStackTrace(); } } } } } } private Print print; public Letter(Print print) { super(); this.print = print; } @Override public void run() { if (!print.getTemp()) { synchronized (print) { for (int i = 65; i <= 90; i++) { System.out.println((char) i); try { print.setTemp(true); print.notifyAll(); print.wait(); print.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } } } private boolean temp = true; public boolean getTemp() { return temp; } public void setTemp(boolean temp) { this.temp = temp; }
避免死锁
有很多方针可供我们使用来避免死锁的局面。避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。例如,这里是另一个运行中没有嵌套封锁的run()方法,而且程序运行没有死锁局面,运行得很成功。
public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " acquiring lock on " + obj1); synchronized (obj1) { System.out.println(name + " acquired lock on " + obj1); work(); } System.out.println(name + " released lock on " + obj1); System.out.println(name + " acquiring lock on " + obj2); synchronized (obj2) { System.out.println(name + " acquired lock on " + obj2); work(); } System.out.println(name + " released lock on " + obj2); System.out.println(name + " finished execution."); }
只对有请求的进行封锁:你应当只想你要运行的资源获取封锁,比如在上述程序中我在封锁的完全的对象资源。但是如果我们只对它所属领域中的一个感兴趣,那我们应当封锁住那个特殊的领域而并非完全的对象。
避免无限期的等待:如果两个线程正在等待对象结束,无限期的使用线程加入,如果你的线程必须要等待另一个线程的结束,若是等待进程的结束加入最好准备最长时间。
线程调度
当多个线程运行的时候必须要考虑线程的运行顺序的问题,因为既要避免死锁又要避免饥饿等待。优先级
在Java中不是所有的线程都是均等的,每个线程都有一个优先级,从0——10不等,0是最低级,10是最高级。如果不对线程指定优先级就会默认5。要注意的事不是所有的操作系统都支持者11个优先级,Windows只有7个优先级,并且1和2,3和4,6和7,8和9会做同样的处理,也就是说优先级9的线程并不会抢占优先级8的线程。线程的优先级可以通过setPriority来改变public static void main(String[] args) throws InterruptedException { ListCallbackDigest lad = new ListCallbackDigest(fileName); lad.add(this); Thread thread = new Thread(lad); thread.setPriority(9); thread.start(); }
不过,一般情况加不要使用太高优先级,因为可能会造成其他线程饥饿状态。
抢占
线程调度器主要有两种:抢占式和协作式。抢占式线程调度器确定一个线程正常的轮到其CPU时间的时候回暂停这个线程,协作式将CPU控制权交给其他线程的时候会等待正在运行的线程自己暂停。放弃
yield()方法将通知虚拟机如果有一个线程在等待运行那么就可以运行,当前线程放弃运行。休眠
sleep()interrrupt()
连接线程
join()等待、通知
wait()notify()
notifyAll()
结束
线程以合理的方法结束,线程将撤销,其他线程接管CPU。线程池
这里我们只简单的说一下线程池的问题。之前就已经了解到向一个程序中添加多个线程会极大的提升性能,尤其是I/O受限的程序。不过,线程也是需要开销的,如果一个程序中有数百个线程,java虚拟机会做大量的工作,就算是线程很快就会结束,但是对虚拟机而言也会加重垃圾回收器等的负担,为了减轻CPU的开销减轻虚拟机的压力我们可以使用java.util.concurrent中的Excutors类,轻松的建立线程池。** * @author Aaron * @创建日期:2017年9月5日下午5:09:37 * @修改日期 */ public class ExcutorsDemo implements Runnable { private File inputs; public ExcutorsDemo(File inputs) { this.inputs = inputs; } @Override public void run() { // 不压缩已经压缩的文件 if (!inputs.getName().endsWith(".gz")) { File outputs = new File(inputs.getParent(), inputs.getName() + ".gz"); if (!outputs.exists()) { // 不覆盖已经存在的文件 try (InputStream is = new BufferedInputStream( new FileInputStream(inputs)); OutputStream os = new BufferedOutputStream( new GZIPOutputStream(new FileOutputStream( outputs)));) { int b; while ((b = is.read()) != -1) { os.write(b); os.flush(); } } catch (IOException e) { e.printStackTrace(); } } } } public File getInputs() { return inputs; } public void setInputs(File inputs) { this.inputs = inputs; } }
/** * @author Aaron * @创建日期:2017年9月5日下午5:21:39 * @修改日期 */ public class ExcutorsDemoMain { public final static int THREAD_COUNT = 4; public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT); for (String filename : args) { File file = new File(filename); if (file.exists()) { if (file.isDirectory()) { File[] files = file.listFiles(); for (int i = 0; i < files.length; i++) { if (!files[i].isDirectory()) {// 不递归处理目录 Runnable task = new ExcutorsDemo(files[i]); pool.submit(task); } } } else { Runnable task = new ExcutorsDemo(file); pool.submit(task); } } } pool.shutdown(); } }
我们使用GZIPOutputStream过滤流来对当前目录的每一个文件进行压缩,一方面这是一个大量的I/O操作,另一方面数据压缩是一个CPU密集度很高的操作,所以我们不能用太多线程,这是使用线程池的大好机会,客户端线程将压缩文件,主程序告诉线程压缩什么文件,在这里主程序会远远快于线程,所以我们需要填充线程池,然后启动线程池的中线程。主程序构造 了一个线程池并填充了4个线程,以迭代的方式列出所有的文件和目录,这些文件以及这些目录中的文件会用来构建ExcutorsDemo,然后交到线程池,最后由着4个线程之一处理。一旦将所有的文件增加到这个线程池,就可以调用pool.shutDown,这个方法不会终止等待的工作,他只是通知线程池在没有更多的任务增加到内部队列,而且一旦完成所有的等待的工作就应当关闭。
这里我给出一个操作I/O经典不算复杂的例子:
public class Test { int a = 0; static File startPath = null; static File endPath = null; public static void main(String[] args) throws IOException { Scanner input = new Scanner(System.in); System.out.println("请输入您要复制的原目录:(完整目录)"); startPath = new File(input.next()); System.out.println("请输入您要复制的目标目录:(完整目录)"); endPath = new File(input.next()); Test2016510 test2016510 = new Test2016510(); test2016510.copy(startPath, endPath); System.out.println("复制完成"); } @SuppressWarnings("unused") public void copy(File startFile, File endFile) throws IOException { File startFilePath = startFile; File endFilePath = endFile; File[] startfilePaths = startFilePath.listFiles(); if (startFile.exists()) { for (File file : startfilePaths) { if (file.isDirectory()) { if (file != null) { String ss = file.toString().substring(3, file.toString().length()); File newFile = new File(endFilePath.toString() + "\\" + ss); newFile.mkdirs(); this.copy(file, newFile); } else { String ss = file.toString().substring(3, file.toString().length()); File newFile = new File(endFilePath.toString() + "\\" + ss); newFile.mkdirs(); } } else if (file.isFile()) { String ss = file.getPath().substring(3, file.toString().length()); File newFile = new File(this.endPath + "\\" + ss); String ssss = file.getParent().substring(3); File parentFile = new File(this.endPath + "\\" + ssss); if (!parentFile.exists()) { parentFile.mkdirs(); FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(newFile); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] buff = new byte[1024]; int temp = 0; while ((temp = bis.read(buff)) != -1) { bos.write(buff, 0, temp); bos.flush(); } bos.close(); fos.close(); bis.close(); fis.close(); } else { FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(newFile); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] buff = new byte[1024]; int temp = 0; while ((temp = bis.read(buff)) != -1) { bos.write(buff, 0, temp); bos.flush(); } bos.close(); fos.close(); bis.close(); fis.close(); } } } } else { System.out.println("您指定的文件不存在,程序结束。"); } }
这个列子使用字节流完整的复制文件夹以及文件里面的文件,对于理解I/O会有帮助。当然更复杂的运算,我们还可以使用多线程甚至线程池来复制完整的文件以及文件夹,就有点类似于某雷了。
相关文章推荐
- Java网络编程:从线程返回信息。
- Java网络编程之单线程下载文件设置显示进度
- Java线程及网络编程简介
- 通过培训学到的一个java的基于线程,网络编程等的文件多线程断点下载器(断点功能还在操作实践中)
- 实训 Java基础知识---流,线程,网络编程
- 读书笔记-java网络编程-3线程-java线程概述
- [java网络编程]线程与进程
- java网络编程(一)单线程网络编程
- [java网络编程]线程的互斥
- 读书笔记-java网络编程-3线程-从线程返回信息
- java网络编程-双线程实现UDP通信
- Java网络编程之单线程下载文件设置显示进度(一)
- Java网络编程 线程
- Java网络编程学习笔记(三)线程
- Java线程——线程池结合网络编程
- Java网络编程—(3)线程上
- java 网络编程
- Java并发编程之守护线程(短文)
- 【幻化万千戏红尘】qianfengDay23-java基础学习:网络编程、TCP IP协议、端口Socket、ServerSocket
- 别样JAVA学习(十四)网络编程1.1