多线程引入

1、进程和线程介绍

  多线程技术主要是解决的是让多个程序能够同时执行起来。可以提高程序的执行效率。

 1.1、进程

  正在执行的程序。当我们运行系统上安装的应用程序之后,这个应用程序就会被加载内存中,并且在内存中开始运行。

  这个应用程序被加载到内存中,它需要在内存中分配内存空间。这时就需要分配内存空间,而这个内存空间就是一个进程。这个空间专门负责当前这个应用程序的执行。

  进程其实是专门负责当前这个应用程序的内存空间的分配和管理,以及程序的执行过程的任务调度。

  进程它们是独立的运行单元,在内存中是不会互相影响。

 1.2、线程

  一个应用程序肯定是由多部分代码组成。而这些代码在当前这个进程中要执行。

  当一个应用程序被启动之后,这个应用程序所占的内存空间又会被划分成多个区域,多个区域来负责运行当前进程中不同功能。

  而负责运行这个功能的那些单独执行空间(执行路径)就称为每个线程。

  一个进程中最少要有一个线程。线程才是真正执行应用程序的执行空间。而现在大部分的程序都是多线程程序。这样可以保证当前程序的执行效率。

2、cpu执行线程简介

  cpu(中央处理器)它是整个电脑的大脑,所有数据的运行都由它完成。它是负责计算和调度正常电脑系统的运行以及其他程序的运行。

  cpu执行程序:

    真正cpu执行程序,在某个时刻某个时间点上,cpu只能执行一个线程。而不是同时在执行多个程序。cpu在执行应用程序的时候,它是以时间碎片为概念在多个程序中的多个线程之间来回切换造成。并且cpu的切换速度非常快,导致我们感觉好像是多个程序在同时运行。

  是不是在程序开的线程越多越好?

    不是,在cpu的有效的处理能力范围内,多开线程,可以提高效率。

3、主线程介绍

  在我们书写任何程序中都一个启动的线程,这个线程主线程。

  在Java中我们书写的程序主要从main方法开始运行。然后当main方法执行完之后这个程序就结束了。

  

  DemoA2.java——多线程程序引入,主要介绍主线程:

public class DemoA2
{

    public static void show()
    {
        for( int i=0;i<20;i++ )
        {
            System.out.println("show i="+i);
        }
    }

    public static void main(String[] args)
    {
        show();

        for( int i=0;i<20;i++ )
        {
            System.out.println("main i="+i);
        }

        System.out.println("main over....");
    }
}
/*
    当我们在dos窗口中输入了java DemoA2回车之后,
    会启动JVM,这时就会在JVM运行进程中划分出一片区域用来运行
    main方法中的代码。如果main方法中调用了其他的方法,其他的方法也会在当前分配的区域中运行。
    这时这些执行的区域,或者称为执行的路径都称为主线程所在的路径。
*/

4、线程的实现方式一

  线程也是属于一类事物,Java对这个事物就会有自己的描述。我们需要在api中找Java是使用哪个类来描述线程这类事物。

  Java中使用Thread这个类来描述线程:这个类在java.lang包中。

    Thread类是专门用来描述线程这类事物。它可以负责在程序执行过程单独的开辟出一片内存区域用来运行当前单独分配的任务。

  创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。

 4.1、实现线程的第一种方式:

    1、书写一个类,必须继承Thread

    2、重写Thread类中的run方法

    3、创建子类的对象

    4、通过子类对象开启线程

    ThreadDemo.java­——创建线程的第一种方式 继承Thread  ★★★★★

//定义一个类继承Thread
class ThreadDemo extends Thread
{
    //重写Thread类中的run方法
    public void run()
    {
        for( int i=0;i<20;i++ )
        {
            System.out.println("run i="+i);
        }
    }

    //程序都是从main方法开始运行
    public static void main(String[] args)
    {
        ThreadDemo d = new ThreadDemo();
        //启动这个线程
        d.start();
        /*
            创建的子类对象之后调用start方法
            使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
        */

        for( int i=0;i<20;i++ )
        {
            System.out.println("main i="+i);
        }
        System.out.println("main over....");
    }
}

 4.2、为什么继承Thread和复写run方法

  为什么要继承Thread类?

    因为在Java中使用Thread类描述线程这类事物。也就是说Thread类具备了操作线程这类事物的基本行为(功能)。当书写了一个类继承了Thread这个类,那么我们写的这个子类也就变成线程类了。那么我们书写的这个类也会具备线程的继承操作行为(这些行为其实是从Thread类中继承到的)。我们就可以开启我们自己写的这个线程对象类。

  

  我们定义线程目的是什么?

    目的是希望在内存中开辟出多条执行路径,让多部分代码能够(并发)同时执行。如果我们直接new Thread类,而这个Thread是api中已经写好的类,我们无法把自己想让线程执行的代码交给Thread类。那么我们就直接写个类继承Thread,那么我们自己的类也就变成线程类了,那么就可以在自己的类中书写线程要并发执行的代码。

  为什么要复写run方法?

    run方法也是Thread类中写好的方法。这个方法是在调用start方法开启线程的时候,有JVM自动调用run方法。JVM调用run方法的目的是去执行我们给run方法中分配的需要线程并发执行的那些代码。复写run方法的目的就是在run方法书写当前线程要执行的任务代码。

  如果是不调用start方法,而是创建一个线程对象,直接通过这个对象去调用run方法和使用start间接的去调用run方法有什么区别?

    ThreadDemo2 d = new ThreadDemo2();

    //启动这个线程

    //d.start();

    d.run();

    在这里new ThreadDemo2 的确是创建了线程对象。但是这个对象直接调用run方法的话,就是前面我们所学习对象调用方法是一样的,这时并没有在内存把这个线程开启,也就是线程存在了,但是它不运行。

  

  ThreadDemo2.java——调用start和run方法区别  ★★★★★ 

  

5、多线程运行图解

  多线程执行过程:

  

  线程内存执行图解:

  

  线程中的异常问题:

    当我们在运行多线程程序的时候,如果那个线程发生了异常,那么这个线程所在的代码就会停止运行,但是其他线程不受影响。

    

    红色部分表示异常发生在哪个线程上。黄色部分异常的名字以及异常的发生原因

    这时在Thread-0线程上发生了异常,就会导致Thread-0线程停止运行。但是其他线程依然可以正常运行

  ThreadDemo3.java——重复启动线程,以及异常问题

//定义一个类继承Thread
class Demo extends Thread
{
    //复写run方法
    public void run()
    {
        for( int i=0;i<10;i++ )
        {
            System.out.println( getName()+"....run i="+i);
        }
    }
}
class ThreadDemo3
{
    public static void main(String[] args)
    {
        //创建子类对象
        Demo d = new Demo();
        Demo d2 = new Demo();
        System.out.println("......"+d.getName());
        System.out.println("=================="+d2.getName());
        //d.setName("小强");
        //d2.setName("旺财");
        //开启线程
        d.start();
        d2.start();

        //d2.start();  // java.lang.IllegalThreadStateException
        //发生异常的原因是线程已经处于运行状态,不能再次开启
        //d2.run();
        for( int i=0;i<10;i++ )
        {
            System.out.println(Thread.currentThread().getName()+ ".................main i="+i);
        }
        System.out.println("over..................");
    }
}

6、获取线程名字和线程状态

 6.1、获取线程的名字:

  Thread类是是描述线程本身的,而现在我们又需要获取线程的名字,猜测有这个方法,猜测返回值可能是String。方法:getName();

  在Thread类中的确有getName方法返回当前线程的名字;

  在Java中如果我们没有手动的指定线程的名字,那么JVM会自动给我们线程分配名字,名字是 Thread-x   x从0开始;

  

  

  上面报错的原因是ThreadDemo3这个类中就没有getName()方法。而我们又想在ThreadDemo3中的main方法中获取当前正在运行的主线程的名字。这时可以使用Thread类中的静态的方法currentThread就可以获取当前正在运行的这个线程对象。从而就可以获取到当前这个线程的名字

  获取线程名字的方法:

    先使用Thread类中的currentThread方法获取到当前正在运行的线程对象,然后再调用getName方法获取线程的名字。

 6.2、线程状态

  

7、线程的实现方式二

  1、定义类实现Runnable接口

  2、实现run方法,这个run方法是接口中的方法

  3、创建实现类对象

  4、创建Thread类对象,把实现类对象作为参数传递

  5、开启线程

  当我们要定义一个线程时,目的是给这个线程分配线程运行时要执行的任务代码。Java在设计的时候使用Thread类来描述线程这个事物,这时在Thread类中定义了一个run方法。而这个run方法是专门用来存放线程要执行的任务。Java在设计Thread类的时候,让线程本身对象和线程要执行的任务严重的耦合在一起。于是就把这个任务单独的抽离出来,放到一个接口中,这样就可以保证Thread类专门负责线程这个事物,而Runnble接口专门负责线程要执行的任务。

  当我们要让线程执行某个任务时,首先我们可以创建Thread本身对象,然后在把任务传递给Thread对象。这样Thread对象就可以执行我们传递的这个任务。

  由于Java只支持单继承,如果有一个类中有部分代码需要多线程来执行,而这个类已经继承了其他的类,这时这个类无法在继承Thread类,可是其中需要多线程执行的任务没有办法交给Thread类。这时就可以让这个类实现Runnable接口,然后创建这个类对象,在把这个类对象交给Thread,那么Thread 就可以执行其中的任务。

  

  RunnableDemo.java——创建线程的第二种方式  实现Runnable接口 ★★★★★

//定义一个类,实现Runnable接口
class Demo  implements Runnable
{
    //实现run方法
    public void run()
    {
        for (int i=0;i<20 ;i++ )
        {
            System.out.println(Thread.currentThread().getName()+"...i..."+i);
        }
    }
}

class RunnableDemo
{
    public static void main(String[] args)
    {
        //创建实现类对象
        Demo d = new Demo();   //这里仅仅创建了线程要执行的任务对象

        //创建Thread对象    目的是创建线程本身对象
        Thread t = new Thread( d );
        Thread t2 = new Thread( d );
        Thread t3 = new Thread( d );

        t.start();
        t2.start();
        t3.start();
    }
}

8、线程练习

  模拟售票窗口:

   一般情况下火车站有多个窗口负责售票。这些窗口都会同时(并发执行)售票。使用多线程技术来模拟窗口售票的现象。

   可以把售票的这个动作认为是线程要操作的任务。这个任务什么时候结束?当把某个趟列车上的票售完之后,窗口就不能在售这趟列车上的票。

   我们现在模拟假设有100张票,4个窗口同时来卖。只要有任何一个窗口售完最后一张票,其他窗口保包含当前窗口不能在继续售票。

  

   ThreadTest.java——使用thread 完成售票的功能  ★★★★★

//定义类来模拟售票的案例,售票的动作要被多线程执行
class Ticket extends Thread{
    //定义一个变量用来记录当前的总票数
    //由于我们当前的类本身就是线程对象类,在创建当前类对象的时候
    //num没有被静态,每个对象中都会有自己的num那么在run方法运行的时候
    //就会出现每个run中都有使用自己当前这个对象中的num
    //把num变成静态之后,那么所有的Ticket对象就共享这个num成员变量
    static int num = 50;
    //售票的动作要被多线程执行,就需要把售票的动作放在run方法中
    public void run()    {
        //书写死循环的目的是让任何一个线程进来之后,就不断的售票
        //由于是死循环,就会导致线程在不断的售票
        while( true )
        {
            //当num为0的时候,就说明票已经卖完,就不应该在打印数据了
            if( num > 0 )
            {
                //每打印一次,就代表售出一张票
                //让任何线程执行到这里都休眠5毫秒
                System.out.println(Thread.currentThread().getName()+"....."+num);
                num--;
            }
        }
    }
}
class ThreadTest {
    public static void main(String[] args)     {
        //创建四个线程对象
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();
        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}
时间: 2024-11-17 18:07:31

多线程引入的相关文章

C++拾遗--C++多线程引入

C++拾遗--C++多线程引入 前言 C++库文件也提供了对多线程的支持,主要包含头文件thread即可使用C++中的多线程.它的一些与多线程有关的方法和C语言不同.我们有必要来探讨下C++编程下如何使用多线程. 正文 1.示例 与C语言多线程引入相同,我们先看一个C++多线程的示例. #include <iostream> #include <thread> //C++中的多线程头文件 #include <Windows.h> using namespace std;

【Java】多线程_学习笔记

多线程 1.进程 进程:当一个程序进入内存运行时,它就成为了进程.进程具有独立性.动态性.并发性. A.独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间.在没有进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间. B.动态性:进程与程序的区别在于,程序是一个静态的指令集合,而进程是一个正在运行的指令集合.进程有时间的概念,有自己的生命周期和各种不同的状态,而程序不具备这种概念. C.并发性:多个进程可以在单个处理器上并发执行,相互之

java多线程系列1--线程实现与调度

java的重要功能之一就是内部支持多线程,这一系列文章将详细剖析java多线程的基础知识 多线程概述 多线程引入 程序只有一个执行流程,所以这样的程序就是单线程程序. 假如一个程序有多条执行流程,那么,该程序就是多线程程序. 进程:正在运行的程序,是系统进行资源分配和调用的独立单位.每一个进程都有它自己的内存空间和系统资源. 线程:是进程中的单个顺序控制流,是一条执行路径.一个进程如果只有一条执行路径,则称为单线程程序. 一个进程如果有多条执行路径,则称为多线程程序. Java程序运行原理 ja

从epoll构建muduo-12 多线程入场

mini-muduo版本号传送门 version 0.00 从epoll构建muduo-1 mini-muduo介绍 version 0.01 从epoll构建muduo-2 最简单的epoll version 0.02 从epoll构建muduo-3 增加第一个类.顺便介绍reactor version 0.03 从epoll构建muduo-4 增加Channel version 0.04 从epoll构建muduo-5 增加Acceptor和TcpConnection version 0.0

Operating System-Thread(5)弹出式线程&amp;&amp;使单线程代码多线程化会产生那些问题

本文主要内容 弹出式线程(Pop-up threads) 使单线程代码多线程化会产生那些问题 一.弹出式线程(Pop-up threads) 以在一个http到达之后一个Service的处理为例子来介绍弹出式线程. 上面的例子中传统的做法有可能是在Service中有一个线程一直在等待request的到达,等request到达后这个线程会开始检查请求最后在进行处理.当这个线程在处理request的时候,后面来的request会被block,一直到线程处理完当前request为止.如下图所示. 弹出

多线程总结四:线程同步(一)

1.线程安全问题 a.银行取钱问题:取钱时银行系统判断账户余额是否大于取款金额,如果是,吐出钞票,修改余额.这个流程在多线程并发的场景下就可能会出现问题. 1 /** 2 * @Title: Account.java 3 * @Package 4 * @author 任伟 5 * @date 2014-12-8 下午5:35:27 6 * @version V1.0 7 */ 8 9 /** 10 * @ClassName: Account 11 * @Description: 账户类 12 *

JAVA多线程--线程的同步安全

每当我们在项目中使用多线程的时候,我们就不得不考虑线程的安全问题,而与线程安全直接挂钩的就是线程的同步问题.而在java的多线程中,用来保证多线程的同步安全性的主要有三种方法:同步代码块,同步方法和同步锁.下面就一起来看: 一.引言 最经典的线程问题:去银行存钱和取钱的问题,现在又甲乙两个人去同一个账户中取款,每人取出800,但是账户中一共有1000元,从逻辑上来讲,如果甲取走800,那么乙一定取不出来800: 1 package thread.threadInBank; 2 3 /** 4 *

多线程及线程池学习心得

一.线程的应用与特点 多线程是程序员不可或缺的技术能力,多线程技术在各个方面都有应用,特别在性能优化上更是起到至关重要的作用.但是,如果多线程写得不好,往往会适得其反,特别是高并发时会造成阻塞.超时等现象.多线程具有以下特点:1.独立性,拥有自己独立的资源,拥有自己私有的地址空间:2.动态性,进程具有自己的生命周期和各种不同的状态:3.并发性,多个进程可以在单个处理器上并发执行,不会相互影响,并行是指同一时刻有多条指令在多个处理器上同时执行.线程是进程的组成部分,一个进程可以拥有多个线程,一个线

有关多线程程序开发

多线程程序设计的困难 难以重现失败,bug难以追踪 并发错误难以追踪和消除 独立任务的拆分并不总是那么明朗 线程同步.通信引入的复杂 多线程引发的竞争.死锁.可见性问题(难以调试.追踪) 测试困难,简单的测试并不能覆盖生产环境的问题 设计不当,并不能充分提升性能(不控制线程数量,造成无谓上下文切换和Cache失效) 多线程引入的开销 上下文切换[包括Cache] 内存同步/Memory Barrier[java中的synchronized 和 volatile] 锁 内存开销 多线程程序优化 线