关于Java多线程------(1,认识多线程)

来源:互联网 发布:网络做饭卖用啥软件 编辑:程序博客网 时间:2024/05/22 09:51

1,进程与线程的区别

2,Java线程的两种实现方式以及区别

3,线程的操作状态

1,

微软最初的dos是采用单进程的处理方式,在同一段时间内,只能有一个程序独自运行,后来,Windows是采用了多进程处理方式,在一个时间段上,会有多个程序同事运行。比如说,打开Windows的任务管理器,就可以看到很多的正在运行的程序以及各个进程。
打开一个应用程序,其实在操作系统上就是给分配了一个进程,比如打开一个world文档的操作。

线程实际上就是在进程基础之上的进一步划分,比如说打开的这个world,可以吧拼写检查当做一个线程进行处理,当然,会同时存在多个线程。

如果一个进程没有了,那么他旗下的线程都会消失;一个线程消失了,它的进程不一定消失,所有的线程都是在进程的基础上并发执行的,也就是说是同时运行的

进程是程序的一次动态执行过程,它经理了从代码加载,执行,执行完毕,一个完整过程,这个过程也是进程从产生,发展到最后消亡的过程。

多线程是实现并发机制的一种有效手段,进程和线程一样,都是实现并发的一个基本单元
这里写图片描述

如果现在同事运行多个任务,则所有的系统资源将是共享的,被所有线程所公用,但是程序处理需要CPU,传统的单核CPU来说,在同一个时间段上会有多个程序执行,但是在同一个时间点上只能存在一个程序运行,也就是说,所有的程序都要抢占CPU资源。

但是现在的CPU已经发展到多核状态了,在一个电脑上可能会存在多个CPU,那么这个时候,就可以非常清楚的发现多线程操作间是如何进行并发执行的。

2,Java的多线程实现

有两种方式:

①,继承Thread类
②,实现Runnable接口

首先说第一种实现多线程的方式:

一个类只要继承了Thread类,此类就称为多线程操作类,就具备了多线程的操作功能,在Thread子类之中,必须明确的覆写Thread类中的run()方法,此方法为线程的主体。

继承Thread类实现多线程的定义语法:

class 类名称 extends Thread{        属性。。。。        方法。。。。        //覆写run()方法,此方法为线程的主体        public void run(){            线程主体;    }}

举个例子:

class MyThread extends Thread{  // 继承Thread类,作为线程的实现类    private String name ;       // 表示线程的名称    public MyThread(String name){        this.name = name ;      // 通过构造方法配置name属性    }    public void run(){  // 覆写run()方法,作为线程 的操作主体        for(int i=0;i<10;i++){            System.out.println(name + "运行,i = " + i) ;        }    }};public class ThreadDemo01{    public static void main(String args[]){        MyThread mt1 = new MyThread("线程A ") ;    // 实例化对象        MyThread mt2 = new MyThread("线程B ") ;    // 实例化对象        mt1.run() ; // 调用线程主体        mt2.run() ; // 调用线程主体    }};

运行结果:
这里写图片描述
可以发现,程序执行,是先执行A,再执行B,并没有达到并发执行的效果,为什么呢?因为以上的程序还是按照一种古老的形式调用的,通过对象.方法,但是如果想要启动一个线程,必须使用Thread类中定义的start()方法,这个方法,才是真真的启动一个多线程,这个start执行内部,会调用run方法,也就是说,一旦调用了start方法,实际上就是调用了run方法。

class MyThread extends Thread{  // 继承Thread类,作为线程的实现类    private String name ;       // 表示线程的名称    public MyThread(String name){        this.name = name ;      // 通过构造方法配置name属性    }    public void run(){  // 覆写run()方法,作为线程 的操作主体        for(int i=0;i<10;i++){            System.out.println(name + "运行,i = " + i) ;        }    }};public class ThreadDemo02{    public static void main(String args[]){        MyThread mt1 = new MyThread("线程A ") ;    // 实例化对象        MyThread mt2 = new MyThread("线程B ") ;    // 实例化对象        mt1.start() ;   // 调用线程主体        mt2.start() ;   // 调用线程主体    }};

运行结果:

这里写图片描述
可以发现,运行结果就非常明显了,一会儿A,一会儿B,那个线程先强占到了CPU,谁就先执行,达到了多线程的目的。

但是有一个问题:为什么不直接调用哦run方法,而是调用start方法,才开启了多线程呢??

不妨看一下Thread类的源码, 发现里面有一个native关键字修饰的方法和属性,这个native关键字表示的是一个由Java调用本机操作系统函数的函数以完成特定的功能。
如果想要实现多线程的话,则肯定要操作系统的支持,因为多线程操作中牵扯到抢占CPU的问题,要等待CPU进行调度,这一点,肯定需要操作系统的底层支持,所以,使用native调用本机的系统函数,而且,在各个操作系统中,多线程的底层实现代码肯定是不同的,所以使用native关键字,可以让jvm自动去调整不同的jvm去实现。

如果一个线程已经启动了,再次调用start去启动它就会出现异常。

class MyThread extends Thread{  // 继承Thread类,作为线程的实现类    private String name ;       // 表示线程的名称    public MyThread(String name){        this.name = name ;      // 通过构造方法配置name属性    }    public void run(){  // 覆写run()方法,作为线程 的操作主体        for(int i=0;i<10;i++){            System.out.println(name + "运行,i = " + i) ;        }    }};public class ThreadDemo03{    public static void main(String args[]){        MyThread mt1 = new MyThread("线程A ") ;    // 实例化对象        mt1.start() ;   // 调用线程主体        mt1.start() ;   // 错误    }};

运行结果:
这里写图片描述
很明显,一个线程,只能启动一次,启动多次就会抛出异常。

第二种实现多线程的方式

Thread继承实现多线程是有局限的,因为Java只支持单继承,所以为了解决这个单继承的问题,又出现了这第二种实现多线程的方式——–实现Runnable接口的方式实现多线程,Runnable接口中之定义了一个抽象方法public void run()

实现Runnable接口实现多线程的语法格式

class 类名称 implements Runnable{    属性。。。    方法。。。    public void run(){        线程主体    }}

要想启动线程,肯定要调用start方法,而这个方法是在Thread类中的,也就是说,要想启动一个线程必须得依靠Thread类。也就是说,要想启动一个线程必须得依靠Thread类。也就是说,要想启动一个线程必须得依靠Thread类。重要的事情说三遍。

之前是直接继承Thread类,直接可以继承下来这个start方法,并使用,但是在这个Runnable接口中并么有这个start方法,查看Thread类的API发现,Thread有一个构造方法可以接受Runnable实例,这样一来就可以通过Thread启动线程了。

class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类    private String name ;       // 表示线程的名称    public MyThread(String name){        this.name = name ;      // 通过构造方法配置name属性    }    public void run(){  // 覆写run()方法,作为线程 的操作主体        for(int i=0;i<10;i++){            System.out.println(name + "运行,i = " + i) ;        }    }};public class RunnableDemo01{    public static void main(String args[]){        MyThread mt1 = new MyThread("线程A ") ;    // 实例化对象        MyThread mt2 = new MyThread("线程B ") ;    // 实例化对象        Thread t1 = new Thread(mt1) ;       // 实例化Thread类对象        Thread t2 = new Thread(mt2) ;       // 实例化Thread类对象        t1.start() ;    // 启动多线程        t2.start() ;    // 启动多线程    }};

运行结果:
这里写图片描述
可以发现,两个线程交替执行了,说明实现了多线程的功能了。

问题来了

既然有2种实现多线程的方法,到底使用哪一种好呢?

两种多线程实现方式的比较

首先看一下Thread的定义:
发现Thread本身是Runnable接口的子类。

public class Threadextends Objectimplements Runnable

实现一个接口,然后构造或者参数中又传入这个接口的实例,这样的设计,——-似乎好熟悉,原来它是代理模式的设计。。。。
这里写图片描述

Thread与Runnable的联系—-

从类的关系上看来,这是一种代理设计模式,Thread类完成比线程主体更多的操作,比如分配CPU资源,判断是否已经启动了线程等等,线程主体在MyThread类中独立进行。

Thread与Runnable的区别—–

使用Thread类,在操作多线程的时候,无法达到资源共享的目的,而使用Runnable接口的方式实现的多线程,在操作多线程的时候,可以实现资源共享。

什么意思呢?举个例子

比如说有这样一个场景,卖火车票,假设现在有5张票,开3个窗口去卖这些票,需要保证的是,卖出去的票不能重复,而且都是合法的,如果对于一个座位号卖出去了多张票,这就tm非常尴尬了,所以这一点必须要保证。
Thread实现方式:

class MyThread extends Thread{  // 继承Thread类,作为线程的实现类    private int ticket = 5 ;        // 表示一共有5张票    public void run(){  // 覆写run()方法,作为线程 的操作主体        for(int i=0;i<100;i++){            if(this.ticket>0){                System.out.println("卖票:ticket = " + ticket--) ;            }        }    }};public class ThreadDemo04{    public static void main(String args[]){        MyThread mt1 = new MyThread() ;  // 实例化对象        MyThread mt2 = new MyThread() ;  // 实例化对象        MyThread mt3 = new MyThread() ;  // 实例化对象        mt1.run() ; // 调用线程主体        mt2.run() ; // 调用线程主体        mt3.run() ; // 调用线程主体    }};

运行结果
这里写图片描述
发现,3个窗口,各卖个的的票,一种卖出了15张票,发现,并没有达到资源共享的目的。因为在每一个MyThread对象中,都包含个字的ticket票数属性,无法共享这个参数。

如果现在使用Runnable接口呢,同样启动多个线程,那么所有的线程将卖出共同的5张票。
Runnable实现方式

class MyThread implements Runnable{ // 继承Thread类,作为线程的实现类    private int ticket = 5 ;        // 表示一共有5张票    public void run(){  // 覆写run()方法,作为线程 的操作主体        for(int i=0;i<100;i++){            if(this.ticket>0){                System.out.println("卖票:ticket = " + ticket--) ;            }        }    }};public class RunnableDemo02{    public static void main(String args[]){        MyThread mt = new MyThread() ;   // 实例化对象        new Thread(mt).run() ;  // 调用线程主体        new Thread(mt).run() ;  // 调用线程主体        new Thread(mt).run() ;  // 调用线程主体    }};

这里写图片描述

从运行结果可以发现,虽然启动了3个线程,但是3个线程一共卖出了5张票,达到了资源共享的目的。

使用结论

1,实现Runnable接口比继承Thread类有如下的明确优点:
①,资源共享,适合多个相同程序代码的线程去处理同一个资源
②,可以避免由于单继承局限所带来的影响
③,增强了程序的健壮性,代码可以被多个线程共享,代码与数据是独立的。

综合以上来看,开发中使用runnable接口是最合适的。

3,线程的状态

多线程在操作中也是有一个固定的操作状态的:
创建状态:准备好了一个多线程对象Thread t=new Thread();
就绪状态:调用了start方法,等待CPU调度
运行状态:执行run方法
阻塞状态:暂时停止执行,可能将资源交给其他线程使用
终止状态(死亡状态):线程执行完毕,不在使用了

多线程操作状态的转换

实际上,线程调用start方法的时候,不是立刻启动的,而是要等待CPU进行调度的,

总结:
1,线程与进程的区别:线程是在进程的基础上的进一步划分;’线程消失了,进程不一定消失;进程如果消失了,则线程肯定会消失。
2,Java多线程的两种方式:1,继承Thread类,2,实现Runnable接口。
3,线程的启动:通过start方法完成,需要进行等待CPU调度, 调用start方法实际上就是执行run方法。
4,Thread类也是Runnable的子类,使用了代理模式。
5,使用多线程,最好使用Runnable方式,可以避免单继承局限,同事可以达到资源共享的目的。
6,线程状态。