您的位置:首页 > 理论基础 > 计算机网络

Java网络编程—(4)线程下

2017-09-01 00:27 387 查看
本来打算简单的说一说java线程就算的,无奈线程实在是博大精深,一篇文章连基础都说不完,所以又写了个下篇来说一说线程的问题。上一节说了说线程的基本概念以及其基本的使用方法,也说明了线程存在的问题,这篇文章主要就线程会出现的问题来做出解决办法。

同步

上一节已经说过线程的异步有时候会出现问题,所以为了解决有时候会出现的问题就要用同步来解决。事实上只要有多个线程共享资源,都必须要考虑同步。这些线程可能是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会有帮助。当然更复杂的运算,我们还可以使用多线程甚至线程池来复制完整的文件以及文件夹,就有点类似于某雷了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: