一、多线程概述

一个进程中至少有一个线程,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

不能没一个问题都使用多线程,能使用单线程解决的问题就不要使用多线程解决。

使用多线程的弊端:

一旦开启了多个程序,电脑就会变卡,原因就是每个程序被CPU处理的几率变小了。最明显的例子就是如果在复制文件的时候如果开启了多个其他程序,则复制文件所需要的时间就会明显变长。

使用多核CPU可以解决一部分问题,它们的瓶颈就是内存。

在学习多线程之前我们写的Java程序也是多线程的,只不过除了主线程之外的其他线程是虚拟机隐式调用的。最明显的例子就是垃圾回收机制。

Object类中有一个方法名为finalize,该方法由垃圾回收器自动调用,不需要程序员维护。

需要注意的是,堆中产生垃圾之后并不会立刻被垃圾回收器回收,而是经过一段时间后才会被回收。这么做的原因就是如果立即回收,就会和主方法争夺CPU的执行权,影响程序执行效率,这在垃圾产生较为频繁的程序中尤其明显。

演示垃圾回收机制:

Demo类:

  1. public class Demo {
  2. public int data;
  3. public Demo(int data)
  4. {
  5. this.data=data;
  6. }
  7. public void finalize()
  8. {
  9. System.out.println(this.data);
  10. }
  11.  
  12. }

Main类:

  1. public class Main {
  2. public static void main(String args[])
  3. {
  4. new Demo(1);
  5. new Demo(2);
  6. new Demo(3);
  7. new Demo(4);
  8. System.gc();
  9. System.out.println("Hello,World!");
  10. }
  11. }

使用System.gc()方法可以告知垃圾回收器已经产生垃圾了,但是垃圾回收器可能不会立即启动。

我们使用多线程的目的就是让程序并发执行两段或者两段以上代码。

二、线程创建的方式一:继承Thread类

Thread类位于lang包下,而且不是抽象类,因此使用起来很方便。

创建线程的目的是为了开启一条执行路径,去运行指定的代码并和其他代码实现同时运行。

Thread类用于描述线程,而线程是需要任务的,这个任务由run方法体现。也就是说run方法就是封装自定义线程任务的函数。

使用Thread类创建线程的步骤:

1.定义一个类继承Thread类。

2.覆盖Thread类的run方法

3.创建Thread子类对象。

4.调用start方法启动线程。

Thread类中有两个方法:getName方法用于获取线程名,currentThread用于获取当前线程。前者是非静态类,后者是静态类。

代码演示:

  1. class Demo extends Thread
  2. {
  3. public void run()
  4. {
  5. for(int i=1;i<=3;i++)
  6. {
  7. System.out.println("Thread_Name="+Thread.currentThread().getName());
  8. }
  9. }
  10. }
  11. public class Main
  12. {
  13. public static void main(String args[])
  14. {
  15. new Demo().start();
  16. new Demo().start();
  17. for(int i=1;i<=3;i++)
  18. {
  19. System.out.println("Thread_Name="+Thread.currentThread().getName());
  20. }
  21. }
  22. }

运行结果:

应当注意,虽然main方法已经结束,但是仍然打印出了Thread-0,是因为每个线程在内存中都会开辟一个单独的栈,栈与栈之间互不影响。

三、线程的几种状态。

线程一共有五种状态:创建、运行、临时堵塞、冻结、消亡。

我们将线程的权限划分为CPU执行资格和CPU执行权。

各个状态之间的转换关系如下:

一个线程如果具备CPU执行资格,它可以在处理队列中排队等待CPU的执行。

一个线程如果具备CPU执行权,说明它正在被CPU执行,并且它一定具备CPU执行资格。

运行状态:具备CPU执行资格和CPU执行权。

冻结状态:既不具备CPU执行资格也不具备CPU执行权。

堵塞状态:具备CPU执行资格,但是不具备CPU执行权。

举例:

老师回答学生问题:

学生排成一队,挨个问老师问题,其中睡着了的同学自动从队伍中剔除出去(这类似于CPU不会关心处于冻结状态的线程),提出的问题正在被老师解答的学生具备着等待老师回答问题的权利和要求老师回答问题的权利(这类似于出于运行状态的线程同时具有CPU执行权和CPU执行资格),正处于队列中的其他同学则等待老师解答问题(这类似于出于堵塞状态的线程正在等待CPU执行权)。

使得线程出于冻结状态的方法有两种:sleep方法,这种方法时间一到,线程自动加入到堵塞队列中,等待CPU执行权;wait方法,这种方法使得线程彻底成为植物人,除非调用notify方法或者notifyAll方法,否则一直处于冻结状态。

四、创建线程的第二种方式:实现Runnable接口

Runnable接口中只有一个方法run,它和Thread类结合可以实现创建新线程的功能。

使用Runnable接口创建新线程的步骤是:

1.覆盖接口的run方法,将线程的任务代码封装到run方法中。
2.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread累的构造函数进行传递。
为什么?因为线程的任务都封装在Runnable接口的run方法中。
所以要在线程对象创建时就必须明确要运行的任务。
也就是说,如果你不传递,则线程对象调用自己的run(new Thread())方法,如果你传递,则线程对象调用传递的run(new Thread(target))方法。
3.调用线程对象的start方法开启线程。

使用Runnable接口的方法:

  1. class Demo implements Runnable
  2. {
  3. public void run()
  4. {
  5. for(int i=1;i<=3;i++)
  6. {
  7. System.out.println("Thread_Name="+Thread.currentThread().getName());
  8. }
  9. }
  10. }
  11. public class Main
  12. {
  13. public static void main(String args[])
  14. {
  15. new Thread(new Demo()).start();
  16. new Thread(new Demo()).start();
  17. for(int i=1;i<=3;i++)
  18. {
  19. System.out.println("Thread_Name="+Thread.currentThread().getName());
  20. }
  21. }
  22. }

运行结果:

对于Thread类的简单模拟:

  1. class Thread implements Runnable
  2. {
  3. private Runnable target=null;
  4. public Thread(){}
  5. public Thread(Runnable target)
  6. {
  7. this.target=target;
  8. }
  9. public void run()
  10. {
  11. if(this.target)
  12. {
  13. this.target.run();
  14. }
  15. }
  16. public void start()
  17. {
  18. this.run();
  19. }
  20. }

我们可以看到,如果给了实现了Runnable接口的对象,则调用接口中的run方法,否则,不处理,即调用Thread的空的run方法。

使用Runnable方式的好处是什么?

1.将线程的任务从线程的子类中分离出来,进行了单独的封装。
按照面向对象的思想将任务封装成对象。
2.避免了java单继承的局限性。

所以实际开发中大多使用实现Runnable接口的方式创建线程,而不是继承Thread类,这符合少用继承、多用组合原则。

五、综合案例:通过买票系统比较两种方式的异同

需求:将10张票分发给4个站点售出。

1.使用Thread的方式:

  1. class Ticket extends Thread
  2. {
  3. public static int sum=10;
  4. public void run()
  5. {
  6. while(true)
  7. {
  8. if(sum>0)
  9. {
  10. System.out.println(Thread.currentThread().getName()+":"+sum--);
  11. }
  12. }
  13. }
  14.  
  15. }
  16. public class Demo01
  17. {
  18. public static void main(String args[])
  19. {
  20. Ticket t0=new Ticket();
  21. Ticket t1=new Ticket();
  22. Ticket t2=new Ticket();
  23. Ticket t3=new Ticket();
  24. t0.start();
  25. t1.start();
  26. t2.start();
  27. t3.start();
  28.  
  29. }
  30. }

运行结果:

2.使用Runnable方式

  1. class Ticket implements Runnable
  2. {
  3. public int sum=10;
  4. public void run()
  5. {
  6. while(true)
  7. {
  8. if(sum>0)
  9. {
  10. System.out.println(Thread.currentThread().getName()+":"+sum--);
  11. }
  12. }
  13. }
  14. }
  15. public class Demo
  16. {
  17. public static void main(String args[])
  18. {
  19. Ticket t=new Ticket();
  20. new Thread(t).start();
  21. new Thread(t).start();
  22. new Thread(t).start();
  23. new Thread(t).start();
  24. }
  25. }

运行结果:

虽然两种方法都可以达到目的,但是后者并没有采用静态成员变量的方式,具有更大的灵活性。

【JAVA多线程概述】的更多相关文章

  1. Java多线程-Java多线程概述

    第一章 Java多线程概述 线程的启动 线程的暂停 线程的优先级 线程安全相关问题 1.1 进程与线程 进程:可以将运行在内存中的程序(如exe文件)理解为进程,进程是受操作系统管理的基本的运行单元. ...

  2. Java多线程概述

    /*多线程1.首先说进程,进程---就是正在进行的程序    每一个进程都有一个执行程序.该顺序是一个执行路径,或者叫一个控制单元 2.线程:就是进程中的一个独立的进程单元        线程在控制着 ...

  3. Java 多线程概述

    几乎所有的操作系统都支持同时运行多个任务,一 个任务通常就是一个程序,每个运行中的程序就是一个进程.当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 线程和进程 几乎所有的 ...

  4. 【程序员翻身计划】Java高性能编程第一章-Java多线程概述

    目标 重点: 线程安全的概念 线程通信的方式与应用 reactor线程模型 线程数量的优化 jdk常用命令 Netty框架的作用 难点 java运行的原理 同步关键字的原理 AQS的抽象 JUC的源码 ...

  5. Java多线程——<一>概述、定义任务

    一.概述 为什么使用线程?从c开始,任何一门高级语言的默认执行顺序是“按照编写的代码的顺序执行”,日常开发过程中写的业务逻辑,但凡不涉及并发的,都是让一个任务顺序执行以确保得到想要的结果.但是,当你的 ...

  6. Java:多线程概述与创建方式

    目录 Java:多线程概述与创建方式 进程和线程 并发与并行 多线程的优势 线程的创建和启动 继承Thread类 start()和run() 实现Runnable接口 实现Callable接口 创建方 ...

  7. Java多线程| 01 | 线程概述

    Java多线程| 01 | 线程概述 线程相关概念 进程与线程 进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位.可以把进程简单的理解 ...

  8. java多线程(一)-概述

    最近这段在看java多线程编程方面的东西.所以特写了几篇文章,来总结和回顾一下自己所学习到的相关知识.因为水平有限,文章中总结不全面甚至理解错误的地方,欢迎读者指点批评. 我们平时所接触到的程序,都是 ...

  9. JAVA 多线程和并发学习笔记(四)

    1. 多进程 实现并发最直接的方式是在操作系统级别使用进程,进程是运行在它自己的地址空间内的自包容的程序.多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程. 尽 ...

随机推荐

  1. Ubuntu固定ip和dns配置和查看

    1.查看dns: cat /etc/resolv.conf 2.Ubuntu固定ip sudo vim /etc/network/interfaces 修改如下部分: auto p3p1 iface ...

  2. centos 本地dns配置

    折腾了差不多两天,看了不少中文,英文文档.终于搞定,记录下心得.本文只讨论正向解析. 安装 ============= yum install bind 全局配置 ========= 由于只是做本地d ...

  3. Expression表达式树

    表达式树表示树状数据结构的代码,树状结构中的每个节点都是一个表达式,例如一个方法调用或类似 x < y 的二元运算 1.利用 Lambda 表达式创建表达式树 Expression<Fun ...

  4. JavaScript——事件模型

    DOM事件流: DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程可称为DOM事件 ...

  5. ODATA 云驱动 http://www.cdata.com/cloud/

    ODATA 云驱动   http://www.cdata.com/cloud/    目前支持:ORACLE.MS SQL . MYSQL. -------------- rssbus      ht ...

  6. ansible中tag的用法

    Tags 根据官方文档介绍: ansible允许通过自定义的关键字来给playbook中的资源打上标签,然后只运行标签标记的那个task任务. 例如,可能有个完成的OS配置,然后特定的步骤标记为“nt ...

  7. ffmpeg-20160617-git-bin.7z ffmpeg-20160626-git-bin.7z

    ESC 退出 0 进度条开关 1 屏幕原始大小 2 屏幕1/2大小 3 屏幕1/3大小 4 屏幕1/4大小 S 下一帧 [ -2秒 ] +2秒 ; -1秒 ' +1秒 下一个帧 -> -5秒 f ...

  8. Django函数——url()

    The url() function is passed four arguments, two required: regex and view, and two optional: kwargs, ...

  9. Effective C++ -----条款30:透彻了解inlining的里里外外

    将大多数inlining限制在小型.被频繁调用的函数身上.这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化 ...

  10. codeforces 496B. Secret Combination 解题报告

    题目链接:http://codeforces.com/problemset/problem/496/B 题目意思:给出 n 位数你,有两种操作:1.将每一位数字加一(当某一位 > 9 时只保存个 ...