Java多线程编程核心技术---线程间通信(二)

时间:2022-12-08 13:35:20
通过管道进行线程间通信:字节流

Java提供了各种各样的输入/输出流Stream可以很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,无需借助于类似临时文件之类的东西。

JDK提供了4个类来使线程间可以进行通信:

  1. PipedInputStream和PipedOutputStream
  2. PipedReader和PipedWriter
public class WriteData {
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write:");
for (int i = 0; i < 100; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
//System.out.print(outData);
}
//System.out.println();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ReadData {
public void readMethod(PipedInputStream input) {
try {
System.out.println("read:");
byte[] byteArray = new byte[20];
int readLength = input.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.println(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadWrite extends Thread {
private WriteData writeData;
private PipedOutputStream out; public ThreadWrite(WriteData writeData, PipedOutputStream out) {
super();
this.out = out;
this.writeData = writeData;
} @Override
public void run() {
writeData.writeMethod(out);
}
} public class ThreadRead extends Thread {
private ReadData readData;
private PipedInputStream input; public ThreadRead(ReadData readData, PipedInputStream input) {
super();
this.input = input;
this.readData = readData;
} @Override
public void run() {
readData.readMethod(input);
}
} public class Main {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData(); PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream(); outputStream.connect(inputStream);
//inputStream.connect(outputStream); ThreadRead threadRead = new ThreadRead(readData, inputStream);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream); threadRead.start();//先启动读线程 Thread.sleep(1000); threadWrite.start();//后启动写线程
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

read:
write:
123456
78910111213141516171
81920212223242526272
8293031323334353637
383940414243
44454647484950
51525354555657
58596061626364
65666768697071
72737475767778
798081828384
858687888990
91929394959697
9899100

先启动读线程,由于没有数据被写入,读线程阻塞在readLength = input.read(byteArray),直到有数据被写入才继续往下运行。

通过管道进行线程间通信:字符流
public class WriteData {
public void writeMethod(PipedWriter writer) {
try {
System.out.println("write:");
for (int i = 0; i < 100; i++) {
String outData = "" + (i + 1);
writer.write(outData);
//System.out.print(outData);
}
//System.out.println();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ReadData {
public void readMethod(PipedReader reader) {
try {
System.out.println("read:");
char[] array = new char[20];
int readLength = reader.read(array);
while (readLength != -1) {
String newData = new String(array, 0, readLength);
System.out.println(newData);
readLength = reader.read(array);
}
System.out.println();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadWrite extends Thread {
private WriteData writeData;
private PipedWriter writer; public ThreadWrite(WriteData writeData, PipedWriter writer) {
super();
this.writer = writer;
this.writeData = writeData;
} @Override
public void run() {
writeData.writeMethod(writer);
}
} public class ThreadRead extends Thread {
private ReadData readData;
private PipedReader reader; public ThreadRead(ReadData readData, PipedReader reader) {
super();
this.reader = reader;
this.readData = readData;
} @Override
public void run() {
readData.readMethod(reader);
}
} public class Main {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData(); PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter(); // writer.connect(reader);
reader.connect(writer); ThreadRead threadRead = new ThreadRead(readData, reader);
ThreadWrite threadWrite = new ThreadWrite(writeData, writer); threadRead.start(); Thread.sleep(1000); threadWrite.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

read:
write:
12345678910111213141
51617181920212223242
52627282930313233343
53637383940414243444
54647484950515253545
55657585960616263646
56667686970717273747
57677787980818283848
58687888990919293949
596979899100

打印结果和前一个例子基本一样。此实验是在两个线程中通过管道流进行字符数据的传输。


等待/通知:交叉备份

创建20个线程,10个线程将数据备份到A数据库中,10个线程将数据备份到B数据库中,并且备份A数据库和备份B数据库是交叉进行的。

public class DBTools {
volatile private boolean prevIsA = false;
synchronized public void backupA(){
try {
while (prevIsA) {
wait();
}
for (int i = 0; i < 5; i++) {
System.out.println("☆☆☆☆☆");
}
prevIsA = true;
notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
} synchronized public void backupB(){
try {
while (!prevIsA) {
wait();
}
for (int i = 0; i < 5; i++) {
System.out.println("★★★★★");
}
prevIsA = false;
notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadA extends Thread {
private DBTools dbTools;
public ThreadA(DBTools dbTools) {
super();
this.dbTools = dbTools;
} @Override
public void run() {
dbTools.backupA();
}
} public class ThreadB extends Thread {
private DBTools dbTools;
public ThreadB(DBTools dbTools) {
super();
this.dbTools = dbTools;
} @Override
public void run() {
dbTools.backupB();
}
} public class Main {
public static void main(String[] args) {
DBTools dbTools = new DBTools();
for (int i = 0; i < 20; i++) {
ThreadA threadA = new ThreadA(dbTools);
threadA.start();
ThreadB threadB = new ThreadB(dbTools);
threadB.start();
}
}
}

控制台打印结果如下:

......
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
......

交替打印是使用prevIsA作为标记来实现的。


方法join的使用

在很多情况下,主线程创建并启动子线程,如果子线程要进行大量运算,主线程往往比子线程先运行完毕。如果主线程想要等待子线程执行完之后再结束,就需要用到join方法。

先看一个实验。

public class MyThread extends Thread {
@Override
public void run() {
try {
int secondValue = (int)(Math.random() * 10000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
// Thread.sleep(?);
System.out.println("我想在thread对象执行完毕之后再执行");
System.out.println("但是上面sleep()中的值写多少呢?");
System.out.println("答案是:不能确定!!!");
}
}
用join()方法来解决上面的问题
public class MyThread extends Thread {
@Override
public void run() {
try {
int secondValue = (int)(Math.random() * 10000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
// Thread.sleep(?);
thread.join();
System.out.println("我想在thread对象执行完毕之后再执行");
}
}

控制台打印结果如下:

9230
我想在thread对象执行完毕之后再执行

方法join()的作用是使所属线程对象x正常执行run()方法中的任务,而使当前线程进行无限期的阻塞。等线程x执行完毕之后再执行当前线程后续的代码。


方法join()与异常

在join()过程中,如果当前线程对象被中断,则当前线程出现异常。

public class ThreadA extends Thread {

	@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
String newString = new String();
Math.random();
}
}
} public class ThreadB extends Thread {
@Override
public void run() {
try {
ThreadA a = new ThreadA();
a.start();
a.join();
System.out.println("线程B在run end处打印");
} catch (Exception e) {
System.out.println("线程B在catch处打印");
e.printStackTrace();
}
}
} public class ThreadC extends Thread {
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
super();
this.threadB = threadB;
} @Override
public void run() {
threadB.interrupt();
}
} public class Main {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
b.start();
Thread.sleep(2000);
ThreadC c = new ThreadC(b);
c.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

线程B在catch处打印
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Thread.join(Unknown Source)
at java.lang.Thread.join(Unknown Source)
at com.umgsai.thread.thread38.ThreadB.run(ThreadB.java:9)

此时程序不结束,a线程扔在运行。


方法join(long)的使用
public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println("MyThread开始,time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("MyThread结束,time = " + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
thread.join(2000);
System.out.println("main线程结束 time = " + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

MyThread开始,time = 1466909154102
main线程结束 time = 1466909156103
MyThread结束,time = 1466909159102

如果将main中的join(2000)改成Thread.sleep(2000),运行效果是一样的,main线程都是等待2秒。主要区别来自于这两个方法对同步的处理上。


方法join(long)与sleep(long)的区别

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。

方法join(long)源代码如下:

public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);//释放锁
now = System.currentTimeMillis() - base;
}
}
}

当执行wait(long)方法后,当前线程的锁被释放,其他线程就可以调用此线程中的同步方法了。

public class ThreadA extends Thread {
private ThreadB b;
public ThreadA(ThreadB b) {
super();
this.b = b;
}
@Override
public void run() {
try {
synchronized (b) {
b.start();
Thread.sleep(6000);////不释放b对象锁
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadB extends Thread {
@Override
public void run() {
try {
System.out.println("B begin time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("B end time = " + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
} synchronized public void bService(){
System.out.println("bService time = " + System.currentTimeMillis());
}
} public class ThreadC extends Thread {
private ThreadB threadB;
public ThreadC(ThreadB threadB) {
super();
this.threadB = threadB;
} @Override
public void run() {
threadB.bService();
}
} public class Main {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
Thread.sleep(1000);
ThreadC c = new ThreadC(b);
c.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

B begin time = 1466927915044
B end time = 1466927920045
bService time = 1466927921044

将线程A做如下修改:

public class ThreadA extends Thread {
private ThreadB b;
public ThreadA(ThreadB b) {
super();
this.b = b;
}
@Override
public void run() {
try {
synchronized (b) {
b.start();
b.join();//释放b对象锁
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

此时控制台打印结果如下:

B begin time = 1466927989797
bService time = 1466927990801
B end time = 1466927994798

方法join()后面的代码提前运行:出现意外
public class ThreadA extends Thread {
private ThreadB b;
public ThreadA(ThreadB b) {
super();
this.b = b;
}
@Override
public void run() {
try {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + " A begin time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " A end time = " + System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadB extends Thread {
@Override
synchronized public void run() {
try {
System.out.println(Thread.currentThread().getName() + " B begin time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + " B end time = " + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
} public class Main {
public static void main(String[] args) {
try {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
b.join(2000);
System.out.println("main end " + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}

P187 此节未完待续...

类ThreadLocal的使用

变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static变量。类ThreadLocal为每个线程绑定自己的值。

每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保存到其中。ThreadLocalMap针对每个thread保留一个entry,如果对应的thread不存在则会调用initValue。

方法get()与null
public class Run {
public static ThreadLocal t1 = new ThreadLocal<>();
public static void main(String[] args) {
if (t1.get() == null) {
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}

控制台打印结果如下:

从未放过值
我的值
我的值

类ThreadLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以放入ThreadLocal类中进行保存的。


验证线程变量的隔离性
public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
Tools.t1.set("ThreadA " + (i + 1));
System.out.println("ThreadA getValue=" + Tools.t1.get());
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
Tools.t1.set("ThreadB " + (i + 1));
System.out.println("ThreadB getValue=" + Tools.t1.get());
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class Main {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for (int i = 0; i < 100; i++) {
Tools.t1.set("Thread main " + (i + 1));
System.out.println("Thread main getValue=" + Tools.t1.get());
Thread.sleep(200);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

......
Thread main getValue=Thread main 99
ThreadB getValue=ThreadB 99
ThreadA getValue=ThreadA 99
Thread main getValue=Thread main 100
ThreadA getValue=ThreadA 100
ThreadB getValue=ThreadB 100

虽然三个线程都向t1对象中set()数据值,但每个线程还是能取出自己的数据。

第一次调用ThreadLocal类的get()方法返回值是null。可以设置默认值。

public class ThreadLocalExt extends ThreadLocal {
public static ThreadLocalExt t1 = new ThreadLocalExt(); @Override
protected Object initialValue() {
return "This is the default value";
} public static void main(String[] args) {
if (t1.get() == null) {
System.out.println("从未放过值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}

控制台打印结果如下:

This is the default value
This is the default value

子线程和父线程各自拥有自己的值

public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
} public class Tools {
public static ThreadLocalExt t1 = new ThreadLocalExt();
} public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("ThreadA取值:" + Tools.t1.get());
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class Main {
public static void main(String[] args) {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Main取值:" + Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

Main取值:1467118837063
Main取值:1467118837063
Main取值:1467118837063
Main取值:1467118837063
Main取值:1467118837063
ThreadA取值:1467118842566
ThreadA取值:1467118842566
ThreadA取值:1467118842566
ThreadA取值:1467118842566
ThreadA取值:1467118842566

类InheritableThreadLocal的使用

使用类InheritableThreadLocal可以在子线程中获得父线程继承下来的值。

public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
}
} public class Tools {
public static InheritableThreadLocalExt t1 = new InheritableThreadLocalExt();
} public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("ThreadA取值:" + Tools.t1.get());
Thread.sleep(100);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class Main {
public static void main(String[] args) {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Main取值:" + Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印结果如下:

Main取值:1467119046785
Main取值:1467119046785
Main取值:1467119046785
Main取值:1467119046785
Main取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
ThreadA取值:1467119046785
值继承再修改

将上面带做如下修改:

public class InheritableThreadLocalExt extends InheritableThreadLocal {
@Override
protected Object initialValue() {
return new Date().getTime();
} @Override
protected Object childValue(Object parentValue) {
return parentValue + "我是在子线程中加的~~~";
}
}

重新运行程序,控制台打印结果如下:

Main取值:1467119256537
Main取值:1467119256537
Main取值:1467119256537
Main取值:1467119256537
Main取值:1467119256537
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~
ThreadA取值:1467119256537我是在子线程中加的~~~

需要注意的是,如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。

Java多线程编程核心技术---线程间通信(二)的更多相关文章

  1. Java多线程编程核心技术---线程间通信(一)

    线程是操作系统中独立的个体,但这些个体如果不经过特殊处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一.线程间通信可以使系统之间的交互性更强大,在大大提高CPU利用率的同时还会使程序员对各 ...

  2. Java多线程编程&lpar;6&rpar;--线程间通信&lpar;下&rpar;

      因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式.   在实际的软件开发过程中,经常会碰到如下场景 ...

  3. Java多线程编程&lpar;5&rpar;--线程间通信

    一.等待与通知   某些情况下,程序要执行的操作需要满足一定的条件(下文统一将其称之为保护条件)才能执行.在单线程编程中,我们可以使用轮询的方式来实现,即频繁地判断是否满足保护条件,若不满足则继续判断 ...

  4. java多线程同步以及线程间通信详解&amp&semi;消费者生产者模式&amp&semi;死锁&amp&semi;Thread&period;join&lpar;&rpar;(多线程编程之二)

    本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...

  5. 《Java多线程编程核心技术》读后感(二)

    方法内的变量为线程安全 package Second; public class HasSelfPrivateNum { public void addI(String username) { try ...

  6. 《java多线程编程核心技术》不使用等待通知机制 实现线程间通信的 疑问分析

    不使用等待通知机制 实现线程间通信的 疑问分析 2018年04月03日 17:15:08       ayf 阅读数:33 编辑 <java多线程编程核心技术>一书第三章开头,有如下案例: ...

  7. Java多线程编程核心技术&lpar;三&rpar;多线程通信

    线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...

  8. Java多线程编程核心技术&lpar;二&rpar;对象及变量的并发访问

    本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...

  9. java多线程5:线程间的通信

    在多线程系统中,彼此之间的通信协作非常重要,下面来聊聊线程间通信的几种方式. wait/notify 想像一个场景,A.B两个线程操作一个共享List对象,A对List进行add操作,B线程等待Lis ...

随机推荐

  1. Spinner控件

    首先在XML文件中声明一个Spinner控件: <Spinner android:id="@+id/spinnerId" android:layout_width=&quot ...

  2. 【mybaits】Mybatis中模糊查询的各种写法

    工作中用到,写三种用法吧,第四种为大小写匹配查询 1. sql中字符串拼接 SELECT * FROM tableName WHERE name LIKE CONCAT(CONCAT('%', #{t ...

  3. 万事开头难,用HTML写的第一个界面,收获颇多

        很开心跟了叶老师学习和做项目,基础不好,前期他会帮你安排好学习路线和计划.前期没有项目做,叶老师先让我先学习jQuery,给我推荐了一些网站,叫我一边学习,一边写博客.其实很早就有想写博客的想 ...

  4. Oracle 表三种连接方式&lpar;sql优化)

    在查看sql执行计划时,我们会发现表的连接方式有多种,本文对表的连接方式进行介绍以便更好看懂执行计划和理解sql执行原理. 一.连接方式: 嵌套循环(Nested Loops (NL)) (散列)哈希 ...

  5. OpenGL—Android 开机动画源码分析一

    .1 Android开机动画实现方式目前实现Android开机动画的方式主要是逐帧动画和OpenGL动画. ?逐帧动画 逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的 ...

  6. KoaHub&period;js:使用ES6&sol;7特性开发Node&period;js框架(2)

    介绍   KoaHub.js -- 基于 Koa.js 平台的 Node.js web 快速开发框架.可以直接在项目里使用 ES6/7(Generator Function, Class, Async ...

  7. YARN集群的mapreduce测试(四)

    将手机用户使用流量的数据进行分组,排序: 测试准备: 首先同步时间,然后master先开启hdfs集群,再开启yarn集群:用jps查看: master上: 先有NameNode.SecondaryN ...

  8. Mvc&lowbar;ActionResult返回值

    //ViewResult 表示HTML的页面内容 //EmptyResult 表示空白的页面内容 //RedirectResult 表示定位到另外一个URL //JsonResult 表示可以运用到A ...

  9. Raspbian首次安装后无法使用SSH链接

    使用Putty连接树莓派,出现Network Error:Connection Refused 新版的Raspbian系统默认禁用了SSH. 解决方法:在/boot分区创建名为"ssh&qu ...

  10. Excel indirect引用其它xlsx文件内容作为下拉框

    效果如下图: 在第一个excel文件中有一个下拉框 这里面的选项,需要从另外一个Excel文件中读取内容,另外一个Excel文件如下: 实现的步骤如下: 1.新建一个Excel文件select.xls ...