《JAVA从入门到??》Java多线程DAY01

一、多线程基础

在之前的学习过程中,程序在没有跳转语句的前提下,都是从上至下依次执行。那现在要想设计一个程序,一边敲代码、一边听歌,怎么设计?
要想解决上述问题,咱们得使用多进程或多线程来解决。
1.1 相关的概念
并行与并发:
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。

进程与线程:
线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

线程调度:
分时调度:所有线程轮流使用CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
设置线程的优先级:

抢占式调度详解:大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是 在同时运行,”感觉这些软件好像在同一时刻运行着“。 实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
1.2 多线程好处
充分利用CPU的资源
简化编程模型
带来良好的用户体验
多个线程之间互不干扰

1.3 主线程
之前学习的Java程序中只有一个线程,从main方法开始,从上到下依次执行。这种程序我们又称之为单线程。
public class MainThreadExample01 {
public static void main(String[] args) {
Car car1 = new Car(“宝马”);
car1.run();
int i = 1 / 0;
Car car2 = new Car(“奔驰”);
car2.run();
}
}

1.4 创建线程常用2种方式
1.4.1 继承Thread类
Java使用 java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的
步骤如下:

  1. 定义Thread类的子类
  2. 并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
  3. 创建Thread子类的实例,即创建了线程对象。
  4. 调用线程对象的start()方法来启动该线程。
    语法格式:
    class xxx extends Thread{
    重写run(){
    }
    }

代码示例:

public class ThreadExample01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 20; i++){
            System.out.println("main:"+i);
        }
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++){
            System.out.println("MyThread:"+i);
        }
    }
}

代码总结:两个线程,一个main线程,一个新线程,一起抢夺CPU执行权(时间片),谁抢到了就执行谁对应的代码。

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

如上内容中我们已经可以完成最基本的线程开启,那么在我们完成操作过程中用到了 java.lang.Thread类, API中该类中定义了有关线程的一些方法,具体如下:

构造方法:
public Thread() :分配一个新的 Thread对象。。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName() :获取当前线程名称。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
public void setName(String name):将此线程的名称更改为等于参数 name 。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run() :此线程要执行的任务在此处定义代码。
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

1.4.2 实现Runnable接口
采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。
    语法格式:
    class xxx implements Runnable{
    实现run(){
    }
    }
    代码示例:
public class RunnableExample01 {
    public static void main(String[] args) {
        //创建自定义类对象线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "奔驰");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("宝马 " + i);
        }
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread 对象的start()方法来运行多线程代码。 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:

  1. 可以避免java中的单继承的局限性。
  2. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
  4. 适合多个相同的程序代码的线程去共享同一个资源。

扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

匿名内部类方式实现线程的创建
使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法。

public class NoNameInnerClassThread {
    public static void main(String[] args) {
        new Thread(){
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    System.out.println(Thread.currentThread().getName()+"====Java开发");
                }
            }
        }.start();
        //线程Runnable方式
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    System.out.println(Thread.currentThread().getName()+"===Java程序员");
                }
            }
        };
        new Thread(r).start();
        //简化
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    System.out.println(Thread.currentThread().getName()+"===哈哈哈");
                }
            }
        }).start();
    }
}

1.5 线程生命周期
在这里插入图片描述

1.新建
new关键字创建了一个线程之后,该线程就处于新建状态
JVM为线程分配内存,初始化成员变量值

2.就绪
当线程对象调用了start()方法之后,该线程处于就绪状态
JVM为线程创建方法栈和程序计数器,等待线程调度器调度

3.运行
就绪状态的线程获得CPU资源,开始运行run()方法,该线程进入运行状态

4.阻塞
当发生如下情况时,线程将会进入阻塞状态
线程调用sleep()方法主动放弃所占用的处理器资源
线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
线程试图获得一个同步锁(同步监视器),但该同步锁正被其他线程所持有。
线程在等待某个通知(notify)
程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

5.死亡
线程会以如下3种方式结束,结束后就处于死亡状态:
run()或call()方法执行完成,线程正常结束。
线程抛出一个未捕获的Exception或Error。
调用该线程stop()方法来结束该线程,该方法容易导致死锁,不推荐使用。

1.6 线程分配cpu的使用权(常用方法)
在这里插入图片描述

Java 中的线程优先级是在 Thread 类中定义的常量
NORM_PRIORITY : 值为 5
MAX_PRIORITY : 值为 10
MIN_PRIORITY : 值为 1
缺省优先级为 NORM_PRIORITY
有关优先级的方法有两个:
final void setPriority(int newp) : 修改线程的当前优先级
final int getPriority() : 返回线程的优先级

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值