JavaSE进阶06-多线程

01 多线程的基本概念

线程指进程中的一个执行场景,即执行流程。进程与线程的区别:

  • 每个进程是一个应用程序,都有独立的内存空间
  • 同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的栈内存)

1.1 什么是进程

  一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。如:QQ、网易云等。

1.2 系统引入多进程的作用

提高CPU的使用率。
重点:进程与进程之前的内存相互独立。

1.3 什么是线程

线程是进程的一个执行场景,一个进程可以启动多个线程。

1.4 进程引入多线程的作用

提高进程的使用率。
重点:线程和线程之间栈内存独立,堆内存和方法区内存共享,一个线程一个栈。

1.5 描述Java程序的执行原理

  Java命令执行会启动JVM,JVM的启动表示启动一个应用程序即启动一个进程。该进程会自动启动一个“主线程”,然后主线程负责调用某个类的main方法。main方法的执行是在主线程中执行的,然后通过main方法代码的执行可以启动其他的“分支线程”。所以main方法结束程序不一定结束,因为其他的支线分支有可能还在执行。

02 线程的创建和启动

Java虚拟机的主线程入口是main方法,用户可以自己创建线程,创建方式有两种:

  • 继承Thread
  • 实现Runnable接口(推荐使用)

2.1 继承Thread类

  Thread类中创建线程最重要的两个方法为public void run()public void start()。采用Thread类创建线程,用户只需要继承Thread,重写Threadrun()方法,父类Thread中的run()方法没有抛出异常,那么子类也不能抛出异常,最后采用start()启动线程即可。

public class ThreadTest {
    public static void main(String[] args) {
        Processor p=new Processor();
        p.start();
        method1();
    }

    private static void method1(){
        System.out.println("------method1()-------");
    }
}

class Processor extends Thread{
    // 重写Thread中的run方法
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
    }
}

输出结果为:

------method1()-------
0
1
2
3
4

  以上输出并没有顺序执行,从效率上看,采用多线程的示例要快一些。method1并没有等待前面的操作完成才执行,称为“异步编程模型”

2.2 实现Runnable接口

  Thread对象本身实现了Runnable接口,但一般建议直接使用Runnable接口来写多线程,因为接口比类更好用。

public class ThreadTest {
    public static void main(String[] args) {
        Runnable r1=new Processor();
        // 不能直接调用run
        Thread t1=new Thread(r1);
        // 启动线程
        t1.start();

        method1();
    }
    private static void method1(){
        System.out.println("----method1()------");
    }
}

class Processor implements Runnable{
    // 实现Runnable中的run方法
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
    }
}

03 线程的生命周期

线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡
在这里插入图片描述

  • 新建:采用new语句创建完成
  • 就绪:执行start后
  • 运行:占用CPU时间
  • 阻塞:执行了wait语句、执行了sleep语句和等待某个对象锁,等待输入的场合
  • 终止:退出run()方法
    在这里插入图片描述

04 线程的调度与控制

  通常我们的计算机只有一个 CPU,CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有在多个 CPU 上线程才可以并行运行。Java 虚拟机要负责线程的调度,取得 CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。

  • 分时调度模型:
     所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型:
     优先级高的线程获取 CPU 的时间片相对多一些,如果线程的优先级相同,那么会随机选择一个

4.1 线程优先级

  线程优先级主要分三种:MAX_PRIORITY( 最高级 );MIN_PRIORITY(最低级); NOM_PRIORITY(标准)默认

public class ThreadTest {
    public static void main(String[] args) {
        Runnable r1=new Processor();

        Thread t1=new Thread(r1,"t1");
        // 设置线程的优先级,线程启动后不能再设置优先级
        t1.setPriority(Thread.MAX_PRIORITY);
        // 启动线程
        t1.start();

        Thread t2=new Thread(r1,"t2");
        // 设置最低优先级
        t2.setPriority(Thread.MIN_PRIORITY);
        t2.start();
        System.out.println(Thread.currentThread().getName());
    }

}

class Processor implements Runnable{
    // 实现Runnable中的run方法
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }
    }
}

优先级高的线程(t1)会抢占到更多的CPU时间片,优先执行完成

4.2 Thread.sleep

  sleep 设置休眠的时间,单位毫秒,当一个线程遇到 sleep 的时候,就会睡眠,进入到阻塞状态,放弃 CPU,腾出 cpu 时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得 CPU 时间片,当睡眠时间到达了,线程会进入可运行状态,得到 CPU 时间片继续执行,如果线程在睡眠状态被中断了,将会抛出 IterruptedException

class Processor implements Runnable{
    // 实现Runnable中的run方法
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
            if (i%10==0){
                try{
                    // 睡眠100毫秒,将时间片交给其他线程使用
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

4.3 Thread.yield

它与 sleep()类似,只是不能由用户指定暂停多长时间,并且 yield()方法只能让同优先级的线程有执行的机会

class Processor implements Runnable{
    // 实现Runnable中的run方法
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
            if (i%10==0) {
                System.out.println("------------");
                // 采用yield可以将CPU的使用权让给同一个优先级的线程
                Thread.yield();
            }
        }
    }
}

4.4 t.join()

当前线程可以调用另一个线程的 join 方法,调用后当前线程会被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会执行。

4.5 interrupt(中断)

如果我们的线程正在睡眠,可以采用 interrupt 进行中断

4.6 如何正确停止一个线程

通常定义一个标记来判断标记的状态停止线程的执行

public class ThreadTest {
    public static void main(String[] args) {
        Processor r1=new Processor();
        Thread t1=new Thread(r1,"t1");
        t1.start();

        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 停止线程
        r1.setFlag(true);
    }

}

class Processor implements Runnable{
    // 线程停止标记,true为停止
    private boolean flag;
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
            // 为true停止线程执行
            if(flag){
                break;
            }
        }
    }
    public void setFlag(boolean flag){
        this.flag=flag;
    }
}

05 线程的同步(加锁)

实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
  局部变量永远都不会存在线程安全问题。
  因为局部变量不共享。(一个线程一个栈)
  局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。

局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。

5.1 为什么需要同步

  • 共享一个对象启动两个线程,s为每个线程的局部变量
public class ThreadTest {
    public static void main(String[] args) {
        Processor r1=new Processor();
        Thread t1=new Thread(r1,"t1");
        t1.start();

        Thread t2=new Thread(r1,"t2");
        t2.start();
    }

}

class Processor implements Runnable{
    @Override
    public void run() {
        // 定义局部变量s,作为累加变量
        int s=0;
        for (int i = 0; i < 10; i++) {
            s+=i;
        }
        System.out.println(Thread.currentThread().getName()+","+s);
    }
}

输出:

t1,45
t2,45

以上t1和t2并发执行,s为每个线程的局部变量,位于各自的栈帧中,因为栈帧中的数据是互不干扰的,所以计算结果都为45

  • 共享一个对象启动两个线程,s为每个线程的成员变量
class Processor implements Runnable{
    // 定义成员变量s,作为累加变量
    int s=0;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            s+=i;
        }
        System.out.println(Thread.currentThread().getName()+","+s);
    }
}

输出:

t1,45
t2,90

  因为共享同一对象的成员变量,两个线程同时对其进行操作,所以出现了问题,此时称Processor为线程不安全的。想得到正确结果,必须采用线程同步,加锁,该变量不能共享使用。
线程安全问题:

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改行为

5.2 使用线程同步

  线程同步指某一时刻允许一个线程来访问共享资源,其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法。
采用线程同步解决上面的问题:

class Processor implements Runnable{
    // 定义局部变量s,作为累加变量
    int s=0;
    public void run(){
        // 使用同步块
        synchronized (this){
            for (int i = 0; i < 10; i++) {
                s+=i;
            }
            System.out.println(Thread.currentThread().getName()+","+s);
            s=0;
        }
    }
}

  synchronized是对对象加锁,优先考虑synchronized 同步块,因为同步的代码越多,其他线程等待的时间就会越长影响效率。
synchronized的三种写法

  1. 同步代码块
    synchronized(线程共享对象){
    同步代码块;
    }
  2. 在实例方法上使用synchronized
    表示共享对象一定是this
    并且同步代码块是整个方法体。
  3. 在静态方法上使用synchronized
    表示找类锁。
    类锁永远只有1把。
    就算创建了100个对象,那类锁也只有一把。

5.3 为每一个线程创建一个对象来解决线程安全问题

public class ThreadTest {
    public static void main(String[] args) {
        Processor r1=new Processor();
        Thread t1=new Thread(r1,"t1");
        t1.start();

        // 再次创建Processor对象
        Processor r2=new Processor();
        Thread t2=new Thread(r2,"t2");
        t2.start();
    }

}

class Processor implements Runnable{
    // 定义局部变量s,作为累加变量
    int s=0;
    public void run(){
        for (int i = 0; i < 10; i++) {
            s+=i;
        }
        System.out.println(Thread.currentThread().getName()+","+s);
    }
}

  每个线程操作都是自己的对象,没有操作共享的资源
在这里插入图片描述

如何解决线程安全问题?

  1. 尽量使用局部变量代替“实例变量和静态变量”
  2. 如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存不共享
  3. 不能使用局部变量及对象不能创建多个时,考虑synchronized

06 守护线程

  从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。所有的用户线程结束生命周期后,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束。例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。

6.1 用户线程

public class ThreadTest {
    public static void main(String[] args) {
        Processor r1=new Processor();
        Thread t1=new Thread(r1,"t1");
        t1.start();

        for (int i = 0; i <5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }

        System.out.println("主线程结束!!!");
    }

}

class Processor implements Runnable{

    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }

    }
}
main,0
main,1
main,2
t1,0
main,3
main,4
主线程结束!!!
t1,1
t1,2
t1,3
t1,4

主线程执行结束后,用户线程仍然在执行

6.2 守护线程(服务线程)

public class ThreadTest {
    public static void main(String[] args) {
        Processor r1=new Processor();
        Thread t1=new Thread(r1,"t1");
        // 将当前线程修改为守护线程
        t1.setDaemon(true);
        t1.start();

        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }

        System.out.println("主线程结束!!!");
    }

}

class Processor implements Runnable{

    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+","+i);
        }

    }
}

输出:


main,0
main,1
t1,0
main,2
t1,1
main,3
t1,2
main,4
t1,3
主线程结束!!!
t1,4

当主线程结束后,守护线程并没有把所有数据输出完就结束了。
**结论:**当用户线程全部结束,守护线程会自动结束

07 Timer定时器

08 Window定时器(任务)

09 重点掌握

  1. 进程与线程的概念
    2. 线程的两种实现方式(Thread,Runnable
  2. 了解线程的优先级
  3. sleep 的含义
  4. 如果正确的结束一个线程
  5. 线程同步的含义(同步共享资源,局部变量不存在共享的问题)
  6. 守护线程的概念
  7. 了解 Timer
  8. 了解 winodw提供的计划
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值