GVKun编程网logo

Java基础16:Java多线程基础最全总结(java多线程基础知识)

3

此处将为大家介绍关于Java基础16:Java多线程基础最全总结的详细内容,并且为您解答有关java多线程基础知识的相关问题,此外,我们还将为您介绍关于Java基础17:JavaIO流总结、Java基

此处将为大家介绍关于Java基础16:Java多线程基础最全总结的详细内容,并且为您解答有关java多线程基础知识的相关问题,此外,我们还将为您介绍关于Java基础17:Java IO流总结、Java基础教程:多线程基础——线程的状态、Java基础教程:多线程基础——锁机制、Java基础教程:多线程基础——阻塞队列的有用信息。

本文目录一览:

Java基础16:Java多线程基础最全总结(java多线程基础知识)

Java基础16:Java多线程基础最全总结(java多线程基础知识)

Java基础16:Java多线程基础最全总结

Java中的线程

Java之父对线程的定义是:

线程是一个独立执行的调用序列,同一个进程的线程在同一时刻共享一些系统资源(比如文件句柄等)也能访问同一个进程所创建的对象资源(内存资源)。java.lang.Thread对象负责统计和控制这种行为。

每个程序都至少拥有一个线程-即作为Java虚拟机(JVM)启动参数运行在主类main方法的线程。在Java虚拟机初始化过程中也可能启动其他的后台线程。这种线程的数目和种类因JVM的实现而异。然而所有用户级线程都是显式被构造并在主线程或者是其他用户线程中被启动。

  本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。在这之前,首先让我们来了解下在操作系统中进程和线程的区别:

  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)

  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

  线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

  多进程是指操作系统能同时运行多个任务(程序)。

  多线程是指在同一程序中有多个顺序流在执行。

在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用

Java线程内存模型

下面的图大致介绍了Java线程的调用过程,每个线程使用一个独立的调用栈进行线程执行,栈中的数据不共享,堆区和方法区的数据是共享的。

构造方法和守护线程

构造方法
Thread类中不同的构造方法接受如下参数的不同组合:

一个Runnable对象,这种情况下,Thread.start方法将会调用对应Runnable对象的run方法。如果没有提供Runnable对象,那么就会立即得到一个Thread.run的默认实现。

一个作为线程标识名的String字符串,该标识在跟踪和调试过程中会非常有用,除此别无它用。

线程组(ThreadGroup),用来放置新创建的线程,如果提供的ThreadGroup不允许被访问,那么就会抛出一个SecurityException



Thread对象拥有一个守护(daemon)标识属性,这个属性无法在构造方法中被赋值,但是可以在线程启动之前设置该属性(通过setDaemon方法)

当程序中所有的非守护线程都已经终止,调用setDaemon方法可能会导致虚拟机粗暴的终止线程并退出。

isDaemon方法能够返回该属性的值。守护状态的作用非常有限,即使是后台线程在程序退出的时候也经常需要做一些清理工作。

(daemon的发音为”day-mon”,这是系统编程传统的遗留,系统守护进程是一个持续运行的进程,比如打印机队列管理,它总是在系统中运行。)

启动线程的方式和isAlive方法

启动线程 调用start方法会触发Thread实例以一个新的线程启动其run方法。新线程不会持有调用线程的任何同步锁。

当一个线程正常地运行结束或者抛出某种未检测的异常(比如,运行时异常(RuntimeException),错误(ERROR) 或者其子类)线程就会终止。

当线程终止之后,是不能被重新启动的。在同一个Thread上调用多次start方法会抛出InvalidThreadStateException异常。

如果线程已经启动但是还没有终止,那么调用isAlive方法就会返回true.即使线程由于某些原因处于阻塞(Blocked)状态该方法依然返回true。

如果线程已经被取消(cancelled),那么调用其isAlive在什么时候返回false就因各Java虚拟机的实现而异了。没有方法可以得知一个处于非活动状态的线程是否已经被启动过了。

优先级

Java的线程实现基本上都是内核级线程的实现,所以Java线程的具体执行还取决于操作系统的特性。

Java虚拟机为了实现跨平台(不同的硬件平台和各种操作系统)的特性,Java语言在线程调度与调度公平性上未作出任何的承诺,甚至都不会严格保证线程会被执行。但是Java线程却支持优先级的方法,这些方法会影响线程的调度:

每个线程都有一个优先级,分布在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间(分别为1和10) 默认情况下,新创建的线程都拥有和创建它的线程相同的优先级。main方法所关联的初始化线程拥有一个默认的优先级,这个优先级是Thread.NORM_PRIORITY (5).

线程的当前优先级可以通过getPriority方法获得。 线程的优先级可以通过setPriority方法来动态的修改,一个线程的最高优先级由其所在的线程组限定。

线程的控制方法

只有很少几个方法可以用于跨线程交流:

每个线程都有一个相关的Boolean类型的中断标识。在线程t上调用t.interrupt会将该线程的中断标识设为true,除非线程t正处于Object.wait,Thread.sleep,或者Thread.join,这些情况下interrupt调用会导致t上的这些操作抛出InterruptedException异常,但是t的中断标识会被设为false。

任何一个线程的中断状态都可以通过调用isInterrupted方法来得到。如果线程已经通过interrupt方法被中断,这个方法将会返回true。

但是如果调用了Thread.interrupted方法且中断标识还没有被重置,或者是线程处于wait,sleep,join过程中,调用isInterrupted方法将会抛出InterruptedException异常。

调用t.join()方法将会暂停执行调用线程,直到线程t执行完毕:当t.isAlive()方法返回false的时候调用t.join()将会直接返回(return)

另一个带参数毫秒(millisecond)的join方法在被调用时,如果线程没能够在指定的时间内完成,调用线程将重新得到控制权。

因为isAlive方法的实现原理,所以在一个还没有启动的线程上调用join方法是没有任何意义的。同样的,试图在一个还没有创建的线程上调用join方法也是不明智的。

起初,Thread类还支持一些另外一些控制方法:suspend,resume,stop以及destroy。这几个方法已经被声明过期。其中destroy方法从来没有被实现,估计以后也不会。而通过使用等待/唤醒机制增加suspend和resume方法在安全性和可靠性的效果有所欠缺

Thread的静态方法

静态方法
Thread类中的部分方法被设计为只适用于当前正在运行的线程(即调用Thread方法的线程)。为强调这点,这些方法都被声明为静态的。

Thread.currentThread方法会返回当前线程的引用,得到这个引用可以用来调用其他的非静态方法,比如Thread.currentThread().getPriority()会返回调用线程的优先级。

Thread.interrupted方法会清除当前线程的中断状态并返回前一个状态。(一个线程的中断状态是不允许被其他线程清除的)

Thread.sleep(long msecs)方法会使得当前线程暂停执行至少msecs毫秒。

Thread.yield方法纯粹只是建议Java虚拟机对其他已经处于就绪状态的线程(如果有的话)调度执行,而不是当前线程。最终Java虚拟机如何去实现这种行为就完全看其喜好了。

线程组

每一个线程都是一个线程组中的成员。默认情况下,新建线程和创建它的线程属于同一个线程组。线程组是以树状分布的。

当创建一个新的线程组,这个线程组成为当前线程组的子组。getThreadGroup方法会返回当前线程所属的线程组,对应地,ThreadGroup类也有方法可以得到哪些线程目前属于这个线程组,比如enumerate方法。

ThreadGroup类存在的一个目的是支持安全策略来动态的限制对该组的线程操作。比如对不属于同一组的线程调用interrupt是不合法的。

这是为避免某些问题(比如,一个applet线程尝试杀掉主屏幕的刷新线程)所采取的措施。ThreadGroup也可以为该组所有线程设置一个最大的线程优先级。

线程组往往不会直接在程序中被使用。在大多数的应用中,如果仅仅是为在程序中跟踪线程对象的分组,那么普通的集合类(比如java.util.Vector)应是更好的选择。

多线程的实现

public class 多线程实例 {

   //继承thread
   @Test
   public void test1() {
       class A extends Thread {
           @Override
           public void run() {
               System.out.println("A run");
          }
      }
       A a = new A();
       a.start();
  }

   //实现Runnable
   @Test
   public void test2() {
       class B implements Runnable {

           @Override
           public void run() {
               System.out.println("B run");
          }
      }
       B b = new B();
       //Runable实现类需要由Thread类包装后才能执行
       new Thread(b).start();
  }

   //有返回值的线程
   @Test
   public void test3() {
       Callable callable = new Callable() {
           int sum = 0;
           @Override
           public Object call() throws Exception {
               for (int i = 0;i < 5;i ++) {
                   sum += i;
              }
               return sum;
          }
      };
       //这里要用FutureTask,否则不能加入Thread构造方法
       FutureTask futureTask = new FutureTask(callable);
       new Thread(futureTask).start();
       try {
           System.out.println(futureTask.get());
      } catch (InterruptedException e) {
           e.printStackTrace();
      } catch (ExecutionException e) {
           e.printStackTrace();
      }
  }

   //线程池实现
   @Test
   public void test4() {
       ExecutorService executorService = Executors.newFixedThreadPool(5);
       //execute直接执行线程
       executorService.execute(new Thread());
       executorService.execute(new Runnable() {
           @Override
           public void run() {
               System.out.println("runnable");
          }
      });
       //submit提交有返回结果的任务,运行完后返回结果。
       Future future = executorService.submit(new Callable<String>() {
           @Override
           public String call() throws Exception {
               return "a";
          }
      });
       try {
           System.out.println(future.get());
      } catch (InterruptedException e) {
           e.printStackTrace();
      } catch (ExecutionException e) {
           e.printStackTrace();
      }

       ArrayList<String> list = new ArrayList<>();
       //有返回值的线程组将返回值存进集合
       for (int i = 0;i < 5;i ++ ) {
           int finalI = i;
           Future future1 = executorService.submit(new Callable<String>() {
               @Override
               public String call() throws Exception {
                   return "res" + finalI;
              }
          });
           try {
               list.add((String) future1.get());
          } catch (InterruptedException e) {
               e.printStackTrace();
          } catch (ExecutionException e) {
               e.printStackTrace();
          }
      }
       for (String s : list) {
           System.out.println(s);
      }
  }
}

线程状态转换

public class 线程的状态转换 {
//一开始线程是init状态,结束时是terminated状态
class t implements Runnable {
   private String name;
   public t(String name) {
       this.name = name;
  }
   @Override
   public void run() {
       System.out.println(name + "run");
  }
}

//测试join,父线程在子线程运行时进入waiting状态
@Test
public void test1() throws InterruptedException {
   Thread dad = new Thread(new Runnable() {
       Thread son = new Thread(new t("son"));
       @Override
       public void run() {
           System.out.println("dad init");
           son.start();
           try {
               //保证子线程运行完再运行父线程
               son.join();
               System.out.println("dad run");
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  });
   //调用start,线程进入runnable状态,等待系统调度
   dad.start();
   //在父线程中对子线程实例使用join,保证子线程在父线程之前执行完

}

//测试sleep
@Test
public void test2(){
   Thread t1 = new Thread(new Runnable() {
       @Override
       public void run() {
           System.out.println("t1 run");
           try {
               Thread.sleep(3000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  });

   //主线程休眠。进入time waiting状态
   try {
       Thread.sleep(3000);
  } catch (InterruptedException e) {
       e.printStackTrace();
  }
   t1.start();

}

//线程2进入blocked状态。
public static void main(String[] args) {
   test4();
   Thread.yield();//进入runnable状态
}

//测试blocked状态
public static void test4() {
   class A {
       //线程1获得实例锁以后线程2无法获得实例锁,所以进入blocked状态
       synchronized void run() {
           while (true) {
               System.out.println("run");
          }
      }
  }
   A a = new A();
   new Thread(new Runnable() {
       @Override
       public void run() {
           System.out.println("t1 get lock");
           a.run();
      }
  }).start();
   new Thread(new Runnable() {
       @Override
       public void run() {
           System.out.println("t2 get lock");
           a.run();
      }
  }).start();

}

//volatile保证线程可见性
volatile static int flag = 1;
//object作为锁对象,用于线程使用wait和notify方法
volatile static Object o = new Object();
//测试wait和notify
//wait后进入waiting状态,被notify进入blocked(阻塞等待锁释放)或者runnable状态(获取到锁)
public void test5() {
   new Thread(new Runnable() {
       @Override
       public void run() {
           //wait和notify只能在同步代码块内使用
           synchronized (o) {
               while (true) {
                   if (flag == 0) {
                       try {
                           Thread.sleep(2000);
                           System.out.println("thread1 wait");
                           //释放锁,线程挂起进入object的等待队列,后续代码运行
                           o.wait();
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                  }
                   System.out.println("thread1 run");
                   System.out.println("notify t2");
                   flag = 0;
                   //通知等待队列的一个线程获取锁
                   o.notify();
              }
          }
      }
  }).start();
   //解释同上
   new Thread(new Runnable() {
       @Override
       public void run() {
           while (true) {
               synchronized (o) {
                   if (flag == 1) {
                       try {
                           Thread.sleep(2000);
                           System.out.println("thread2 wait");
                           o.wait();
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                  }
                   System.out.println("thread2 run");
                   System.out.println("notify t1");
                   flag = 1;
                   o.notify();
              }
          }
      }
  }).start();
}

//输出结果是
//   thread1 run
//   notify t2
//   thread1 wait
//   thread2 run
//   notify t1
//   thread2 wait
//   thread1 run
//   notify t2
//不断循环

}

Java基础17:Java IO流总结

Java基础17:Java IO流总结

更多内容请关注微信公众号【Java技术江湖】

这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”资料“即可领取 3T 免费技术学习资源以及我我原创的程序员校招指南、Java学习指南等资源)

本文介绍了Java IO流的基本概念,使用方法,以及使用的注意事项等。帮助你更好地理解和使用Java的IO流。

具体代码在我的GitHub中可以找到

https://github.com/h2pl/MyTech

喜欢的话麻烦点一下星哈谢谢。

文章首发于我的个人博客:

https://h2pl.github.io/2018/05/04/javase17

更多关于Java后端学习的内容请到我的CSDN博客上查看:

https://blog.csdn.net/a724888

本文参考

并发编程网 – ifeve.com

IO概述

在这一小节,我会试着给出Java IO(java.io)包下所有类的概述。更具体地说,我会根据类的用途对类进行分组。这个分组将会使你在未来的工作中,进行类的用途判定时,或者是为某个特定用途选择类时变得更加容易。

输入和输出

术语“输入”和“输出”有时候会有一点让人疑惑。一个应用程序的输入往往是另外一个应用程序的输出

那么OutputStream流到底是一个输出到目的地的流呢,还是一个产生输出的流?InputStream流到底会不会输出它的数据给读取数据的程序呢?就我个人而言,在第一天学习Java IO的时候我就感觉到了一丝疑惑。

为了消除这个疑惑,我试着给输入和输出起一些不一样的别名,让它们从概念上与数据的来源和数据的流向相联系。

Java的IO包主要关注的是从原始数据源的读取以及输出原始数据到目标媒介。以下是最典型的数据源和目标媒介:

文件
管道
网络连接
内存缓存
System.in, System.out, System.error(注:Java标准输入、输出、错误输出)

下面这张图描绘了一个程序从数据源读取数据,然后将数据输出到其他媒介的原理:

image

在Java IO中,流是一个核心的概念。流从概念上来说是一个连续的数据流。你既可以从流中读取数据,也可以往流中写数据。流与数据源或者数据流向的媒介相关联。在Java IO中流既可以是字节流(以字节为单位进行读写),也可以是字符流(以字符为单位进行读写)。

类InputStream, OutputStream, Reader 和Writer 一个程序需要InputStream或者Reader从数据源读取数据,需要OutputStream或者Writer将数据写入到目标媒介中。以下的图说明了这一点:

image

InputStream和Reader与数据源相关联,OutputStream和writer与目标媒介相关联。

Java IO的用途和特征

Java IO中包含了许多InputStream、OutputStream、Reader、Writer的子类。这样设计的原因是让每一个类都负责不同的功能。这也就是为什么IO包中有这么多不同的类的缘故。各类用途汇总如下:

文件访问
网络访问
内存缓存访问
线程内部通信(管道)
缓冲
过滤
解析
读写文本 (Readers / Writers)
读写基本类型数据 (long, int etc.)
读写对象

当通读过Java IO类的源代码之后,我们很容易就能了解这些用途。这些用途或多或少让我们更加容易地理解,不同的类用于针对不同业务场景。

Java IO类概述表 已经讨论了数据源、目标媒介、输入、输出和各类不同用途的Java IO类,接下来是一张通过输入、输出、基于字节或者字符、以及其他比如缓冲、解析之类的特定用途划分的大部分Java IO类的表格。

image

Java IO类图

image

什么是Java IO流

Java IO流是既可以从中读取,也可以写入到其中的数据流。正如这个系列教程之前提到过的,流通常会与数据源、数据流向目的地相关联,比如文件、网络等等。

流和数组不一样,不能通过索引读写数据。在流中,你也不能像数组那样前后移动读取数据,除非使用RandomAccessFile 处理文件。流仅仅只是一个连续的数据流。

某些类似PushbackInputStream 流的实现允许你将数据重新推回到流中,以便重新读取。然而你只能把有限的数据推回流中,并且你不能像操作数组那样随意读取数据。流中的数据只能够顺序访问。

Java IO流通常是基于字节或者基于字符的。字节流通常以“stream”命名,比如InputStream和OutputStream。除了DataInputStream 和DataOutputStream 还能够读写int, long, float和double类型的值以外,其他流在一个操作时间内只能读取或者写入一个原始字节。

字符流通常以“Reader”或者“Writer”命名。字符流能够读写字符(比如Latin1或者Unicode字符)。可以浏览Java Readers and Writers获取更多关于字符流输入输出的信息。

InputStream

java.io.InputStream类是所有Java IO输入流的基类。如果你正在开发一个从流中读取数据的组件,请尝试用InputStream替代任何它的子类(比如FileInputStream)进行开发。这么做能够让你的代码兼容任何类型而非某种确定类型的输入流。

组合流

你可以将流整合起来以便实现更高级的输入和输出操作。比如,一次读取一个字节是很慢的,所以可以从磁盘中一次读取一大块数据,然后从读到的数据块中获取字节。为了实现缓冲,可以把InputStream包装到BufferedInputStream中。

代码示例 InputStream input = new BufferedInputStream(new FileInputStream("c:\data\input-file.txt"));

缓冲同样可以应用到OutputStream中。你可以实现将大块数据批量地写入到磁盘(或者相应的流)中,这个功能由BufferedOutputStream实现。

缓冲只是通过流整合实现的其中一个效果。你可以把InputStream包装到PushbackInputStream中,之后可以将读取过的数据推回到流中重新读取,在解析过程中有时候这样做很方便。或者,你可以将两个InputStream整合成一个SequenceInputStream。

将不同的流整合到一个链中,可以实现更多种高级操作。通过编写包装了标准流的类,可以实现你想要的效果和过滤器。

IO文件

在Java应用程序中,文件是一种常用的数据源或者存储数据的媒介。所以这一小节将会对Java中文件的使用做一个简短的概述。这篇文章不会对每一个技术细节都做出解释,而是会针对文件存取的方法提供给你一些必要的知识点。在之后的文章中,将会更加详细地描述这些方法或者类,包括方法示例等等。

通过Java IO读文件

如果你需要在不同端之间读取文件,你可以根据该文件是二进制文件还是文本文件来选择使用FileInputStream或者FileReader。

这两个类允许你从文件开始到文件末尾一次读取一个字节或者字符,或者将读取到的字节写入到字节数组或者字符数组。你不必一次性读取整个文件,相反你可以按顺序地读取文件中的字节和字符。

如果你需要跳跃式地读取文件其中的某些部分,可以使用RandomAccessFile。

通过Java IO写文件

如果你需要在不同端之间进行文件的写入,你可以根据你要写入的数据是二进制型数据还是字符型数据选用FileOutputStream或者FileWriter。

你可以一次写入一个字节或者字符到文件中,也可以直接写入一个字节数组或者字符数据。数据按照写入的顺序存储在文件当中。

通过Java IO随机存取文件

正如我所提到的,你可以通过RandomAccessFile对文件进行随机存取。

随机存取并不意味着你可以在真正随机的位置进行读写操作,它只是意味着你可以跳过文件中某些部分进行操作,并且支持同时读写,不要求特定的存取顺序。

这使得RandomAccessFile可以覆盖一个文件的某些部分、或者追加内容到它的末尾、或者删除它的某些内容,当然它也可以从文件的任何位置开始读取文件。

下面是具体例子:

@Test
    //文件流范例,打开一个文件的输入流,读取到字节数组,再写入另一个文件的输出流
    public void test1() {
        try {
            FileInputStream fileInputStream = new FileInputStream(new File("a.txt"));
            FileOutputStream fileOutputStream = new FileOutputStream(new File("b.txt"));
            byte []buffer = new byte[128];
            while (fileInputStream.read(buffer) != -1) {
                fileOutputStream.write(buffer);
            }
            //随机读写,通过mode参数来决定读或者写
            RandomAccessFile randomAccessFile = new RandomAccessFile(new File("c.txt"), "rw");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

字符流和字节流

Java IO的Reader和Writer除了基于字符之外,其他方面都与InputStream和OutputStream非常类似。他们被用于读写文本。InputStream和OutputStream是基于字节的,还记得吗?

Reader Reader类是Java IO中所有Reader的基类。子类包括BufferedReader,PushbackReader,InputStreamReader,StringReader和其他Reader。

Writer Writer类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。

这是一个简单的Java IO Reader的例子:

Reader reader = new FileReader("c:\\data\\myfile.txt");

int data = reader.read();

while(data != -1){

    char dataChar = (char) data;

    data = reader.read();

}

你通常会使用Reader的子类,而不会直接使用Reader。Reader的子类包括InputStreamReader,CharArrayReader,FileReader等等。可以查看Java IO概述浏览完整的Reader表格。

整合Reader与InputStream

一个Reader可以和一个InputStream相结合。如果你有一个InputStream输入流,并且想从其中读取字符,可以把这个InputStream包装到InputStreamReader中。把InputStream传递到InputStreamReader的构造函数中:

Reader reader = new InputStreamReader(inputStream);

在构造函数中可以指定解码方式。

Writer

Writer类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。这是一个Java IO Writer的例子:

Writer writer = new FileWriter("c:\\data\\file-output.txt"); 

writer.write("Hello World Writer"); 

writer.close();

同样,你最好使用Writer的子类,不需要直接使用Writer,因为子类的实现更加明确,更能表现你的意图。常用子类包括OutputStreamWriter,CharArrayWriter,FileWriter等。Writer的write(int c)方法,会将传入参数的低16位写入到Writer中,忽略高16位的数据。

整合Writer和OutputStream

与Reader和InputStream类似,一个Writer可以和一个OutputStream相结合。把OutputStream包装到OutputStreamWriter中,所有写入到OutputStreamWriter的字符都将会传递给OutputStream。这是一个OutputStreamWriter的例子:

Writer writer = new OutputStreamWriter(outputStream);

IO管道

Java IO中的管道为运行在同一个JVM中的两个线程提供了通信的能力。所以管道也可以作为数据源以及目标媒介。

你不能利用管道与不同的JVM中的线程通信(不同的进程)。在概念上,Java的管道不同于Unix/Linux系统中的管道。在Unix/Linux中,运行在不同地址空间的两个进程可以通过管道通信。在Java中,通信的双方应该是运行在同一进程中的不同线程。

通过Java IO创建管道

可以通过Java IO中的PipedOutputStream和PipedInputStream创建管道。一个PipedInputStream流应该和一个PipedOutputStream流相关联。

一个线程通过PipedOutputStream写入的数据可以被另一个线程通过相关联的PipedInputStream读取出来。

Java IO管道示例 这是一个如何将PipedInputStream和PipedOutputStream关联起来的简单例子:

//使用管道来完成两个线程间的数据点对点传递
    @Test
    public void test2() throws IOException {
        PipedInputStream pipedInputStream = new PipedInputStream();
        PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    pipedOutputStream.write("hello input".getBytes());
                    pipedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte []arr = new byte[128];
                    while (pipedInputStream.read(arr) != -1) {
                        System.out.println(Arrays.toString(arr));
                    }
                    pipedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

管道和线程 请记得,当使用两个相关联的管道流时,务必将它们分配给不同的线程。read()方法和write()方法调用时会导致流阻塞,这意味着如果你尝试在一个线程中同时进行读和写,可能会导致线程死锁。

管道的替代 除了管道之外,一个JVM中不同线程之间还有许多通信的方式。实际上,线程在大多数情况下会传递完整的对象信息而非原始的字节数据。但是,如果你需要在线程之间传递字节数据,Java IO的管道是一个不错的选择。

Java IO:网络

Java中网络的内容或多或少的超出了Java IO的范畴。关于Java网络更多的是在我的Java网络教程中探讨。但是既然网络是一个常见的数据来源以及数据流目的地,并且因为你使用Java IO的API通过网络连接进行通信,所以本文将简要的涉及网络应用。

当两个进程之间建立了网络连接之后,他们通信的方式如同操作文件一样:利用InputStream读取数据,利用OutputStream写入数据。换句话来说,Java网络API用来在不同进程之间建立网络连接,而Java IO则用来在建立了连接之后的进程之间交换数据。

基本上意味着如果你有一份能够对文件进行写入某些数据的代码,那么这些数据也可以很容易地写入到网络连接中去。你所需要做的仅仅只是在代码中利用OutputStream替代FileOutputStream进行数据的写入。因为FileOutputStream是OuputStream的子类,所以这么做并没有什么问题。

//从网络中读取字节流也可以直接使用OutputStream
public void test3() {
    //读取网络进程的输出流
    OutputStream outputStream = new OutputStream() {
        @Override
        public void write(int b) throws IOException {
        }
    };
}
public void process(OutputStream ouput) throws IOException {
    //处理网络信息
    //do something with the OutputStream
}

字节和字符数组

从InputStream或者Reader中读入数组

从OutputStream或者Writer中写数组

在java中常用字节和字符数组在应用中临时存储数据。而这些数组又是通常的数据读取来源或者写入目的地。如果你需要在程序运行时需要大量读取文件里的内容,那么你也可以把一个文件加载到数组中。

前面的例子中,字符数组或字节数组是用来缓存数据的临时存储空间,不过它们同时也可以作为数据来源或者写入目的地。 举个例子:

//字符数组和字节数组在io过程中的作用
    public void test4() {
        //arr和brr分别作为数据源
        char []arr = {''a'',''c'',''d''};
        CharArrayReader charArrayReader = new CharArrayReader(arr);
        byte []brr = {1,2,3,4,5};
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(brr);
    }

System.in, System.out, System.err

System.in, System.out, System.err这3个流同样是常见的数据来源和数据流目的地。使用最多的可能是在控制台程序里利用System.out将输出打印到控制台上。

JVM启动的时候通过Java运行时初始化这3个流,所以你不需要初始化它们(尽管你可以在运行时替换掉它们)。

System.in
System.in是一个典型的连接控制台程序和键盘输入的InputStream流。通常当数据通过命令行参数或者配置文件传递给命令行Java程序的时候,System.in并不是很常用。图形界面程序通过界面传递参数给程序,这是一块单独的Java IO输入机制。

System.out
System.out是一个PrintStream流。System.out一般会把你写到其中的数据输出到控制台上。System.out通常仅用在类似命令行工具的控制台程序上。System.out也经常用于打印程序的调试信息(尽管它可能并不是获取程序调试信息的最佳方式)。

System.err
System.err是一个PrintStream流。System.err与System.out的运行方式类似,但它更多的是用于打印错误文本。一些类似Eclipse的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式通过System.err输出到控制台上。

System.out和System.err的简单例子: 这是一个System.out和System.err结合使用的简单示例:

 //测试System.in, System.out, System.err    
    public static void main(String[] args) {
        int in = new Scanner(System.in).nextInt();
        System.out.println(in);
        System.out.println("out");
        System.err.println("err");
        //输入10,结果是
//        err(红色)
//        10
//        out
    }

字符流的Buffered和Filter

BufferedReader能为字符输入流提供缓冲区,可以提高许多IO处理的速度。你可以一次读取一大块的数据,而不需要每次从网络或者磁盘中一次读取一个字节。特别是在访问大量磁盘数据时,缓冲通常会让IO快上许多。

BufferedReader和BufferedInputStream的主要区别在于,BufferedReader操作字符,而BufferedInputStream操作原始字节。只需要把Reader包装到BufferedReader中,就可以为Reader添加缓冲区(译者注:默认缓冲区大小为8192字节,即8KB)。代码如下:

Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"));

你也可以通过传递构造函数的第二个参数,指定缓冲区大小,代码如下:

Reader input = new BufferedReader(new FileReader("c:\\data\\input-file.txt"), 8 * 1024);

这个例子设置了8KB的缓冲区。最好把缓冲区大小设置成1024字节的整数倍,这样能更高效地利用内置缓冲区的磁盘。

除了能够为输入流提供缓冲区以外,其余方面BufferedReader基本与Reader类似。BufferedReader还有一个额外readLine()方法,可以方便地一次性读取一整行字符。

BufferedWriter

与BufferedReader类似,BufferedWriter可以为输出流提供缓冲区。可以构造一个使用默认大小缓冲区的BufferedWriter(译者注:默认缓冲区大小8 * 1024B),代码如下:

Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"));

也可以手动设置缓冲区大小,代码如下:

Writer writer = new BufferedWriter(new FileWriter("c:\\data\\output-file.txt"), 8 * 1024);

为了更好地使用内置缓冲区的磁盘,同样建议把缓冲区大小设置成1024的整数倍。除了能够为输出流提供缓冲区以外,其余方面BufferedWriter基本与Writer类似。类似地,BufferedWriter也提供了writeLine()方法,能够把一行字符写入到底层的字符输出流中。

值得注意是,你需要手动flush()方法确保写入到此输出流的数据真正写入到磁盘或者网络中。

FilterReader

与FilterInputStream类似,FilterReader是实现自定义过滤输入字符流的基类,基本上它仅仅只是简单覆盖了Reader中的所有方法。

就我自己而言,我没发现这个类明显的用途。除了构造函数取一个Reader变量作为参数之外,我没看到FilterReader任何对Reader新增或者修改的地方。如果你选择继承FilterReader实现自定义的类,同样也可以直接继承自Reader从而避免额外的类层级结构。

--Posted from Rpc

Java基础教程:多线程基础——线程的状态

Java基础教程:多线程基础——线程的状态

Java基础教程:多线程基础——线程的状态

线程的状态

  在Java中,线程有6种状态,分别为:

  • 初始:NEW
  • 运行:RUNNABLE
  • 阻塞:BLOCKED
  • 等待:WAITING
  • 超时等待:TIMED_WAIT
  • 终止:TERMINAL

  这六种状态分别对应于Thread.State中的枚举类型。可以用下面这张图来解释一下Java中的线程的状态转换

  

初始态

  初始态表示一个线程刚被初始化,即new Thread()

Thread thread = new Thread();
System.out.println(thread.getState());

//Output:NEW

  这个没什么好说的,也是最简单。

运行态

  当调用一个Thread对象的start方法后,该线程进入运行态。运行态的名字是很有迷惑性的,其实运行态再细分还可以分为两个子状态:

  • Ready:调用start后,该线程放入可运行线程池中,等待被调度,获得CPU运行权
  • Running:获得CPU时间片后变为运行中状态

  也即是就绪和运行中都是运行态,一定要谨记!

阻塞态

  阻塞态可能理解就要上一个台阶了,阻塞态表示一个线程因为等待临界区的锁而被阻塞产生的状态。我们举一个例子,我们有一个上锁的单人厕所,有两个人同时要求上厕所,但是有一个人抢先进去了,然后反锁了,第二个就在厕所门口等着,他就属于阻塞态。此时又来了第三个人、第四个等等,他们都属于阻塞态,只有第一个出来,下一个进去的才能摆脱这个状态!

  话不多说,我们写代码来实现这情形,在这里用了Lambda表达式来简写线程的run方法。

public class BlockedDemo {
    public static void main(String[] args) throws InterruptedException {
        Toilet room = new Toilet();
        Thread a = new Thread(() -> room.use());
        a.start();
        Thread.sleep(1000);
        Thread b = new Thread(() -> room.use());
        b.start();
        Thread.sleep(2000);
        System.out.println(b.getState());
    }

}

class Toilet{
    synchronized void use(){
        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  此时打印出来的状态就是:BLOCKED状态!

等待态与超时等待态

  当我们拿到锁执行方法的时候,我们可能由于某些原因,还是以厕所为例,发现没带纸,我们需要出去,这时肯定不能霸占厕所,所以我们要主动出去,遂调用wait方法后,让出锁,我们就处于等待状态。如果送纸的人来了,再把我们唤醒notify。

public class WaitingDemo {
    public static void main(String[] args) throws InterruptedException {
        WaitToilet room = new WaitToilet();
        Thread a = new Thread(() -> room.use());
        a.start();
        Thread.sleep(100);
        System.out.println(a.getState());
    }

}

class WaitToilet{
    synchronized void use(){
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  你可以看到wait(long time)有一个参数指定时间,表示我们先出去等time时间,然后再排队如厕,此时我们就处于超时等待状态。如果送纸的人迟迟不来,也不等了,

我们再来简单总结一下:

  • 一个线程A在拿到锁但不满足执行条件的时候,需要另一个线程B去满足这个条件,那么线程A就会释放锁并处于waiting的状态,等线程B执行完再执行。
  • waiting状态的好处是:此状态的线程不再活动,不再参与调度,因此不会浪费 CPU 资源,也不会去竞争锁了,相比暴力的blocking状态,要优雅很多
  • 如果设置等待时间的话,超过时间,会自动被唤醒

终止状态

  线程所有逻辑执行完了,就处于终止状态。这个和初始态一样都是好理解的!

 

Java基础教程:多线程基础——锁机制

Java基础教程:多线程基础——锁机制

Java基础教程:多线程基础——锁机制

显示锁

ReentrantLock

  ReentrantLock是一种标准的互斥锁,每次只允许一个线程持有ReentrantLock。

  使用ReentrantLock来保护对象状态:

Lock lock = new ReentrantLock();
       lock.lock();
       try {
           //更新对象状态
           //捕获异常,并在必要时恢复不变性条件
       }finally {
           lock.unlock();
       }

  如果没有finally来释放Lock,相当于启动了一个定时炸弹。

 

ReentrantReadWriteLock

  互斥锁是一种保守的枷锁策略,它虽然避免了“写/写”冲突,“写/读”冲突,但是同样也避免了“读/读”操作。在许多情况下,数据结构上的操作都是“读操作”。若放宽加锁需求,允许多个执行读操作的线程同时访问数据结构,那么将提高程序的性能

  我们引入读写锁,它允许一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行。也就是说当写操作进行时,所有读操作都进入阻塞状态,只有写操作完成后,读操作才能获取到最新状态,避免了脏读的发生

  在如下的代码中,我们使用读写锁封装一个Map,来使其线程安全。

public class ReadWriteMap<K,V> {
    private final Map<K,V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();

    public ReadWriteMap(Map<K,V> map){
        this.map = map;
    }

    public V put(K key,V value){
        w.lock();
        try {
            return map.put(key,value);
        } finally {
            w.unlock();
        }
    }

    public V get(Object key){
        r.lock();
        try {
            return map.get(key);
        } finally {
            r.unlock();
        }
    }
}

  

 

 

使用Condition实现等待/通知

  关键字Synchronized与wait、notify/All方法相结合可以实现等待/通知模式,类ReentrantLock同样可以实现相同的功能,但是需要借助Condition

  Condition类是JDK5出现的新技术,可以实现 多路通知的功能,也就是在一个Lock对象里可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

  必须在Condition.await()方法调用之前调用Lock.lock()方法获得同步监视器

MyService类

public class MyService {
    private Lock lock  = new ReentrantLock();
    public Condition condition = lock.newCondition();

    public void await()
    {
        try {
            lock.lock();
            System.out.println("await的等待时间是"+System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signal()
    {
        try {
            lock.lock();
            System.out.println("signal的等待时间是"+System.currentTimeMillis());
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}  

ThreadA类

public class ThreadA extends Thread {
    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.await();
    }
}  

Main类

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyService myService = new MyService();
        ThreadA threadA = new ThreadA(myService);
        threadA.start();
        Thread.sleep(3000);
        myService.signal();
    }
}  

多个Condition实现通知部分线程

MyService类

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MySevice {
    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();

    public void awaitA()
    {
        try {
            lock.lock();
            System.out.println("awaitA的start时间是"+System.currentTimeMillis());
            conditionA.await();
            System.out.println("awaitA的end时间是"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void awaitB()
    {
        try {
            lock.lock();
            System.out.println("awaitB的start时间是"+System.currentTimeMillis());
            conditionB.await();
            System.out.println("awaitB的end时间是"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signalA()
    {
        try {
            lock.lock();
            System.out.println("signalA的等待时间是"+System.currentTimeMillis());
            conditionA.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
    public void signalB()
    {
        try {
            lock.lock();
            System.out.println("signalB的等待时间是"+System.currentTimeMillis());
            conditionB.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}  

ThreadA类

public class ThreadA extends Thread {
    private MySevice myService;

    public ThreadA(MySevice myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.awaitA();
    }
}

ThreadB类

public class ThreadB extends Thread {
    private MySevice myService;

    public ThreadB(MySevice myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.awaitB();
    }
}

Main类

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MySevice mySevice =new MySevice();
        ThreadA threadA = new ThreadA(mySevice);
        ThreadB threadB = new ThreadB(mySevice);
        threadA.start();
        threadB.start();
        Thread.sleep(3000);
        mySevice.signalA();
    }
}

  

 

 

Java基础教程:多线程基础——阻塞队列

Java基础教程:多线程基础——阻塞队列

Java基础教程:多线程基础——阻塞队列

快速开始

引入问题

  生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据

模拟情景

  这里我们实现如下的情况的生产-消费模型:

生产者不断交替地生产两组数据“姓名--1-->内容--1”,“姓名--2-->内容--2”,这里的“姓名--1”和“姓名--2”模拟为数据的名称,“内容--1 ”和“内容--2 ”模拟为数据的内容

由于本程序中牵扯到线程运行的不确定性,因此可能会出现以下问题:

  1.假设生产者线程刚向数据存储空间添加了数据的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程把信息的名称和上一个信息的内容联系到了一起;

  2.生产者生产了若干条数据,消费者才可以取数据,或者是,消费者取完一次数据后,还没等生产者放入新的数据,又重复取出了已取过的数据。

通过分析我们可知:

  第一个问题可以通过同步来解决,第二个问题就需要用到线程通信。生产者线程放入数据后,通知消费者线程取出数据,消费者线程取出数据后,通知生产者线程生产数据,这里用wait\notigy机制来实现。

Java代码

定义信息类

package thread;

public class Info {
    private String name = "name";
    private String content = "content";

    //设置标志位,用来进行线程通信
    private boolean flag =true;
    /**
     * 设置消息,此处用到线程同步
     * @param name
     * @param content
     */
    public synchronized void set(String name,String content)
    {
        while (!flag)
        {
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.name=name; //设置名称

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.content=content; //设置内容

        flag =false; //设置标志位,表示现在生产停止,可以取走!
    }


    public synchronized void get()
    {
        while (flag)
        {
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(name +
                " --> " + content) ;
        flag  = true ;  // 改变标志位,表示可以生产
        super.notify();
    }

}  

定义生产者

public class Producer implements Runnable {

    private Info info=null;
    public Producer(Info info)
    {
        this.info=info;
    }
    @Override
    public void run() {
        boolean flag = true ;   // 定义标记位
        for(int i=0;i<10;i++){
            if(flag){
                this.info.set("姓名--1","内容--1") ;    // 设置名称
                flag = false ;
            }else{
                this.info.set("姓名--2","内容--2") ;    // 设置名称
                flag = true ;
            }
        }
    }
}

定义消费者

public class Consumer implements Runnable {
    private Info info = null ;
    public Consumer(Info info){
        this.info = info ;
    }
    public void run(){
        for(int i=0;i<10;i++){
            this.info.get() ;
        }
    }


    public static void main(String[] args) {
        Info info = new Info(); // 实例化Info对象
        Producer pro = new Producer(info) ; // 生产者
        Consumer con = new Consumer(info) ; // 消费者
        new Thread(pro).start() ;
        //启动了生产者线程后,再启动消费者线程
        try{
            Thread.sleep(500) ;
        }catch(InterruptedException e){
            e.printStackTrace() ;
        }
        new Thread(con).start() ;
    }
}

 使用阻塞队列来实现相同功能

引入BlockingQueue

  任何有效的生产者-消费者问题解决方案都是通过控制生产者put()方法(生产资源)和消费者take()方法(消费资源)的调用来实现的,一旦你实现了对方法的阻塞控制,那么你将解决该问题。Java通过BlockingQueue提供了开箱即用的支持来控制这些方法的调用(一个线程创建资源,另一个消费资源)。java.util.concurrent包下的BlockingQueue接口是一个线程安全的可用于存取对象的队列

  

  BlockingQueue是一种数据结构支持一个线程往里存资源,另一个线程从里取资源。这正是解决生产者消费者问题所需要的,那么让我们开始解决该问题吧。

Java代码

消息类

public class InfoPlus {
    private String name = "name";
    private String content = "content";

    public InfoPlus(String name, String content) {
        this.name = name;
        this.content = content;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "InfoPlus{" +
                "name=''" + name + ''\'''' +
                ", content=''" + content + ''\'''' +
                ''}'';
    }
}

生产者

import java.util.concurrent.BlockingQueue;

public class ProducerPlus implements Runnable {
    private BlockingQueue<InfoPlus> queue;

    public ProducerPlus(BlockingQueue<InfoPlus> queue) {
        this.queue = queue;
    }


    @Override
    public void run() {
        for (int i=0;i<10;i++)
        {
            try {
                Thread.sleep(1000);
                queue.put(new InfoPlus("name"+i,"content"+i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class ConsumerPlus implements Runnable{
    private BlockingQueue<InfoPlus> queue;

    public ConsumerPlus(BlockingQueue<InfoPlus> queue) {
        this.queue = queue;
    }

    public void run() {
        while (true) {
            try {
                System.out.println(this.queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<InfoPlus> blockingQueue = new LinkedBlockingDeque<>();
        ProducerPlus producerPlus = new ProducerPlus(blockingQueue);
        ConsumerPlus consumerPlus = new ConsumerPlus(blockingQueue);
        ConsumerPlus consumerPlus1 = new ConsumerPlus(blockingQueue);
        new Thread(producerPlus).start();
        new Thread(consumerPlus).start();
        new Thread(consumerPlus1).start();

    }
}

 

关于Java基础16:Java多线程基础最全总结java多线程基础知识的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Java基础17:Java IO流总结、Java基础教程:多线程基础——线程的状态、Java基础教程:多线程基础——锁机制、Java基础教程:多线程基础——阻塞队列的相关知识,请在本站寻找。

本文标签: