Java多线程——线程的概念和线程的创建

1、多线程的概念

进程:程序在环境中的执行过程,是一个实体,每一个进程都有他自己的内存空间和系统资源;

线程:在一个进程内部又可以执行多个任务,一般将进程内部的任务称为线程,线程是程序中单个顺序的控制流,是程序使用CPU的基本单位;在进程内部,多个线程是共享一个储存空间的,因此,线程间的通信比较简洁方便。

多线程:在单CPU的计算机中,某一时刻实际上只能运行一个进程,所以说,多进程实际就是CPU交替轮流执行多个进程的结果,是并发的。(并发不是并行,并发是逻辑上的同时发生,是指在某一时间段内同时运行多个进程,而并行是物理上的同时发生,是指在某一时间点同时运行多个进程)

1、线程和进程的区别:

  1. 地址空间和其他资源:每个进程占有独立的地址空间,包括代码、数据及其他资源;然而,属于同一个进程的线程只能共享这些资源,不具有独立的地址空间和其他资源。
  2. 通信:进程之间的通信(IPC)开销较大且受到诸多限制,必须具有明确的对象或操作接口并采用统一的通信协议;而线程之间可以直接读写进程数据段(如全局变量)来进行通信,需要进程同步和互斥手段的辅助,以保证数据的一致性,开销较少且较简单。
  3. 切换:进程间的切换开销也比较大,而线程间的切换开销比较小。若是在多进程程序中切换进程时,需要改变地址空间位置;而多线程程序只需要改变执行次序,因为他们位于同一个储存空间内。
  4. 控制表:与进程的控制表PCB类似,线程也有自己的控制表TCP。但TCP中保存的线程状态远小于PCB
  5. 线程是进程的一部分,因此程序有时候也可以说是轻量级进程

2、程序和进程的区别:

  • 动态性与静态性:程序是静态的,而进程是动态的,进程是一个能独立运行的单位,能与其他线程并发执行,程序不能作为独立运行的基本单位
  • 临时性和永久性:程序是永久的,而进程是临时的,程序可以作为一种软件资源长期保存,而进程仅是运行的程序,具有一定的生命周期,会动态的产生和消亡
  • 组成不同:进程包括了程序、数据和PCB进程控制块
  • 程序与进程不一定具有一一对应的关系,因为一方面,一个程序可以有多个进程共用;另一方面,一个进程在其活动中可以顺序的执行若干个程序

2、线程的创建

Java程序中的程序被设计为一个对象,但不是每一个对象都是线程,只有实现类Runnable接口的类对象才能被称为线程,Java语言提供了两种途径实现多线程:

  • Thread类(该类已经实现Runnable接口) 属于java.lang包,系统运行时自动调用,不需要用关键字import显示调用
  • Runnable

启动线程只能用start()方法,线程创建完成后处于新建状态,需要调用start()方法转为就绪状态,当其获得CPU后方可成为运行状态,多个线程对同一组数据访问时,可能会出现线程不安全的问题,造成数据损毁。

1、提供继承Thread类创建线程
Thread类的定义格式:

public class Thread extends Object implements Runnable

可以看出Thread类实现了Runnable接口,所以只要让一个类继承Thread类,并将线程的程序代码写在run()方法内,即重写Thread类的run()方法即可创建线程;多线程的定义语法:

[public] class 类名 extends Thread {
          属性;
          方法;
          public void run() {
          //线程程序代码
          }
     }

Thread类中已经定义了final类型的setName()和getName()方法,所以子类不能重新定义这些方法,直接调用就好

run()方法是线程需要执行的内容,启动线程是start()方法,一个线程对象只能调用一次start()方法。

public class TestDemo1 extends Thread {
    private String name;

    public TestDemo1() {

    }
    public TestDemo1(String name) {
        setName(name);   
    }
    public void run() {
        for(int i = 1;i <= 10;i++) {
            System.out.println("当前运行的线程:"
        +getName()+",i="+i);   
        }
    }


    public static void main(String[] args) {
        TestDemo1 x1 = new TestDemo1("线程1");    //创建TestDemo1的实例
        TestDemo1 x2 = new TestDemo1("线程2");
        x1.start();     //启动线程
        x2.start();

    }

}

运行结果:

当前运行的线程:线程1,i=1
当前运行的线程:线程2,i=1
当前运行的线程:线程1,i=2
当前运行的线程:线程2,i=2
当前运行的线程:线程2,i=3
当前运行的线程:线程2,i=4
当前运行的线程:线程1,i=3
当前运行的线程:线程2,i=5
当前运行的线程:线程1,i=4
当前运行的线程:线程2,i=6
当前运行的线程:线程1,i=5
当前运行的线程:线程1,i=6
当前运行的线程:线程2,i=7
当前运行的线程:线程2,i=8
当前运行的线程:线程2,i=9
当前运行的线程:线程2,i=10
当前运行的线程:线程1,i=7
当前运行的线程:线程1,i=8
当前运行的线程:线程1,i=9
当前运行的线程:线程1,i=10

2、通过实行Runnable接口创建线程
一般都可以通过Runnable接口实现多线程,Runnable接口中仅仅提供了一个抽象方法run(),由于Runnable接口对线程没有任何支持,因此,在获得线程实例之后,必须通过Thread类的构造方法来实现

public interface Runnable {
        public abstract void run();
}

提供Runnable接口实现多线程:

[public] class 类名 implement Runnable {
           属性;
           方法;
           public void run() {
             //线程程序代码
           }
}
public class TestDemo2 implements Runnable {
    private String name;

    public TestDemo2() {

    }

    public TestDemo2(String name) {
        setName();
    }

    private void setName() {
        this.name = name;
    }

    private String getName() {
        return name;
    }

    public void run() {
        for(int i = 1;i <= 10;i++) {
            System.out.println("当前运行的线程:"
        +getName()+",i="+i);   
        }

    }

    public static void main(String[] args) {
        TestDemo1 x1 = new TestDemo1("线程1");    //创建TestDemo1的实例
        TestDemo1 x2 = new TestDemo1("线程2");

        Thread y1 = new Thread(x1);   //实例化Thread对象
        Thread y2 = new Thread(x2);
        Thread y3 = new Thread(x1);

        y1.start();     //启动线程
        y2.start();
        y3.start();

    }
}

运行结果:

当前运行的线程:线程2,i=1
当前运行的线程:线程1,i=1
当前运行的线程:线程1,i=1
当前运行的线程:线程2,i=2
当前运行的线程:线程1,i=2
当前运行的线程:线程2,i=3
当前运行的线程:线程1,i=3
当前运行的线程:线程1,i=4
当前运行的线程:线程1,i=2
当前运行的线程:线程1,i=5
当前运行的线程:线程2,i=4
当前运行的线程:线程1,i=6
当前运行的线程:线程1,i=3
当前运行的线程:线程1,i=4
当前运行的线程:线程1,i=7
当前运行的线程:线程1,i=8
当前运行的线程:线程2,i=5
当前运行的线程:线程2,i=6
当前运行的线程:线程2,i=7
当前运行的线程:线程2,i=8
当前运行的线程:线程2,i=9
当前运行的线程:线程2,i=10
当前运行的线程:线程1,i=9
当前运行的线程:线程1,i=10
当前运行的线程:线程1,i=5
当前运行的线程:线程1,i=6
当前运行的线程:线程1,i=7
当前运行的线程:线程1,i=8
当前运行的线程:线程1,i=9
当前运行的线程:线程1,i=10

3、Java主线程——main()

一个Java应用程序的主类的main()方法就是他的主线程,主线程是产生应用程序所有其他线程的线程,并且主线程通常最后执行,以便完成各种收尾操作,也就是说,程序中只要还有其他非守护线程尚未结束,主线程就不会结束(即使main()方法里使用了return语句或该main()方法里的内容已经执行结束)

在Thread类中定义一个类方法currentThread(),可以利用该方法获取主线程对象引用,借此对主线程做各种控制处理,因为主线程也是一个对象:

public class TestDemo3 implements Runnable {
    public void run() {
        for(int i = 1;i <= 10;i++) {
            System.out.println("当前运行的线程:"
        +Thread.currentThread()+",i="+i);   
        }
    }


    public static void main(String[] args) {
        Thread mt = Thread.currentThread();  //获取主线程
        System.out.println("线程信息:"+mt);    //输出主线程信息
        System.out.println("线程名称:"+mt.getName()); //输出主线程名称
        mt.setName("主线程");       //设置主线程名称
        System.out.println("线程名称:"+mt.getName());  //再次输出主线程名称
        TestDemo3 t = new TestDemo3();
        Thread x = new Thread(t);
        x.start();
        t.run();
    }

}

运行结果:

线程信息:Thread[main,5,main]
线程名称:main
线程名称:主线程
当前运行的线程:Thread[主线程,5,main],i=1
当前运行的线程:Thread[主线程,5,main],i=2
当前运行的线程:Thread[主线程,5,main],i=3
当前运行的线程:Thread[主线程,5,main],i=4
当前运行的线程:Thread[Thread-0,5,main],i=1
当前运行的线程:Thread[Thread-0,5,main],i=2
当前运行的线程:Thread[Thread-0,5,main],i=3
当前运行的线程:Thread[主线程,5,main],i=5
当前运行的线程:Thread[Thread-0,5,main],i=4
当前运行的线程:Thread[主线程,5,main],i=6
当前运行的线程:Thread[Thread-0,5,main],i=5
当前运行的线程:Thread[主线程,5,main],i=7
当前运行的线程:Thread[Thread-0,5,main],i=6
当前运行的线程:Thread[Thread-0,5,main],i=7
当前运行的线程:Thread[主线程,5,main],i=8
当前运行的线程:Thread[Thread-0,5,main],i=8
当前运行的线程:Thread[主线程,5,main],i=9
当前运行的线程:Thread[Thread-0,5,main],i=9
当前运行的线程:Thread[主线程,5,main],i=10
当前运行的线程:Thread[Thread-0,5,main],i=10

4、比较继承Thread类和实现Runnable接口

继承Thread类和实现Runnable接口都可以实现多线程,但是一般都用后者

  1. 可以避免Java单继承特性带来的局限,一个Java类只能继承一个类但可以实现多个接口
  2. 适合多个相同程序代码的线程去处理同一资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
  3. 有利于程序的健壮性,代码能被多个线程共享,代码与数据是独立的。可以让多个线程共享一个实例的数据,因为通过Runnable接口实现的多线程,可以对同一个对象实现创建多个线程

举个例子,就像火车站卖票,有2个窗口(多个相同程序代码的线程)卖10张票(同一资源),写一个售票系统:

public class TestDemo4 extends Thread {
    private int ticket = 10;
    private String name;
    public TestDemo4() {
    }
    public TestDemo4(String name) {
        setName(name);
    }
    public void run() {
        while(ticket > 0) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName()
                        +"卖出第"+(ticket--)+"张门票!");
            }
        }
    }


    public static void main(String[] args) {
        TestDemo4 w1 = new TestDemo4("第一窗口");
        TestDemo4 w2 = new TestDemo4("第二窗口");
        w1.start();
        w2.start();
    }
}

运行结果:

第二窗口卖出第10张门票!
第一窗口卖出第10张门票!
第二窗口卖出第9张门票!
第二窗口卖出第8张门票!
第一窗口卖出第9张门票!
第二窗口卖出第7张门票!
第一窗口卖出第8张门票!
第二窗口卖出第6张门票!
第一窗口卖出第7张门票!
第二窗口卖出第5张门票!
第一窗口卖出第6张门票!
第一窗口卖出第5张门票!
第二窗口卖出第4张门票!
第二窗口卖出第3张门票!
第二窗口卖出第2张门票!
第二窗口卖出第1张门票!
第一窗口卖出第4张门票!
第一窗口卖出第3张门票!
第一窗口卖出第2张门票!
第一窗口卖出第1张门票!

通过Thread类显然没有达到预期的资源共享目的,试试Runnable接口:

public class TestDemo4 implements Runnable {
    private int ticket = 10;
    public TestDemo4() {
    }
    public void run() {
        while(ticket > 0) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName()
                        +"卖出第"+(ticket--)+"张门票!");
            }
        }
    }


    public static void main(String[] args) {
        TestDemo4 w = new TestDemo4();
        Thread t1 = new Thread(w,"第一窗口");
        Thread t2 = new Thread(w,"第二窗口");
        t1.start();
        t2.start();

    }

}

运行结果:

第二窗口卖出第10张门票!
第一窗口卖出第9张门票!
第一窗口卖出第8张门票!
第一窗口卖出第7张门票!
第一窗口卖出第6张门票!
第一窗口卖出第5张门票!
第一窗口卖出第4张门票!
第二窗口卖出第3张门票!
第一窗口卖出第2张门票!
第二窗口卖出第1张门票!

这样目的就达到了,满足资源共享

猜你喜欢

转载自blog.csdn.net/QQ2899349953/article/details/81367454