JavaSE基础09-多线程

目录

  • 前言
      • 基本的线程机制1
        • 1.如何定义任务
        • 2.线程执行任务的两种方式
        • 3.创建多个线程
        • 4.常见的三种线程池
      • 基本的线程机制2
        • 1.从任务中产生返回值
        • 2.休眠
        • 3.优先级

前言

实现并发的最直接的方式就是在操作系统级别上使用进程;进程是运行在自己空间内的自包容程序。多任务操作系统,可以通过周期性,将cpu从一个进程切换到另一个进程,在宏观上实现同时运行多个进程。
并发编程可以使我们将程序划分为多个分离地,互不干扰的独立任务(子任务);每一个独立任务都由执行线程来驱动;一个线程就是在进程中的单一顺序控制流;因此每个进程可以拥有多个并发执行的任务;我们所要学习的就是怎样去定义一个任务,然后定义线程。

基本的线程机制1

Java所使用的并发系统,会共享内存或IO等资源;因此编写多线程程序最基本的困难在于协调多线程对资源的使用,使得不会发生死锁的情况;
线程机制是由执行程序表示的单一进程中创建任务,这种方式产生的一个好处就是操作系统的透明性。
Java的线程机制是抢占式的,表示调度机制会周期性的切断线程,从而为每个线程都分配到一个时间片;

1.如何定义任务

实验代码1:如何定义任务
定义一个任务类,这个任务必须要实现Runable接口,实现run方法,定义任务的行为。

package com.JavaSE10.demo01;

//定义一个任务
public class LiftOff  implements Runnable{

    protected  int countDown =10;//倒计时
    private static int taskCount =0;
    private final int id=taskCount++;

    public LiftOff(){}

    public LiftOff(int countDown){
        this.countDown=countDown;
    }

    public String status(){
        //判断倒计时是否>0
        return "#"+id+"(" +(countDown>0?countDown:"LiftOff!") +").";
    }

    public void run() {//这个方法给到一个线程
        while(countDown-- >0){
            System.out.println(status());
            Thread.yield();//让步方法,暂停当前正在执行的线程对象
        }
    }

    //使用执行器

}

2.线程执行任务的两种方式

定义完任务后,创建一个主线程类执行任务

package com.JavaSE10.demo01;

public class MainThread {
    public static void main(String[] args) {
        LiftOff launch = new LiftOff();
        launch.run();//调用任务类的run方法
        
    }
}

同样地,我们也可以创建一个基本线程类,为任务分配执行线程。

package com.JavaSE10.demo01;

public class BasicThreads {
    public static void main(String[] args) {
        //Thread有一个构造器接收任务
        Thread t =new Thread(new LiftOff());//Thread是一个线程;这条语句代表把任务给到线程
        t.start();//t线程开始工作

        //主线程无需等待子线程,所以告诉t线程开始工作后,马上往下执行
        System.out.println("我是主线程");


    }
}

我们也可以在一个基本线程类中创建多个线程执行多个任务

3.创建多个线程

package com.JavaSE10.demo01;

public class MoreBasicThreads {
    public static void main(String[] args) {
        //创建5个线程执行5个任务
        for (int i = 0; i < 5; i++) {
            //执行顺序是不受控制的
            //这种线程间的切换是由线程调度器自主控制的
            Thread t = new Thread(new LiftOff());
            t.start();
        }
            System.out.println("我是主线程");

    }
}

java1.5以后工具包下的并发包里有一个执行器可以为我们管理线程,从而简化并发编程,我们不需要创建那么多线程,因为执行器在客户端和任务执行中提供了一个间接层。与客户端直接执行任务不同,中介对象去执行任务,执行器允许我们管理异步任务的执行,也无需显示的管理线程的声明周期;
通俗地讲就是:执行器拥有线程池,然后执行器帮我们管理在执行任务中的一系列操作。我们只需要为执行器指定线程池的类型。
线程池:缓冲线程池 、固定线程池

实验代码2:缓冲线程池

4.常见的三种线程池

package com.JavaSE10.demo01;
import java.util.concurrent.*;
public class CacheThreadPool {
    public static void main(String[] args) {
        //取一个带缓冲的线程池,获得执行器
        ExecutorService exec =Executors.newCachedThreadPool();
        //让它执行5个任务;执行器中有一个execute方法接收任务,并开始执行;
        //执行器在线程池里获取线程去执行任务;如果线程不够的话,它会自行创建线程,无需显式管理
        /*
        * 通常情况下,它会自行去创建跟所需线程数量相同的线程;
        * 在不用的时候,会自动回收旧线程,如果有旧线程可以用,那么它就会调用旧线程去执行任务
        * */
        for (int i = 0; i < 5; i++) exec.execute(new LiftOff());

        //最后用完要关闭执行器
        exec.shutdown();


    }
}

我们也可以取一个固定线程数量的线程池,通过预先一次性创建,从而达到节省系统开销的目的。
实验代码3:固定线程池

package com.JavaSE10.demo01;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPool {
    public static void main(String[] args) {
        //创建一个固定数量的线程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        /**
         * 一次性的预先分配线程,限制线程的数量,可以节省时间,不用为每个任务都固定地付出创建线程的开销
         * 在任何线程池当中,在现有线程在可能的情况下都有可能被自动复用。在不用的时候,被线程池回收,但其他任务要用的话,可能会被复用
         *
         */
        for (int i = 0; i < 5; i++) exec.execute(new LiftOff());
        exec.shutdown();

    }
}

我们也可以为执行器分配一个单线程。
实现代码4:单线程的执行器

package com.JavaSE10.demo01;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExec {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        /**
         * 因为是单线程,所以会先把第一个任务执行完了,再去执行第二个任务,即顺序执行
         */
        for (int i = 0; i < 5; i++) {
            executorService.execute(new LiftOff());
        }
        executorService.shutdown();
    }
}

基本的线程机制2

1.从任务中产生返回值

runable接口中的run方法,这个run方法告诉线程,在开始执行任务的时候,从run方法开始执行。
如果我们要实现线程运行任务产生返回值,那么不能实现Runable接口。换一种方式,从javaSE5开始,引入了一个可调用接口,这个可调用接口具有一个类型参数(泛型),我们可以指定返回值的类型。
这个接口是Callable。

实验代码:

package com.JavaSE10.demo02;

import java.util.ArrayList;
import java.util.concurrent.*;

public class TaskWithResult  implements Callable<String> {
    private int id;
    public TaskWithResult(int id){
        this.id=id;
    }
    public String call() throws Exception {
        return "result of TaskWithResult "+id;
    }

    public static void main(String [] args){
        //创建一个缓冲线程池给执行器
        ExecutorService exec = Executors.newCachedThreadPool();
        //执行任务的返回结果存到ArrayList中
        //扩展:Future是用来接收Callable接口返回的信息,所以要指定泛型的类型
        ArrayList<Future<String>>  results = new ArrayList<Future<String>>();
        //当有返回值时,我们要使用submit方法
        //从线程池中获取10个线程,每一个线程都创建一个任务去执行;而任务实现的是Callable接口,可以有返回值,所以我们可以拿到返回值。最终结果放在ArrayList中
        for (int i = 0; i < 10; i++) results.add(exec.submit(new TaskWithResult(i)));

        //查看返回结果
        for (Future<String> fs:results){
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }finally{
                exec.shutdown();
            }
        }




    }
}

2.休眠

有时候,我们可能希望线程等一等,那么就可以设置线程休眠
传统的方式,用线程的sleep方法
javaSE5以后有了新的方式 TimeUnit.MILLISECONDS.sleep
扩展知识
TimeUtil是对Thread.sleep方法的包装,实现是一样的,只是多了时间单位转换和验证,然而TimeUnit枚举成员的方法却提供更好的可读性
sleep和yield的区别

1.sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会

2.yield()方法只会给相同优先级或更高优先级的线程以运行的机会

3.线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态

4.sleep()方法声明会抛出InterruptedException,而yield()方法没有声明任何异常

5.sleep()方法比yield()方法具有更好的移植性(跟操作系统CPU调度相关) ———————————————— 版权声明:本文为CSDN博主「弗兰随风小欢」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_32575047/article/details/80085576

package com.JavaSE10.demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SleepingTask extends LiftOff {
    public void run() {//这个方法给到一个线程
//            Thread.currentThread();//获取到当前的线程
            try {
                while(countDown-- >0){
                /*
                    传统的方式,用线程的sleep方法
                    javaSE5以后有了新的方式     TimeUnit.MILLISECONDS.sleep

                * 每输出一个数字,就睡眠100毫秒,相当于作一个让步
                * 100毫秒之后,不代表会马上执行该线程,要看调用机制执行哪个线程
                *
                * */
                System.out.println(status());
//                Thread.sleep(100);//指定睡眠时间  (毫秒)
                    TimeUnit.MILLISECONDS.sleep(100);//效果是一样的,这是JDK1.5以后的方法
                }
            } catch (InterruptedException e) {
                System.err.println("Interrupted");
            }

    }

    public static void main(String[] args) {
        //一般用缓冲线程池,只有有特定的需求、目的才去使用其他类型线程池。
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            exec.execute( new SleepingTask());
        }
        exec.shutdown();
    }

}

3.优先级

在没有设置线程优先级的情况下,所有线程的优先级都是相同的;在相同的情况执行顺序的先后,是由线程调度机制控制的。

优先级是指:把线程的重要性传给调度器,调度器就让优先级高的线程先执行,但这不意味着优先权低的线程得不到执行。只是优先级别较低的线程,执行频率稍微低一点;在绝大多数的时候,所有的线程都是以默认的优先级执行。少数情况,认为控制优先级。因为在人为的情况下,通常很难保证达到一个预期的效果

package com.JavaSE10.demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimplePriorties  implements  Runnable{
    private int countDown =5;
    //这是一种轻重量的同步方式,一旦修改其值,马上传递给各个线程。
    private volatile double d;
    private int priority;

    public SimplePriorties(int priority){
        this.priority=priority;
    }
    public String toString(){
        //获取当前线程的名称
        return Thread.currentThread().getName()+":"+countDown;
    }
    public void run() {
        //获取当前线程,然后设置优先级
        Thread.currentThread().setPriority(priority);
        while(true){
            for(int i=0;i<10000;i++){
                d+=(Math.PI+Math.E)/(double)i;
                if(i%1000 ==0){
                    Thread.yield();//线程让步方法
                }
                System.out.println(this);
                if(--countDown==0)return;//停止运行
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            //优先级我们一般是用Thread中的优先级数,不要自己设置的数字;为了便于跟系统映射优先级
            exec.execute(new SimplePriorties(Thread.MIN_PRIORITY));
        }
        exec.execute(new SimplePriorties(Thread.MAX_PRIORITY));

        exec.shutdown();
    }
}

你可能感兴趣的:(JavaSE)