根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点。对于代码示例会以Maven工程的形式分享到个人的GitHub上面。

  首先介绍一下这个系列的东西是什么,这个系列自己总结的东西比较多,主要参考的内容是《Java高并发编程详解》这本书,当然也结合了很多的其他书籍,以及网站。现在多元化的学习途径,导致的问题就是没有一个系列的教程来讲解关于某一个点的详细的内容。通过这种系列分享的形式将自己的能力进一步的提升。
  本系列主要有29篇的内容,从最基础的线程入门到高级的线程设计模式。通过详细的代码示例加上图表示例希望可以对大家的能力提升有一定的帮助。

线程的初步

  对于所有的操作系统来说几乎都是支持多个任务的同时执行的,在计算机中,每执行一个任务就存在一个进程,每一个进程中又有很多的线程在协调工作。例如下图

  我们看到在Windows10操作系统中默认就启动了很多的进程来支持我们计算机的运行。
  在Java中线程是程序运行的一个途径,在Java虚拟机中除了有多线程共享的方法区、堆内存等资源,每个线程中还有属于自己的程序计数器、虚拟机栈等资源。而对于Java虚拟机本省来说就是一个进程。在运行Java虚拟机的时候本省也会附带的创建很多的线程。可以通过在JDK的bin路径下面找到对应的虚拟机工具jvisualvm 通过JDK自带的虚拟机工具来查看,如下图

如何快速的创建一个线程呢?

  在工作中生活中经常会遇到很多的两件事情同时做的时候,例如你下班回家之后一边吃饭一边看电视。在单线程的情况下你的操作步骤是先吃饭,然后再去看电视。但是多线程的情况下就是一边看电视一边吃饭,可以说两个都不耽误。

模拟上面描述的操作
  1. 创建一个做事的接口
public interface DoSomething {//看电视操作public void watch() throws InterruptedException;//吃饭操作public void eat() throws InterruptedException;
}
  1. 继承接口编写看电视和吃饭的具体操作
public class WatchAndEat implements DoSomething{public void watch() throws InterruptedException {for (;;) {doWatch();sleep(1000);}}public void eat() throws InterruptedException {for (;;){doEat();sleep(1000);}}private  void doWatch() {System.out.println("我在看电视");}private void doEat(){System.out.println("我在吃饭");}
}
  1. 编写测试类
public class MainTest {/*** 首先描述一个单线程场景* 1.创建一个主线程入口* 2.创建一个看电视方法* 3.创建一个吃饭的方法** 4.测试结果*/public static void main(String[] args) throws InterruptedException {WatchAndEat watchAndEat = new WatchAndEat();watchAndEat.watch();watchAndEat.eat();}
}

结果如下,在单线程中并没有看到吃饭的操作。也就是说吃饭的操作是永远都不可能被执行到的。因为我们知道Java代码始终是从上到下依次执行的,也就是说只要看电视的方法得不到返回,那么就永远不会执行吃饭的方法。

怎么让着两个操作共同执行呢!

这里我们需要编写一个新的测试类。

public class MainThreadTest {public static void main(String[] args) throws InterruptedException {final WatchAndEat watchAndEat = new WatchAndEat();new Thread(()->{try {watchAndEat.eat();} catch (InterruptedException e) {e.printStackTrace();}}).start();watchAndEat.watch();}
}

在这里可以看到吃饭和看电视的方法同时交替输出。这也就是说可以边看电视边吃饭了。

如何观察者两个线程的工作情况?

  在java提供了很多的查看线程的的工具例如之前看到的jvisualvm、还有Jconsole等。
1.点击链接对应的进程

2.点击对应的线程进行查看操作

结果

主线程

名称: main
状态: TIMED_WAITING
总阻止数: 77, 总等待数: 204堆栈跟踪:
java.lang.Thread.sleep(Native Method)
com.example.charp01.demo01.WatchAndEat.watch(WatchAndEat.java:18)
com.example.charp01.demo01.MainThreadTest.main(MainThreadTest.java:20)

自己创建的线程

名称: Thread-0
状态: TIMED_WAITING
总阻止数: 86, 总等待数: 256堆栈跟踪:
java.lang.Thread.sleep(Native Method)
com.example.charp01.demo01.WatchAndEat.eat(WatchAndEat.java:26)
com.example.charp01.demo01.MainThreadTest.lambda$main$0(MainThreadTest.java:15)
com.example.charp01.demo01.MainThreadTest$$Lambda$1/990368553.run(Unknown Source)
java.lang.Thread.run(Thread.java:748)

从上面可以看出JVM确实创建了两个线程,一个是main主线程,一个是Thread-0(这个名字可以自己指定)的用户自定义线程。并且两者也执行的自己对应的方法,一个是watch,一个是eat。

线程的生命周期

  看完线程的运行情况,就要研究一下线程的生命周期情况了,在上面我们看到了两个线程的状态都是TIMED_WAITING,而在Java文档官方定义TIMED_WAITING状态为:“一个线程在一个特定的等待时间内等待另一个线程完成一个动作会在这个状态”,也就是说两个线程在相互等待完成,
提示,在调用以下方法的时候线程会进入到这个状态

1、Thread#sleep()
2、Object#wait() 并加了超时参数
3、Thread#join() 并加了超时参数
4、LockSupport#parkNanos()
5、LockSupport#parkUntil()

  下面就来研究一下线程的生命周期

根据Java API文档的分类可以将Java线程在在JVM中的状态分为六个

New状态
通过new 关键字创建一个线程的时候,也就是说线程被创建的时候
terminated状态
这个状态也就是说run方法执行完毕线程就结束了
runnable状态
对于Runable状态其实还可以细致的分为Runnable状态和running状态,所有的线程运行的时候都在这两个状态下抢占资源
waiting状态
当然如果线程调用了wait方法之后就会进入到waiting,这个状态的时候线程会释放自己获取到的锁。直到调用了notify方法或者是notifyall方法不然永远都不会得到CPU资源。
timed waiting状态
这个状态也是我们通过工具看到的线程的状态,在这个状态的时候线程不会放弃锁,会一直等待其他线程执行完成之后才会执行。
blocking状态
对于这个状态来说就是进入了阻塞状态,对于阻塞来说一般情况下回出现在IO请求的时候由于收不到输入或者输出不到对应的操作中会出现阻塞。例如A线程获取到的cpu进入到了同步方法中,但是由于B线程获得的一半的资源,只有等待A线程运行完成之后才能继续获取到另一半的资源。

  以上就是对于Java线程的生命周期的六个状态的简单的介绍,更为详细的介绍在后面的文章中会详细说明。

start方法剖析

  之前之前我们看到,在每个Thread执行的时候会调用一个strat方法,而这个Start方法和Run方法到底是什么样的关系呢!我们可以通过它的源码来分析这个问题

   public synchronized void start() {//首先线程调用Start方法的时候会判断线程的状态//也就是说线程不能被两次激活,也就是不能调用两次start方法。if (threadStatus != 0)throw new IllegalThreadStateException();//默认添加一个线程组group.add(this);//判断线程是否启动boolean started = false;try {//真正的执行逻辑在这里,调用了一个start0的方法//这个方法被native修饰,也就说是一个底层方法start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable then it will be passed up the call stack */}}}

通过上面的代码可以看到其实在java调用start方法的时候底层调用的是一个start0的方法,也就是说在线程执行的时候JVM就会调用该线程的run方法也就说其实在调用start方法的时候start0方法其实是调用run方法的。

模板设计模式在多线程中的使用。

  首先我们通过分析一个Runable接口源码来分析一下什么是模板设计模式

  public abstract void run();@Overridepublic void run() {if (target != null) {target.run();}}/* What will be run. */private Runnable target;

可以看到在其中Runnable准备了一个抽象的run方法,而在Thread中继承了Runnable接口重写了run方法,其中可以看到有一个target的变量,而这个变量正是我们的接口Runnable的对象,这个也就给了我们一个启示,在我们使用的时候可以定义一个模板方法。而在我们之后的实现中都是实现了不同功能的run方法,但是实际上这个模板类Runnable并没有发生变化,当我们需要进行程序结构的改变的时候只需要将父类的结构改变即可。子类只负责实现具体的逻辑就可以了。例如Runnable和Thread的关系其实就是一个类似于子类和父类的关系。只不过它是被JDK封装好的方法而已。这里我们根据这思路给出了下面的实例
1、首先创建一个模板类

public interface PrintMessage {abstract void print();
}

2、创建一个记者类

public class Repoter implements PrintMessage {public  void printNews() {print();}@Overridepublic void print() {System.out.println("show news");}
}

3、创建最终的测试类

public class TemplateMain {public static void main(String[] args) {Repoter repoter = new Repoter(){@Overridepublic void print() {System.out.println("这个是一个设计模板方法");}};repoter.printNews();}

由于这里是模仿Runable和Thread写的,所以在实现上没有太多的细节上的处理,但是实际上在我们实际开发中所使用的时候对于细节的处理还是比较到位的,所以说要从简单的例子中理解原理,然后应用到实际开发程序设计中。

Thread实际用例

通过继承Thread类实现多线程操作

public class TicketWindow extends Thread {private final String name;private static final int MAX = 50;private int index = 1;public TicketWindow(String name) {this.name = name;}@Overridepublic void run() {while (index<MAX){System.out.println(name+" 号柜台,出号 "+index++);}}public static void main(String[] args){TicketWindow ticketWindow1 = new TicketWindow("一");ticketWindow1.start();TicketWindow ticketWindow2 = new TicketWindow("二");ticketWindow2.start();TicketWindow ticketWindow3 = new TicketWindow("三");ticketWindow3.start();TicketWindow ticketWindow4 = new TicketWindow("四");ticketWindow4.start();}
}

通过继承Runnable接口实现多线程操作
  在之前的例子中提到了Runable的作用就是为子类实现提供了一个程序结构的模板,在Java中比较常见的实现多线程的方式就是通过Thread和Runable的方式实现。而这两者都是重写了run方法。也就是说无论从那个角度上讲其目的就是要将线程本身的控制权和业务逻辑分开。

这里介绍一个新的设计模式策略模式
例如
1、创建一个结果类

public class Result {}

2、创建要给处理类

public interface Handler<T>{T handle(Result rs);
}

3、创建实现具体的操作类

public class GetResult {public <T> T get(Handler<T> handler,String other){Result result = new Result();return handler.handle(result);//这个地方只负责获取结果}
}

在我们实际使用获取结果的时候,可以这样子做一个操作,我们可以在实现get方法的时候传入一个继承了Handler接口的实现了,至于具体的实现什么样的处理我们不用关心,可以根据需要创建自己的Handler处理器。然后传入到get方法中,这样实现了处理逻辑和方法执行的分离。使用这个方法可以处理任何结果集。

继承Runable接口实现买票操作

public class TicketRunable implements Runnable {private int index = 1;private final static int MAX = 50;@Overridepublic void run() {while (index<=50){System.out.println(Thread.currentThread()+" 的号码是 :"+index++);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {TicketRunable ticketRunable = new TicketRunable();Thread thread1 = new Thread(ticketRunable,"一");Thread thread2 = new Thread(ticketRunable,"二");Thread thread3 = new Thread(ticketRunable,"三");Thread thread4 = new Thread(ticketRunable,"四");thread1.start();thread2.start();thread3.start();thread4.start();}
}

总结

  1. 介绍了线程的初步的知识
  2. 了解了线程的生命周期
  3. 引入了两种设计模式,模板模式和策略模式
  4. 通过Thread和Runable分别实现了多线程模型

Java高并发编程详解系列-Java线程入门相关推荐

  1. Java高并发编程详解系列-7种单例模式

    引言 在之前的文章中从技术以及源代码的层面上分析了关于Java高并发的解决方式.这篇博客主要介绍关于单例设计模式.关于单例设计模式大家应该不会陌生,作为GoF23中设计模式中最为基础的设计模式,实现起 ...

  2. Java高并发编程详解系列-线程上下文设计模式及ThreadLocal详解

    导语   在之前的分享中提到过一个概念就是线程之间的通信,都知道在线程之间的通信是一件很消耗资源的事情.但是又不得不去做的一件事情.为了保证多线程线程安全就必须进行线程之间的通信,保证每个线程获取到的 ...

  3. Java高并发编程详解系列-Future设计模式

    导语   假设,在一个使用场景中有一个任务需要执行比较长的时间,通常需要等待任务执行结束之后或者是中途出错之后才能返回结果.在这个期间调用者只能等待,对于这个结果Future设计模式提供了一种凭据式的 ...

  4. Java高并发编程详解系列-类加载

    之前在写关于JVM的时候提到过类加载机制,类加载机制也是在Java面试中被经常问道的一个问题,在这篇博客中就来了解一下关于类加载的知识. 类加载   在JVM执行Java程序的时候实际上执行的编译好的 ...

  5. Java高并发编程详解系列-线程安全数据同步

    在多线程中最为复杂和最为重要的就是线程安全.多个线程访问同一个对象的时候会导致线程安全问题.通过加锁可以避免这种问题.但是在串行执行的过程中又不用考虑线程安全问题,而使用串行程序效率低没有办法将CPU ...

  6. Java高并发编程详解系列-内存模型

    volatile关键字介绍,要了解volatile需要了解的还有Java内存模型,以及CPU内存模型等知识.首先从CPU和Java内存模型开始说起. CPU Cache模型   在之前的时候,分享过一 ...

  7. Java高并发编程详解系列-不可变对象设计模式

    导语   在开发中涉及到的所有关于多线程的问题都离不开共享资源的存在.那么什么是共享资源,共享资源就是被多个线程共同访问的数据资源,而且每个线程都会引起它的变化.伴随共享资源而生的新问题就是线程安全, ...

  8. Java高并发编程详解系列-线程上下文类加载

    前面的分享中提到的最多的概念就是关于类加载器的概念,但是当我们查看Thread源码的时候会发现如下的两个方法,这两个方法就是获取或者设置线程的上下文类加载器的方法,那么为什么要设置这两个方法呢?这个就 ...

  9. Java高并发编程详解系列-ThreadAPI简单说明

    之前的两篇分享中,简单的从概念上和简单的操作上了解了Thread,这篇分享中主要是看看对于Thread的所有API的使用方式,通过解析源码的方式来了解关于Thread的细节的使用方式 引言   首先在 ...

最新文章

  1. 树和二叉树(四种遍历,建树)详解+二叉排序树(包含图像和相关习题)
  2. 机器学习在高德搜索建议中的应用优化实践
  3. 改变eclipse工程中代码的层次结构
  4. mysql最大连接数合理值_MySQL服务器最大连接数的合理设置
  5. 数组c语言与指针,浅析C语言数组与指针
  6. Shiro第四篇【Shiro与Spring整合、快速入门、Shiro过滤器、登陆认证】
  7. 1000道Python题库系列分享20(43道填空与判断题)
  8. C++ delete删除动态分配的内存
  9. 敏捷开发生态系统系列之二:敏捷生态系统-计划跟踪 I(跨职能团队-共同估算-每日立会-同行压力)...
  10. python writelines_详解详解Python中writelines()方法的使用
  11. keras遥感图像Unet语义分割(支持多波段多类)
  12. Win7连接蓝牙耳机(千月蓝牙激活码分享)无需破解软件
  13. 服务器主板支持nvme,给老主板刷上一个加入支持NVMe模块的改版“BIOS”
  14. 矩阵迹(trace), 行列式(determinate)
  15. 3DES实现加密算法
  16. 川普上台,VR游戏开发者也来恶搞蹭热度
  17. 对51job网页招聘信息的简单爬取
  18. C语言练习-还原算术表达式
  19. 科目三 流程 记录 LTS
  20. WebGIS开发入门

热门文章

  1. android cpu 压力测试,两个古董级压力测试工具 leakyapp.exe 和 cpustre.exe
  2. 如何使用 fstream 类进行文件的 I/O 处理
  3. 含有任意量词与存在量词的最值问题
  4. 我花了一年时间来学机器学习
  5. Zabbix探索:无法获取Windows主机CPU利用率、负载等问题处理
  6. iOS vuforia 学习钻研(一)
  7. 第四章:更多的bash shell命令
  8. OpenJudge 2810(1543) 完美立方 / Poj 1543 Perfect Cubes
  9. protel四层板及内电层分割入门
  10. Basic INFO: InstallShield中如何获取所调用Exe的返回值