JAVA学习经验与总结(11)多线程(上)

一 进程概述及多进程的意义

1 什么是进程

我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们
运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令,那么这个运行
中的程序,就被称为「进程」。
现在我们考虑有一个会读取硬盘文件数据的程序被执行了,那么当运行到读取文件的指令时,就会
去从硬盘读取数据,但是硬盘的读写速度是非常慢的,那么在这个时候,如果 CPU 傻傻的等硬盘返回
数据的话,那 CPU 的利用率是非常低的。
做个类比,你去煮开水时,你会傻傻的等水壶烧开吗?很明显,小孩也不会傻等。我们可以在水壶
烧开之前去做其他事情。当水壶烧开了,我们自然就会听到“嘀嘀嘀”的声音,于是再把烧开的水倒入到
水杯里就好了。
所以,当进程要从硬盘读取数据时,CPU 不需要阻塞等待数据的返回,而是去执行另外的进程。当
硬盘数据返回时,CPU 会收到个中断,于是 CPU 再继续运行这个进程。

2 多进程的意义

单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。

二 线程概述及多线程的意义

1 什么是线程

在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。

2 多线程的意义

线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
那么怎么理解这个问题呢?
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.

三 Java程序运行原理和JVM的启动是多线程的吗

1 Java程序运行原理

Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。

2 jvm的启动是多线程的嘛

JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

四 多线程的实现方式

1 如何实现多线程

由于线程是依赖进程而存在的,所以我们应该先创建一个进程(JVM)出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。

2 多线程的实现方式1

a:继承Thread类
b:步骤及代码演示
c:几个小问题:
启动线程使用的是那个方法
线程能不能多次启动
run()和start()方法的区别
我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。

 为什么要重写run方法?

这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定
那么也就是run方法中封装应该是必须被线程执行的代码.

run方法中的代码的书写原则: 一般是比较耗时的代码
案例演示

public class MyTest {
    public static void main(String[] args) {
        // 类 Thread 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
        //创建新执行线程有两种方法。
        //1.一种方法是将类声明为 Thread 的子类。
        // 2.该子类应重写 Thread 类的 run 方法。
        //3.接下来可以分配并启动该子类的实例。

        MyThread th= new MyThread();

        //th.run(); 你直接调用run方法,并没有什么线程创建出来,只是普通的new对象调用方法而已,这些代码还是运行在主线程。
        //正确开启线程的方法。
        //多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
        th.start();
        //th.start();

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

    }
}
public class MyThread extends Thread{

    //run 方法里面的代码,将来有线程来执行的,一般我们会在run方法里面执行一些耗时的代码。
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }

    public void show(){
        System.out.println("abc");
    }
}

3 获取和设置线程对象名称

A:Thread类的基本获取和设置方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称
其实通过构造方法也可以给线程起名字
思考:
如何获取main方法所在的线程名称呢?
public static Thread currentThread()//获取当前执行的线程
我们现在是想获取主线程的名称,那么我们可不可以先获取到主线程,
如果我们能获取到主线程,那么我们就可以调用getName方法获取对应的名称.
如何获取主线程呢? public static Thread currentThread()返回对当前正在执行的线程对象的引用。
案例演示

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(this.getName()+"耗时的循环操作");
        }
    }
}

public class MyTest2 {
    public static void main(String[] args) {
        System.out.println("主线程执行了");
        System.out.println("代码开始执行了");
        //开启了一个子线程
        MyThread th = new MyThread();
        th.start();

        System.out.println("下面的代码");
        System.out.println("下面的代码");
        System.out.println("下面的代码");
        System.out.println("下面的代码");
        System.out.println("下面的代码");
        System.out.println("下面的代码");
    }
}

4 线程调度及获取和设置线程优先级

A:线程的执行
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
B:线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
C:如何设置和获取线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级
D:案例演示: 获取和设置线程优先级
注意事项: 有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?

  • 因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,
  • 所以有的时候一两次的运行说明不了问题

5 线程休眠

线程休眠: public static void sleep(long millis) 线程休眠
案例演示

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始进来执行了");
        //让当前线程线程休眠一下
        //Thread.sleep(3000);


        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        th1.start();
        //th2.start();


        System.out.println("下面的代码");
    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

6 线程控制之加入线程

加入线程: public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法
案例演示

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();
        //join() 可以让多个线程从并发执行,变成串行。
        th1.setName("刘备");
        th2.setName("关羽");
        th3.setName("张飞");
        th1.start();
        th1.join();
        th2.start();
        th2.join();
        th3.start();
        th3.join();

    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

7 线程控制之礼让线程

A:礼让线程: public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
B:案例演示: 礼让线程
按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.
那是为什么呢?
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.

public class MyThread extends Thread{
    @Override
    public void run() {
        Thread.yield(); //线程礼让
        for (int i = 0; i < 100; i++) {

            System.out.println(this.getName()+"=="+i);
        }
    }
}
public class MyThread extends Thread{
    @Override
    public void run() {
        Thread.yield(); //线程礼让
        for (int i = 0; i < 100; i++) {

            System.out.println(this.getName()+"=="+i);
        }
    }
}

8 线程控制之守护线程

A:守护线程: public final void setDemon(boolean on):
将该线程标记为守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
Java用户线程和守护线程1.用户线程和守护线程的区别 用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。 2.用户线程和守护线程的适用场景 由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。 3.创建守护线程 调用线程对象的方法setDaemon(true),设置线程为守护线程。 1)thread.setDaemon(true)必须在thread.start()之前设置。 2)在Daemon线程中产生的新线程也是Daemon的。 3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。 因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。 4.Java守护线程和Linux守护进程 两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。 在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。
B:案例演示: 守护线程

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始执行了");
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        th1.setName("张飞");
        th2.setName("关羽");
        th1.setDaemon(true);
        th2.setDaemon(true);
        th1.start();
        th2.start();

        System.out.println("主线程执行完毕");
    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

9 线程控制之中断线程

A:中断线程
public final void stop(): 停止线程的运行
public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞`

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始执行了");
        MyThread th1 = new MyThread();
        th1.setName("张飞");
        th1.start();
        Thread.sleep(1);
        th1.stop(); //强制终止线程。
        System.out.println("主线程执行完毕");
    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(this.getName()+"=="+i);
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值