二、Java多线程的实现

 

目录

1.创建一个多线程程序

2.多线程的内存图解

3.Thread 的常用方法

4.使用 Runable 接口创建线程

5.线程池简介


1.创建一个多线程程序

在没有接触线程之前,我们的程序都是通过 main ( ) 方法,也就是主方法来运行。主方法其实就是一个线程,被称为主线程,写在主方法里的代码都会从上往下依执行。如果现在有两个方法:方法 A 与方法 B,我们想让这两个方法同时运行,也就是并发执行,又该如何用代码来实现呢?Java 为我们提供了一个专门的多线程类 Thread,通过这个类我们就能实现多个线程同时执行。

    public static void main(String[] args) {

        //使用匿名内部类的写法实例化一个Thread对象
        new Thread() {
            @Override
            public void run() {
                A();
            }
        }.start();//线程开始执行

        new Thread() {
            @Override
            public void run() {
                B();
            }
        }.start();

    }

    public static void A() {
        int count = 1;
        while (count<=100){
            System.out.println("A方法打印第"+count+"次");
            count++;
        }
    }

    public static void B() {
        int count = 1;
        while (count<=100){
            System.out.println("B方法打印第"+count+"次");
            count++;
        }
    }


A方法打印第53次
A方法打印第54次
A方法打印第55次
A方法打印第56次
A方法打印第57次
B方法打印第18次
B方法打印第19次
B方法打印第20次
B方法打印第21次
B方法打印第22次
B方法打印第23次
A方法打印第58次
A方法打印第59次
A方法打印第60次
A方法打印第61次
A方法打印第62次
A方法打印第63次
A方法打印第64次
A方法打印第65次
B方法打印第24次
B方法打印第25次

我们截取了一部分的运行结果,可以很明显的看到:在 A 打印完第 57 次时停止打印并让 B 开始打印,当 B 打印了几次后又开始让 A 打印,A 和 B 的打印是随机的,这就实现了多线程并发执行。

2.多线程的内存图解

如图所示,当在栈 1 中的主线程运行到 start ( ) 方法时,就会在内存中重新开辟一个栈空间,把 start ( ) 方法对应的 run ( ) 方法调入新的栈空间执行。此时第一个 run ( ) 已经脱离主线程,CPU 此时就可以对栈空间进行选择。假设 CPU 仍选择栈 1 ,那就会执行第二个 start ( ) 方法,又开辟了一个新的栈空间供对应的 run ( ) 方法执行。main ( ) 执行完第二个 start ( ) 后结束线程销毁栈 1 ,CPU 继续选择栈 2 或栈 3 执行。

3.Thread 的常用方法

 static ThreadcurrentThread()
          返回对当前正在执行的线程对象的引用。
 booleanisInterrupted()
          测试线程是否已经中断。
 longgetId()
          返回该线程的标识符。
 StringgetName()
          返回该线程的名称。
 intgetPriority()
          返回线程的优先级。
 Thread.State

getState()
          返回该线程的状态。

 ThreadGroupgetThreadGroup()
          返回该线程所属的线程组。
 voidjoin()
          等待该线程终止。
 voidjoin(long millis)
          等待该线程终止的时间最长为 millis 毫秒。
 voidrun()
          如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
 void

setName(String name)
          改变线程名称,使之与参数 name 相同。

 voidsetPriority(int newPriority)
          更改线程的优先级。
 static voidsleep(long millis)
          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
 voidstart()
          使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
 StringtoString()
          返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
 static voidyield()
          暂停当前正在执行的线程对象,并执行其他线程。
 voidsetDaemon(boolean on)
          将该线程标记为守护线程或用户线程。
 voidinterrupt()
          中断线程。
 static booleaninterrupted()
          测试当前线程是否已经中断。
 booleanisAlive()
          测试线程是否处于活动状态。
 booleanisDaemon()
          测试该线程是否为守护线程。

 下面通过一个案例来使用这些方法:

    public static void main(String[] args) throws InterruptedException {
        //------------主线程开始-------------

        System.out.println("主线程开始了");
        //获取此时正在运行的线程
        Thread main = Thread.currentThread();
        //获取线程标识符、线程名、线程优先级、线程状态
        System.out.println();
        System.out.println("线程ID:"+main.getId());
        System.out.println("线程名:"+main.getName());
        System.out.println("线程优先级:"+main.getPriority());
        System.out.println("线程状态:"+main.getState());
        //设置线程信息
        System.out.println("设置主线程信息");
        main.setName("主线程");//线程名
        main.setPriority(10);//优先级
        //再次打印信息
        System.out.println();
        System.out.println("线程ID:"+main.getId());
        System.out.println("线程名:"+main.getName());
        System.out.println("线程优先级:"+main.getPriority());
        //创建一个名为"新线程",优先级为2的线程
        Thread thread = new Thread("新线程"){
            @Override
            public void run() {
                Thread thread1 = Thread.currentThread();
                System.out.println("新线程开始运行了");
                System.out.println("新线程开始休眠"+new Date());
                try {
                    //新线程休眠10秒
                    thread1.sleep(10000);
                } catch (InterruptedException e) {
                }
                System.out.println("新线程结束休眠"+new Date());
                System.out.println("新线程的线程组:"+thread1.getThreadGroup());
                System.out.println("新线程结束了");
            }
        };
        System.out.println();
        System.out.println("新线程被创建了");
        thread.setPriority(2);
        //设置为用户线程
        thread.setDaemon(true);
        //开启线程
        thread.start();
        //判断新线程状态
        System.out.println();
        System.out.println("线程ID:"+thread.getId());
        System.out.println("线程名:"+thread.getName());
        System.out.println("线程优先级:"+thread.getPriority());
        System.out.println("是否活跃:"+thread.isAlive());
        System.out.println("是否是用户级线程:"+thread.isDaemon());
        System.out.println("线程是否被中断:"+thread.isInterrupted());

        //主线程等待新线程执行完
        System.out.println();
        System.out.println("主线程等待新线程结束");
        thread.join();

        System.out.println();
        System.out.println("主线程结束了");


    }

------------------执行结果------------------

主线程开始了

线程ID:1
线程名:main
线程优先级:5
线程状态:RUNNABLE
设置主线程信息

线程ID:1
线程名:主线程
线程优先级:10

新线程被创建了

线程ID:12
线程名:新线程
线程优先级:2
是否活跃:true
是否是用户级线程:true
新线程开始运行了
线程是否被中断:false

主线程等待新线程结束
新线程开始休眠Tue Feb 11 20:29:59 CST 2020
新线程结束休眠Tue Feb 11 20:30:09 CST 2020
新线程的线程组:java.lang.ThreadGroup[name=main,maxpri=10]
新线程结束了

主线程结束了

4.使用 Runable 接口创建线程

Thread 类是 Runable 接口的实现类,实现了 Runable 接口的 run ( ) 方法。除了上面所提到的通过实例化 Thread 类或者其子类来创建线程以外,也可以在 Thread 类的构造方法中传入一个 Runable 的实现类来创建多个相同动作的线程。

    public static void main(String[] args) {
        //使用匿名内部类创建Runable实例化对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //规定线程动作,
                //所有以此实现类为参数构造的Thread对象都执行此run()方法
                System.out.println(Thread.currentThread().getName()
                        + "执行了Runable的run()方法。。。");
            }
        };
        //创建多个相同动作的线程
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        Thread thread3 = new Thread(runnable);

        thread1.start();
        thread2.start();
        thread3.start();

        System.out.println(thread1.getId() + " " + thread1.getName());
        System.out.println(thread2.getId() + " " + thread2.getName());
        System.out.println(thread3.getId() + " " + thread3.getName());

    }

    -----------执行结果------------

    12 Thread-0
    Thread-1执行了Runable的run()方法。。。
    Thread-0执行了Runable的run()方法。。。
    Thread-2执行了Runable的run()方法。。。
    13 Thread-1
    14 Thread-2

注意:

① Runable 实现类中所声明的属性和方法,由所有通过该实现类创建的线程所共享。

② 避免了继承 Thread 类的单继承的局限性。类只能单继承,继承了 Thread 类就不能继承其他的类。而实现了 Runnable 接口,还可以继承其他的类,实现其他的接口。

③ Runnable 接口把设置线程任务和开启新线程进行了分离,增强了程序的可拓展性。

④ Runnable 接口实现时将同类型线程统一封装,更符合面向对象思想。

5.线程池简介

上面所创建的这两种线程在运行结束后都会被虚拟机销毁,如果任务数量多的话,频繁的创建和销毁线程会浪费大量时间,降低任务效率,创建过多的线程也会使内存开销吃紧。那么有没有一种方法能很好的解决这种问题呢?答案就是线程池。线程池可以让任务线程运行完成任务后不用立即销毁,而是让线程重复使用,节省了大量时间。

事实上,线程池运用的思想就是 “以空间换时间” ,线程池就是指定容量的线程的集合,通常采用 LinkedList 作为线程池的容器。需要线程时,使用 removeFirst 从线程池中取出一个线程,用完再使用 addLast 归还线程,当线程池中线程不够用时其他线程就必须等待归还线程。在 JDK 1.5 中加入了线程池类 Executors 供我们直接创建一个性能稳定的线程池,以下是线程池的创建与操作代码实现:

    public static void main(String[] args) {
        //创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
        ExecutorService executors = Executors.newFixedThreadPool(2);
        //定义线程任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了");
            }
        };
        //提交多个 Runnable 任务用于执行
        executors.submit(runnable);
        executors.submit(runnable);
        executors.submit(runnable);
        executors.submit(runnable);
        executors.submit(runnable);
    }

    执行结果:

    pool-1-thread-1执行了
    pool-1-thread-2执行了
    pool-1-thread-1执行了
    pool-1-thread-2执行了
    pool-1-thread-1执行了

因为在创建线程池时只给了两个线程,因此可以看到这些线程任务均是由这两个线程来执行的。而且这个线程池是一直存在的,不会被自动回收,只有在执行 shutdown ( ) 方法之后才会回收此线程池。

其实 Executors 提供了不止一种线程池,在创建线程池时可以根据不同的需求创建不同类型的线程池。以下是 Executors 为我们提供的线程池:

static ExecutorServicenewCachedThreadPool()
          创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
static ExecutorServicenewFixedThreadPool(int nThreads)
          创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
static ExecutorService

newSingleThreadExecutor()
          创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

static ScheduledExecutorServicenewScheduledThreadPool(int corePoolSize)
          创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorServicenewSingleThreadScheduledExecutor()
          创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

线程池的优点:

① 服务器的线程数量是有限的,使用线程池使得每个工作线程都可以重复利用,有效的减少了因过多的创建和销毁线程所带来的时间和空间(内存)压力,省时省资源。

② 可以根据系统的承受能力,调整线程池中工作线程的数量,提高服务器工作效率,降低因为消耗过多内存导致服务器崩溃的风险。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值