java多线程-1-线程的创建及相关知识

文章目录

  • 1 相关概念
    • 1.1 并行、并发
    • 1.2 程序、进程、线程
    • 1.3 进程的五种状态及其转换
  • 2 线程
    • 2.1 线程创建的四种方式
      • 1 继承Thread类
      • 2 实现Runnable接口
      • 3 实现Callable接口
      • 4 线程池
    • 2.2 线程的常用方法及线程优先级
    • 2.3 两种创建线程方式的比较

1 相关概念

1.1 并行、并发

并行性和并发性是既相似又有区别的两个概念。并行性是指两个或多个事件在同一时刻发生,并发性是指两个或多个事件在同一时间间隔内发生。

在多道程序环境下,并发性是指在一段时间内,宏观上有多个程序同时运行,但在单处理器系统中每个时刻却仅能有一道程序执行,因此微观上这些程序只能分时地交替执行。若在计算机系统中有多个处理器,则这些可以并发执行的程序便被分配到多个处理器上,实现并行执行,即利用每个处理器来处理一个可并发执行的程序。

java多线程-1-线程的创建及相关知识_第1张图片

1.2 程序、进程、线程

程序(program)

是静态的,是一段具有特定功能的代码。

进程(process)

是动态的,它是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,进程具有一定的生命周期,如创建,就绪,执行,阻塞,死亡……。进程举例:

  • 运行中的QQ,
  • 运行中的微信

进程是具有特定功能的程序在一个数据集合上的运行过程,是系统进行资源分配和调度的独立单位(没有线程时)。

进程一般由以下几个部分组成:

  • 进程控制块(PCB)。每个进程均有一个PCB,它是一个既能标识进程的存在、又能刻画执行瞬间特征的数据机构。当进程被创建时,系统为它申请和构造一个相应的PCB。
  • 程序段。程序段是进程中能被进程调度程序调度到CPU上执行的程序代码段,能实现相应的特定功能。
  • 数据段。一个进程的数据段可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或结果数据。

线程(thread)

由于进程是并发执行的,每次切换时都需要保存上一个进程的运行环境并恢复下一个进程的环境,这就造成了一定的开销,为了减少这种开销,提高操作系统的并发程度。因此引入了线程的概念,在拥有线程的操作系统中,进程不再是进行资源分配与调度的单位,而仅仅是拥有资源的单位,一个进程可有多个线程,每个线程都是一个可运行的实体,各线程并发执行,即操作系统进行调度的单位是线程、进行资源分配的单位是进程。

线程由线程标识符、程序计数器、一组寄存器的值和堆栈组成。由于线程只是任务调度的单位,只是运行实体,而不是资源分配的单位,因此除了上述线程运行时必不可少的资源外,线程基本不拥有资源。线程创建时不需要另行分配资源,终止线程时也不需要进行资源回收,切换线程时也大大减少了需要保存和恢复的现场信息,因此线程的创建、终止、切换均比进程迅速且开销小,从而提高了操作系统的并发程度,增大了系统吞吐量,提高了系统效率。由于一个进程所拥有的全部线程共享该进程分配的内存、资源等,因此线程间的通信也比进程更快、更方便。

1.3 进程的五种状态及其转换

在进程的运行过程中,由于系统中多个进程的并发运行及相互制约的结果,使得进程的状态不断发生变化。通常,一个运行中的进程至少可划分为5种基本状态。

  • 就绪状态。进程已获得了除处理器以外的所有资源,一旦获得处理器,就可以立即执行,此时进程所处的状态为就绪状态。
  • 执行状态(运行状态)。当一个进程获得必要的资源并止在 CPU上执行时,该进程所处的状态为执行状态。
  • 阻塞状态(等待状态)。正在执行的进程,由于发生某事件而暂时无法执行下去(如等待I/O完成),此时进程所处的状态为阻塞状态。当进程处于阻塞状态时,即使把处理器分配给该进程,它也无法运行。
  • 创建状态。进程正在被创建,尚未转到就绪状态。申请空白的 PCB,并向PCB中填写一些控制和管理进程的信息;然后由系统为该进程分配运行时所需的资源;最后把该进程转入就绪状态。
  • 结束状态。进程正在从系统中消失,可能是正常结束或其他原因中断退出运行。

java多线程-1-线程的创建及相关知识_第2张图片
java中线程各状态转换
java多线程-1-线程的创建及相关知识_第3张图片

2 线程

2.1 线程创建的四种方式

1 继承Thread类

package com.fanyi.fy.thread_creation;

/**
 * @author FanYi
 * @datetime 2021/1/24 13:30
 *
 * 多线程的创建之方式一:继承Thread类
 *      1 创建一个Thread的子类
 *      2 重写Thread类的run()方法,将线程要执行的代码写在run()方法中
 *      3 创建Thread子类对象
 *      4 通过此对象调用start()方法
 *          ① 启动当前线程
 *          ② 调用当前线程的run()方法
 */
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
            }
        }
    }
}
public class ThreadCreation1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        // 1 必须通过start()方法启动线程,不能通过run()方法。否则创建不出多线程
        // myThread.run();

        // 2 同一线程,其start()方法只能调用一次,不能多次调用。否则出现 java.lang.IllegalThreadStateException
        // myThread.start();

        MyThread myThread1 = new MyThread();
        myThread1.start();

        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
            }
        }
    }
}

2 实现Runnable接口

package com.fanyi.fy.thread_creation;

/**
 * @author FanYi
 * @datetime 2021/1/24 15:20
 *
 * 线程创建方式二:实现Runnable接口
 *      1 创建一个实现Runnable接口的类
 *      2 重写接口的run()方法
 *      3 创建实现类的对象
 *      4 将该对象作为参数传递到Thread的构造方法中
 *      5 通过Thread对象调用start()方法
 */
class MyThread2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 3 == 0) {
                System.out.println(Thread.currentThread().getName() + " : " + i);
            }
        }
    }
}
public class ThreadCreation2 {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
        Thread thread = new Thread(myThread2);
        thread.setName("线程一");
        thread.start();
    }
}

3 实现Callable接口

实现Callable接口。— JDK 5.0新增

与使用Runnable相比,Callable功能更强大些相比run()方法强大

  1. call()可以有返回值的,需要借助FutureTask类,比如获取返回结果
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的

Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

  • FutrueTask是Futrue接口的唯一的实现类
  • FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
/**
 * @author FanYi
 * @datetime 2021/1/25 6:13
 * 
 * 实现Callable接口创建线程步骤:
 *     1 创建要给实现Callable接口的类
 *     2 实现接口中的call()方法,并将线程需要完成的任务代码写在方法内
 *     3 创建Callable接口实现类的对象
 *     4 将Callable接口实现类对象作为参数传递到FutureTack构造函数中,创建FutureTask的对象
 *     5 将FutureTask对象作为参数传递到Thread类的构造函数中
 *     6 执行线程的start()方法,启动线程执行
 *     7 如需获取线程返回值,则调用FutureTask对象的get()方法
 */
class PrintEvenAndCalculateSum implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i < 101; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class PrintEvenAndCalculateSumTest {
    public static void main(String[] args) {
        PrintEvenAndCalculateSum thread = new PrintEvenAndCalculateSum();
        FutureTask futureTask = new FutureTask(thread);
        new Thread(futureTask).start();

        Object o = null;
        try {
            // 获取线程返回值。get()方法的返回值和Callable接口中call()方法的返回值一样
            o = futureTask.get();
            System.out.println("1~100内的偶数之和为:" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

4 线程池

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。可以提前创建好多个现场,放入线程池,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

线程池的好处:

  • 提高相应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于线程管理

JDK 5.0 起提供了线程池相关的API:ExecutorService 和 Executors

  • ExecutorService:真正的线程池接口。常见子类为ThreadPoolExecutor,常用方法为:
    void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
    Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
    void shutdown():关闭线程池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。常用方法为:
    Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool():创建一个可重用固定线程数的线程池
    Executors.newSingleThreadPool():创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool():创建一个线程池,它可安排在给定延迟后运行命令或定期地执行
class PrintEven implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + " 打印 " + i);
            }
        }
    }
}
class PrintOdd implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + " 打印 " + i);
            }
        }
    }
}
public class ThreadPoolTest {
    public static void main(String[] args) {
        // 1 创建一个指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 2 执行指定线程的操作。需使用实现Runnable或Callable接口的类对象作为参数
        service.execute(new PrintEven());
        service.execute(new PrintOdd());
        // service.execute(new CallableImpl());

        // 3 关闭线程池
        service.shutdown();
    }
}

2.2 线程的常用方法及线程优先级

package com.fanyi.fy.thread_creation;

/**
 * @author FanYi
 * @datetime 2021/1/24 14:21
 *
 * Thread类常用方法:
 *      1 start() 导致该线程开始执行;java虚拟机调用这个线程的 run方法。
 *      2 run() 将要执行的代码写在其中,让线程执行
 *      3 currentThread() 静态方法,返回当前正在执行的线程对象的引用。
 *      4 getName() 返回此线程的名称。
 *      5 setName(String name) 改变该线程的名称等于参数 name。
 *      6 sleep(long millis) 当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性。
 *      7 join() 在线程a中调用线程b的join方法,则线程a进入阻塞状态,直至线程b执行完毕,线程a结束阻塞再次执行
 *      8 isAlive() 判断当前线程是否存活
 *      9 yield() 当前线程暂时放弃cpu使用权
 *      10 stop() 强制结束当前线程。此方法已过时
 * 线程优先级:
 *      1 线程优先级等级
 *          MAX_PRIORITY 10
 *          MIN_PRIORITY 1
 *          NORM_PRIORITY 5 默认的线程优先级
 *      2 获取和设置线程优先级
 *          getPriority() 获取线程优先级
 *          setPriority(int p) 设置线程优先级
 *      3 Java的优先级调度策略
 *          同优先级线程组成先进先出队列(先到先服务:FCFS),使用时间片策略;
 *          高优先级进程,使用抢占式的优先级调度策略
 *      4 说明
 *          高优先级的线程可抢占低优先级线程的cpu执行权。但只是从概率上将,高优先级的线程获得cpu执行权的概率大,
 *          并不意味着只有高优先级线程执行完毕,低优先级线程才能执行
 */
public class ThreadMethodsTest {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {

                // try {
                //     sleep(2000);
                // } catch (InterruptedException e) {
                //     e.printStackTrace();
                // }

                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + " : " + i);
                }
            }
        };
        thread.setName("线程一");
        System.out.println(thread.getName() +" is alive : " + thread.isAlive());
        thread.start();
        System.out.println(thread.getName() +" is alive : " + thread.isAlive());

        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 100; i++) {
            if (i == 49) {

                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
        System.out.println(thread.getName() +" is alive : " + thread.isAlive());
    }
}

2.3 两种创建线程方式的比较

开发中,优先选择实现Runnable接口创建线程。原因:

  1. 避免了类单继承的局限性
  2. 适合处理多个线程有共享数据的情况,实现Runnable接口有利于资源共享

联系:public class Thread implements Runnable。

相同点:都需要重写run()方法

举例:假如有100张票,三个售票窗口同时卖票。下面通过此例比较两种创建线程方式的异同。

实现Runnable接口

package com.fanyi.fy.thread_creation;

/**
 * @author FanYi
 * @datetime 2021/1/24 15:40
 *
 * 线程创建的两种方式。
 *      开发中,优先选择实现Runnable接口创建线程。原因:
 *          1 避免了类单继承的局限性
 *          2 适合处理多个线程有共享数据的情况
 *      联系:public class Thread implements Runnable
 */
class TicketWindow1 implements Runnable {
    // 实现Runnable接口有利于资源共享,若是继承Thread类,代码应写成 private static int ticket = 100;
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票");
                ticket--;
            } else {
                break;
            }
        }
    }
}
public class TicketWindowTest1 {
    public static void main(String[] args) {
        TicketWindow1 ticketWindow1 = new TicketWindow1();

        Thread thread1 = new Thread(ticketWindow1);
        Thread thread2 = new Thread(ticketWindow1);
        Thread thread3 = new Thread(ticketWindow1);

        thread1.setName("售票窗口1");
        thread2.setName("售票窗口2");
        thread3.setName("售票窗口3");

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

继承Thread类

class TicketWindow2 extends Thread {
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票");
                ticket--;
            } else {
                break;
            }
        }
    }
}

public class TicketWindowTest2 {
    public static void main(String[] args) {
        Thread thread1 = new TicketWindow2();
        Thread thread2 = new TicketWindow2();
        Thread thread3 = new TicketWindow2();

        thread1.setName("售票窗口1");
        thread2.setName("售票窗口2");
        thread3.setName("售票窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
class TicketWindow2 extends Thread {	   		class TicketWindow1 implements Runnable {
    private static int ticket = 100;		   		private int ticket = 100;
    @Override								      @Override
    public void run() {						  	   public void run() {
       //……								              //……
    }										     }
}										      }

public class TicketWindowTest2 {				public class TicketWindowTest1 {
    public static void main(String[] args) {		public static void main(String[] args) {
        											 TicketWindow1 ticketWindow1 = new TicketWindow1();
        
        Thread thread1 = new TicketWindow2();			 Thread thread1 = new Thread(ticketWindow1);
        Thread thread2 = new TicketWindow2();			 Thread thread2 = new Thread(ticketWindow1);
        Thread thread3 = new TicketWindow2();			 Thread thread3 = new Thread(ticketWindow1);

        thread1.setName("售票窗口1");					 thread1.setName("售票窗口1");
        thread2.setName("售票窗口2");					 thread2.setName("售票窗口2");
        thread3.setName("售票窗口3");					 thread3.setName("售票窗口3")

        thread1.start();							   // 其余代码与左侧相同
        thread2.start();
        thread3.start();
    }
}

比较上述代码,class TicketWindow2 extends Thread,若ticket不加static关键字,则为成员变量,三个线程各有一个成员变量,则总共三百份票,很明显是错误的。而class TicketWindow1 implements Runnable,三个线程实现同一个接口,三个线程共享同一个变量ticket。

你可能感兴趣的:(Java,java,多线程)