Java关流对流对象有什么影响_Java面试题全集(1.4)

来源:骆昊

链接:blog.csdn.net/jackfrued/article/details/44921941

60、请说出与线程同步以及线程调度相关的方法。

答:

- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;

- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;

- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

提示:关于Java多线程和并发编程的问题,建议大家看我的另一篇文章《关于Java并发编程的总结和思考》。

补充:Java 5通过Lock接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;此外,Java 5还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。

下面的例子演示了100个线程同时向一个银行账户中存入1元钱,在没有使用同步机制和使用同步机制情况下的执行情况。

银行账户类:

/**

* 银行账户

* @author 骆昊

*

*/

public class Account {

private double balance;     // 账户余额

/**

* 存款

* @param money 存入金额

*/

public void deposit(double money) {

double newBalance = balance + money;

try {

Thread.sleep(10);   // 模拟此业务需要一段处理时间

}

catch(InterruptedException ex) {

ex.printStackTrace();

}

balance = newBalance;

}

/**

* 获得账户余额

*/

public double getBalance() {

return balance;

}

}

存钱线程类:

/**

* 存钱线程

* @author 骆昊

*

*/

public class AddMoneyThread implements Runnable {

private Account account;    // 存入账户

private double money;       // 存入金额

public AddMoneyThread(Account account, double money) {

this.account = account;

this.money = money;

}

@Override

public void run() {

account.deposit(money);

}

}

测试类:

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

public class Test01 {

public static void main(String[] args) {

Account account = new Account();

ExecutorService service = Executors.newFixedThreadPool(100);

for(int i = 1; i <= 100;="" i++)="">=>

service.execute(new AddMoneyThread(account, 1));

}

service.shutdown();

while(!service.isTerminated()) {}

System.out.println('账户余额: ' + account.getBalance());

}

}

在没有同步的情况下,执行结果通常是显示账户余额在10元以下,出现这种状况的原因是,当一个线程A试图存入1元的时候,另外一个线程B也能够进入存款的方法中,线程B读取到的账户余额仍然是线程A存入1元钱之前的账户余额,因此也是在原来的余额0上面做了加1元的操作,同理线程C也会做类似的事情,所以最后100个线程执行结束时,本来期望账户余额为100元,但实际得到的通常在10元以下(很可能是1元哦)。解决这个问题的办法就是同步,当一个线程对银行账户存钱时,需要将此账户锁定,待其操作完成后才允许其他的线程进行操作,代码有如下几种调整方案:

在银行账户的存款(deposit)方法上同步(synchronized)关键字

/**

* 银行账户

* @author 骆昊

*

*/

public class Account {

private double balance;     // 账户余额

/**

* 存款

* @param money 存入金额

*/

public synchronized void deposit(double money) {

double newBalance = balance + money;

try {

Thread.sleep(10);   // 模拟此业务需要一段处理时间

}

catch(InterruptedException ex) {

ex.printStackTrace();

}

balance = newBalance;

}

/**

* 获得账户余额

*/

public double getBalance() {

return balance;

}

}

在线程调用存款方法时对银行账户进行同步

/**

* 存钱线程

* @author 骆昊

*

*/

public class AddMoneyThread implements Runnable {

private Account account;    // 存入账户

private double money;       // 存入金额

public AddMoneyThread(Account account, double money) {

this.account = account;

this.money = money;

}

@Override

public void run() {

synchronized (account) {

account.deposit(money);

}

}

}

通过Java 5显示的锁机制,为每个银行账户创建一个锁对象,在存款操作进行加锁和解锁的操作

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/**

* 银行账户

*

* @author 骆昊

*

*/

public class Account {

private Lock accountLock = new ReentrantLock();

private double balance; // 账户余额

/**

* 存款

*

* @param money

*            存入金额

*/

public void deposit(double money) {

accountLock.lock();

try {

double newBalance = balance + money;

try {

Thread.sleep(10); // 模拟此业务需要一段处理时间

}

catch (InterruptedException ex) {

ex.printStackTrace();

}

balance = newBalance;

}

finally {

accountLock.unlock();

}

}

/**

* 获得账户余额

*/

public double getBalance() {

return balance;

}

}

按照上述三种方式对代码进行修改后,重写执行测试代码Test01,将看到最终的账户余额为100元。当然也可以使用Semaphore或CountdownLatch来实现同步。

61、编写多线程程序有几种实现方式?

答:Java 5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。

补充:Java 5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,代码如下所示:

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

class MyTask implements Callable{

private int upperBounds;

public MyTask(int upperBounds) {

this.upperBounds = upperBounds;

}

@Override

public Integer call() throws Exception {

int sum = 0;

for(int i = 1; i <= upperbounds;="" i++)="">=>

sum += i;

}

return sum;

}

}

class Test {

public static void main(String[] args) throws Exception {

List<>> list = new ArrayList<>();

ExecutorService service = Executors.newFixedThreadPool(10);

for(int i = 0; i < 10;="" i++)="">

list.add(service.submit(new MyTask((int) (Math.random() * 100))));

}

int sum = 0;

for(Futurefuture : list) {

// while(!future.isDone()) ;

sum += future.get();

}

System.out.println(sum);

}

}

62、synchronized关键字的用法?

答:synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。在第60题的例子中已经展示了synchronized关键字的用法。

63、举例说明同步和异步。

答:如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

64、启动一个线程是调用run()还是start()方法?

答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。

65、什么是线程池(thread pool)?

答:在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:

- newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

- newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

- newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

- newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

- newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

第60题的例子中演示了通过Executors工具类创建线程池并使用线程池执行线程的代码。如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。

66、线程的基本状态以及状态之间的关系?

答:

84223860_1

说明:其中Running表示运行状态,Runnable表示就绪状态(万事俱备,只欠CPU),Blocked表示阻塞状态,阻塞状态又有多种情况,可能是因为调用wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用了sleep()方法或join()方法等待休眠或其他线程结束,或是因为发生了I/O中断。

67、简述synchronized 和java.util.concurrent.locks.Lock的异同?

答:Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)。

68、Java中如何实现序列化,有什么意义?

答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。

要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(可以参考第29题)。

69、Java中有几种类型的流?

答:字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。

面试题 - 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;

public final class MyUtil {

private MyUtil() {

throw new AssertionError();

}

public static void fileCopy(String source, String target) throws IOException {

try (InputStream in = new FileInputStream(source)) {

try (OutputStream out = new FileOutputStream(target)) {

byte[] buffer = new byte[4096];

int bytesToRead;

while((bytesToRead = in.read(buffer)) != -1) {

out.write(buffer, 0, bytesToRead);

}

}

}

}

public static void fileCopyNIO(String source, String target) throws IOException {

try (FileInputStream in = new FileInputStream(source)) {

try (FileOutputStream out = new FileOutputStream(target)) {

FileChannel inChannel = in.getChannel();

FileChannel outChannel = out.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(4096);

while(inChannel.read(buffer) != -1) {

buffer.flip();

outChannel.write(buffer);

buffer.clear();

}

}

}

}

}

注意:上面用到Java 7的TWR,使用TWR后可以不用在finally中释放外部资源 ,从而让代码更加优雅。

70、写一个方法,输入一个文件名和一个字符串,统计这个字符串在这个文件中出现的次数。

答:代码如下:

import java.io.BufferedReader;

import java.io.FileReader;

public final class MyUtil {

// 工具类中的方法都是静态方式访问的因此将构造器私有不允许创建对象(绝对好习惯)

private MyUtil() {

throw new AssertionError();

}

/**

* 统计给定文件中给定字符串的出现次数

*

* @param filename  文件名

* @param word 字符串

* @return 字符串在文件中出现的次数

*/

public static int countWordInFile(String filename, String word) {

int counter = 0;

try (FileReader fr = new FileReader(filename)) {

try (BufferedReader br = new BufferedReader(fr)) {

String line = null;

while ((line = br.readLine()) != null) {

int index = -1;

while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {

counter++;

line = line.substring(index + word.length());

}

}

}

} catch (Exception ex) {

ex.printStackTrace();

}

return counter;

}

}

系列:

关注「ImportNew」

看更多 Java 技术精选文章

↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 8的(stream)操作是一种新的API,提供了一种简单而有效的方法来处理集合和数组等数据类型。操作可以让我们以一种更加声明式的方式来处理数据,从而提高代码的可读性和可维护性。 Java 8中的操作具有以下特点: 1. 集成了过滤、映射、排序、聚合等操作; 2. 支持并行处理,提高了运行效率; 3. 可以避免空指针异常等常见问题; 4. 支持延迟计算,节省了资源。 下面是使用Java 8的操作的一些示例: 1. 过滤操作: ``` List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> result = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); ``` 上面的代码使用了stream()方法将List转换成,使用filter()方法过滤出偶数,最后使用collect()方法将结果转换成List。 2. 映射操作: ``` List<String> words = Arrays.asList("hello", "world"); List<Integer> result = words.stream() .map(String::length) .collect(Collectors.toList()); ``` 上面的代码使用了map()方法将List中的字符串转换成它们的长度,最后使用collect()方法将结果转换成List。 3. 排序操作: ``` List<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2); List<Integer> result = numbers.stream() .sorted() .collect(Collectors.toList()); ``` 上面的代码使用了sorted()方法将List中的数字按升序排序,最后使用collect()方法将结果转换成List。 4. 聚合操作: ``` List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> result = numbers.stream() .reduce((a, b) -> a + b); ``` 上面的代码使用了reduce()方法将List中的数字相加,最后返回一个Optional对象,其值为15。 在实际开发中,Java 8的操作可以大大简化代码,提高开发效率。但是需要注意的是,在处理大量数据时,操作可能会对性能产生影响,因此需要根据实际情况选择合适的方法。 ### 回答2: Java 8引入了(stream)操作,它是一种用于处理集合数据的新方式。 操作主要通过对集合进行一系列的处理操作来实现数据的筛选、转换、聚合等功能。与传统的集合操作相比,操作具有更加简洁、灵活和高效的特点。下面是一些常用的操作: 1. 过滤(filter):根据指定条件从中筛选出符合条件的元素。 2. 映射(map):通过对流中的每个元素应用指定的函数来生成一个新的,可以对元素进行转换或提取。 3. 排序(sorted):对流中的元素进行排序,可以根据自然顺序或自定义的排序规则进行排序。 4. 限制(limit):截断,获取指定数量的元素。 5. 跳过(skip):跳过指定数量的元素。 6. 匹配(matching):用于检查中的元素是否满足指定的条件。 7. 查找(finding):在中查找符合指定条件的元素。 8. 聚合(reducing):将中的元素使用指定的操作进行聚合,可以得到最大值、最小值、求和等结果。 9. 收集(collecting):将中的元素收集到一个集合中,可以使用预定义的收集器或自定义的收集器。 使用操作可以大大简化代码,并且使得操作更为直观和易于理解。它提供了丰富的功能,可以处理各种不同类型的集合数据。同时,操作的使用还可以减少中间变量的使用,提高代码的性能。 综上所述,Java 8的操作是一种强大的工具,可以帮助我们对集合数据进行高效的处理,提升代码的简洁性和可读性。它是现代化Java编程中不可或缺的一部分。 ### 回答3: Java 8 引入了(stream)操作,它是一种新的处理数据集合的方式。操作可以让我们以更简洁、清晰和灵活的方式处理数据。 首先,是一组有序的元素,它可以来自各种数据源,比如数组、集合或者I/O通道。操作分为两种类型:中间操作和终端操作。中间操作可以对流进行转换或者过滤,但并不产生最终结果。而终端操作会触发的处理,并产生一个结果或副作用。 通过操作,我们可以进行各种常见的数据处理操作。例如,我们可以使用 filter 方法来过滤中的元素,只保留符合条件的元素。可以使用 map 方法来对流中的元素进行映射,生成一个新的。可以使用 reduce 方法将中的所有元素聚合起来并生成一个结果。 此外,操作还支持并行处理。通过将转换为并行,我们可以同时对多个元素进行处理,提高处理效率。但是需要注意的是,并不是所有的操作都适合并行处理,有些操作在并行处理时可能会导致结果不确定或出错。 总之,Java 8 的操作可以让我们以更简洁、灵活的方式处理数据集合。它提供了丰富的中间操作和终端操作,支持串行和并行处理。通过学习和掌握操作,我们可以更高效地处理数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值