zoukankan      html  css  js  c++  java
  • java并发编程(一)——进程与线程

    基本概念

    进程:进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

    一个应用程序至少对应着一个进程,对于一些应用程序,如浏览器或者QQ,允许启动多个同一应用程序,会对应多个进程。

    每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是系统资源分配和调度的最小单位

    线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。如360安全卫士可以同时进行病毒查杀、加速、电脑清理等不同的子任务,每一个子任务都对应着一个线程。一个应用程序运行起来之后,一定会首先创建一个首要线程-,称作主线程。

    一个线程必须拥有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源(进程是系统资源分配和调度的最小单位)。它与父进程的其他线程共享该进程的堆和方法区资源。

    线程是cpu调度的最小单位,是比进程更小的独立运行的单位。线程之间切换的开销小,所以线程也称为轻量级进程。

    堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    进程与线程的区别:

    1、线程和进程最大的不同在于各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。

    2、一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。

    3、进程切换时消耗的资源大,线程切换消耗的资源较小。

    4、进程是系统资源分配和调度的最小单位,线程是CPU调度的最小单位。

     线程的创建

    java中创建线程有两种方式,一种是继承Thread类,第二种是实现Runnable接口。继承Thread类是将线程和任务合在一起,实现Runable接口是将线程和任务分开。

    /**继承Thread类
     * 1、创建一个类继承Thread类,重写run方法,run方法中的内容即是线程需要执行的任务
    * 2、创建子类的实例,即创建了一个线程对象 * 3、调用该线程对象的start方法启动线程,会使线程执行run方法中的内容
    */ public class MyThread extends Thread{ @Override public void run(){ for (int a=0; a<5; a++){ System.out.println("thread========="); } } public static void main(String args[]){ MyThread myThread = new MyThread(); myThread.start(); } }
    /**实现Runnable接口
     * 1、创建一个类实现Runnable接口,重写run方法
     * 2、将之前Runnable的实现类的对象作为参数,创建一个Thread类,该Thread类即为线程对象
     * 3.调用线程对象的start方法启动线程,就会让线层执行run方法中的内容
     */
    public class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int a=0; a<5; a++){
                System.out.println("thread=========");
            }
        }
    
        public static void main(String args[]){
            MyRunnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            thread.start();
        }
    }
    /**
     * 通过匿名内部类创建线程
     */
    public class Test extends Thread{
    
        public static void main(String args[]){
            
            new Thread(){
                @Override
                public void run(){
                    System.out.println("=========");
                }
            }.start();
            
            //其实是向上转型,实质是 Runnable runnable = new RunnableImpl();
            Runnable runnable = new Runnable(){
                @Override
                public void run() {
                    System.out.println("=========");
                }
            };
            new Thread(runnable).start();
        }
    }
    /**使用Lambda创建线程,其实就是简化代码
     * Lambda表达式的标准格式有三部分给组成
     *  1、一些参数,用()括起来
     *  2、一个箭头,传递的意思,把前面的参数传递个后面的代码
     *  3、一段代码,用{}括起来的代码块,重写接口抽象方法的方法体
     * */
    
    public class LambdaTest {
    
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("匿名内部类创建线程");
                }
            }).start();
    
            //使用Lambda表达式
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "正在执行");
            }).start();
        }
    }

    实现Runnable接口比继承Thread类所具有的优势:

    1、适合多个相同的程序代码的线程去处理同一个资源

    2、可以避免java中的单继承的限制

    3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

    4、线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

    线程中的常用方法

    1、void start(),该方法使线程处于可执行状态,但不一定马上执行,需要和其他线程一起抢占CPU资源。

    2、String getName(),通过该方法可以获取线程的名字,该方法返回值为String。mt.getName(),获取线程mt的名字。默认主线程的名字是main,其他线程的名字是Thread-0,Thread-1,Thread-2,。。。。

    3、void setName(String name),设置线程名称,该方法返回值为void。mt.setName("myThread"),设置线程名称为myThread。

    4、static Thread currentThread(),通过该方法可以获取当前CPU执行的是哪个线程这是Thread类的一个静态方法,该方法返回值为Thread。Thread.currentThread()。

    5、static void sleep(long millis),使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),这是一个静态方法,返回值为void。Thread.sleep(100),是当前线程暂停100毫秒 

    6、void join(),join()方法的作用是等待该线程结束(正常执行完或者异常结束),如果在b线程里面使用a.join(),可以理解为将a线程加入b线程,这样b线程执行的时候就只能等a线程执行完。该方法可以设置最多等待多少毫秒,join(1000),最多等待1000毫秒。

    class JoinTest {
        public static void main(String[] args) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Thread a = new Thread(()->{
                    try {
                        System.out.println(Thread.currentThread().getName() + "开始执行");
                        Thread.sleep(2000);
                        System.out.println(Thread.currentThread().getName() + "执行结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                },"线程2");
                a.start();
                try {
                    a.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "执行结束");
            },"线程1").start();
        }
    }
    
    /**运行结果
     * 线程1开始执行
     * 线程2开始执行
     * 线程2执行结束
     * 线程1执行结束
     * */

    7、static void yield(),暂停当前线程,使其让出当前正在使用的处理器回到可执行状态。需要注意的是,即使线程调用了yield(),让出了正在使用的处理器,该线程还是会和其他同一优先级的线程再次抢占CPU资源,也就是说有可能该线程执行了yield()后会再次执行。sleep()会使线程进入不可执行状态,指定毫秒数内不执行,而yield()不一定会使线程停止执行。

    8、void interrupt(),中断一个线程,该方法通常会与isInterrupted()配合使用。注意,中断使用了sleep,wait,join这类阻塞状态的线程,isInterrupted()的返回值是false,而中断正常运行的线程,isInterrupted()的返回值为true,通过判断返回值来确定是否确实要中断该进程。

    守护线程

    java程序启动时,第一个创建的线程是主线程,之后再创建其他的线程,只要还有一个线程没有结束,这个java应用程序对应的进程就不会结束,即使主线程已经结束了。如果将一个线程设置为守护线程,那么当其他的非守护线程执行完后,不论该守护线程是否执行完毕,都会强制结束。使用 t.setDaemon(true) 将线程t设置为守护线程。

    垃圾回收器线程就是一个守护线程

    线程的生命周期

    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多个状态之间切换。

    线程的五种状态——从操作系统角度描述

    在线程的生命周期中,它要经过新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)和死亡(Dead) 这 5 种状态。

    新建状态(New):当程序使用 new 关键字创建了一个线程对象后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。

    就绪状态(Runnable):当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。

    运行状态(Running):如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。

    阻塞状态(Blocked):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,暂时停止运行。直到线程进入可运行状态(runnable),才有机会再次获得 cpu 时间片 转到运行状态(running)。阻塞状态分为三种:

    1、等待阻塞:运行的线程执行wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。(wait会释放持有的锁)

    2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。

    3、其他阻塞:运行的线程执行sleep()方法或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。(sleep不会释放持有的锁)

    死亡状态(Dead):线程执行完后正常结束或者因异常退出了run()方法,该线程结束生命周期。

    线程的六种状态——从java API层面描述

    从java API角度来描述线程可以分为六种状态,新建状态(NEW),运行状态(RUNNABLE),等待状态(WAITING),超时等待(TIMED_WAIING),阻塞状态(BLOCKED),终止状态(TERMINATED)。这六种状态的划分其实是根据类Thread.state枚举得到的。注意:这里的RUNNABLE状态包括了操作系统层面的就绪状态和运行状态,甚至还包括了某些原因引起的阻塞状态(如BIO导致的线程阻塞在java中无法区分,仍然认为是可运行状态).。BLOCKED、WAITING、TIMED_WAIING都是java API对操作系统阻塞状态的细分。

    修正:原图中 wait到 runnable状态的转换中,join实际上是Thread类的方法,但这里写成了Object

    参考资料

    Java多线程学习(吐血超详细总结)

    Java 并发基础常见面试题总结

  • 相关阅读:
    ubuntu下安装oracle
    网站框架策划时的小技巧--页面原型篇
    中国电商价格欺诈何时休?
    系统升级日记(4):如何快速的修改Infopath中的各种URL
    系统升级日记(3)- 升级SharePoint解决方案和Infopath
    系统升级日记(2)- 升级到SharePoint Server 2013
    系统升级日记(1)- 升级到SQL Server 2012
    【译】《C# Tips -- Write Better C#》
    [.NET] 一步步打造一个简单的 MVC 电商网站
    反骨仔的 2016 年度全文目录索引
  • 原文地址:https://www.cnblogs.com/Zz-feng/p/13144182.html
Copyright © 2011-2022 走看看