Java基础进阶_day15_(多线程)

本文详细介绍了Java中的多线程概念,包括进程、线程、并行与并发的区分,以及JVM中的线程调度模型。讲解了Java创建线程的三种方式,如继承Thread类、实现Runnable接口和匿名内部类,并通过实例代码展示了线程优先级、线程控制等操作。此外,文章还深入探讨了多线程同步的重要性,包括多线程安全问题、解决方法和同步特点,提供了具体的同步策略选择及案例代码。
摘要由CSDN通过智能技术生成

Java基础进阶_day15_(多线程)

1.多线程

1.1进程

正在运行的程序,是系统进行资源分配和调用的独立单位;
每一个进程都有它自己的内存空间和系统资源.

# 多进程的作用:
    多进程的作用不是提高执行速度,而是提高CPU的使用率.
1.2线程

是进程中的单个顺序控制流,是一条执行路径;
一个进程如果只有一条执行路径,则称为单线程程序;
一个进程如果有多条执行路径,则称为多线程程序.

# 多线程的作用:
    多线程的作用不是提高执行速度,而是为了提高应用程序的使用率;
    线程运行具有随机性.
1.3并行和并发
# 并行是逻辑上同时发生,指在某一个时间内同时运行多个程序。
# 并发是物理上同时发生,指在某一个时间点同时运行多个程序。
1.4 jvm运行的线程

JVM虚拟机进程启动时至少启动了运行程序的主线程和垃圾回收的线程,属于多线程.

1.5 线程调度模型
# 线程调度模型:两种线程调度模型
    分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片;
    抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些.
# Java使用的是抢占式调度模型.
1.6线程生命周期
# 线程的生命周期:
1.新建:创建线程对象
2.就绪:线程具有执行资格,没有执行权
3.运行:线程抢占到CPU资源,具有执行资格,有执行权
    阻塞:执行sleep()或wait()或其他原因使得线程处于该状态,没有执行资格,没有执行权其他的有些操作可以将该线程激活,使该线程回转到就绪状态
4.死亡:线程对象变为垃圾,等待垃圾回收器的回收

线程的生命周期

1.7 Java中创建线程的方式
1.7.1继承Thread类

继承Thread类,重写run()方法,使用start()方法启动线程.

run()方法和start()方法的区别

# run()方法只是封装需要开启线程进行执行的代码,本身不能开启线程,单独调用时和普通方法调用效果相同;
# start()方法是开启线程后,由jvm调用run()方法,并且一个线程只能启动一次,不能启动多次(否则会报异常).

获取和设置线程名称

* 可以通过有参构造方法设置线程的名称:
    public Thread(String name):创建线程名称为name的线程对象;
    public final String getName():返回该线程的名称;
    public final void setName(String name):将线程名称改为name.
* 获取调用当前程序的线程的名称:
    public static Thread currentThread():返回对当前正在执行的线程对象的引用. 

案例代码1

public class My_Thread_Demo01 {
    public static void main(String[] args) {
        // 创建对象,启动线程1
        MyThread mt = new MyThread();
        mt.start();
        // 创建对象,启动线程2
        MyThread mt2 = new MyThread();
        mt2.start();
        // 设置线程的名称
        mt.setName("线程1");
        mt2.setName("线程2");
        // 获取调用当前程序的线程
        // 获取main方法执行的线程
        System.out.println(Thread.currentThread().getName());
    }
}
// 创建线程方式1
class MyThread extends Thread {
    // 重写run方法,定义需要在线程中执行的代码
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+":"+i);
        }
    }
}

线程优先级

# 线程优先级:默认的线程优先级是5,范围是[1,10]之间的整数,数值越大优先级越高.
        public final void setPriority(int newPriority):更改线程的优先级为newPriority;
    public final int getPriority():返回线程的优先级.
# 注意事项:线程的优先级只是代表线程被执行的几率高,一般在多次的调用中效果较为明显.

案例代码2

public class My_Thread_Demo02 {
    public static void main(String[] args) {
        MyThread2 mt1 = new MyThread2();
        MyThread2 mt2 = new MyThread2();
        MyThread2 mt3 = new MyThread2();
        mt1.setName("线程1");
        mt2.setName("线程2");
        mt3.setName("线程3");
        // 设置线程优先级
        mt1.setPriority(1);
        mt1.setPriority(5);
        mt1.setPriority(9);
        // 启动线程
        mt1.start();
        mt2.start();
        mt3.start();
        // 获取线程优先级
        System.out.println(mt1.getPriority());
        System.out.println(mt2.getPriority());
        System.out.println(mt3.getPriority());
    }
}

线程控制

# 线程控制:
 * 线程休眠:在线程的run()方法中通过Thread直接调用
        public static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠.
 * 线程加入:该线程执行完毕后其他的线程才能执行
        public final void join():等待该线程执行完毕后,其他的线程才能执行.
 * 线程礼让:在线程的run()方法中调用,是让同一个线程类不同对象间线程的执行尽可能的均衡化.
        public static void yield():暂停当前正在执行的线程对象,并执行其他线程. 
 * 后台线程:就是主线程执行完后,后天线程(守护线程)就会终止(可能会守护线程会再执行一段时间,但不会执行完);
        public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,Java虚拟机退出,该方法必须在启动线程前调用. 
 * 中断线程:
        public final void stop():中断线程,是直接清除线程的状态,被终止的线程中未执行的代码不能再执行;
        public void interrupt():中断线程,是清除线程的状态,并抛出异常,线程中未被执行的代码还可以继续执行.

案例代码3

public class My_Thread_Demo03 {
    public static void main(String[] args) {
        // 测试线程休眠,线程加入,线程礼让,后台线程
        // test01();
        // 测试中断线程
        MyThread3 mt1 = new MyThread3();
        mt1.start();
        // 当mt1休眠时间操作5秒就中断
        try {
            Thread.sleep(5000);
            // mt1.stop();
            mt1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void test01() {
        MyThread3 mt1 = new MyThread3();
        MyThread3 mt2 = new MyThread3();
        MyThread3 mt3 = new MyThread3();
        mt1.setName("线程1");
        mt2.setName("线程2");
        mt3.setName("线程3");
        // 设置mt2和mt3为守护线程,当mt1线程执行完后,mt2和mt3会在终止执行(在继续执行一段时间后)
        mt2.setDaemon(true);
        mt3.setDaemon(true);
        mt1.start();
        // 将线程1加入
        /*try {
            mt1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        mt2.start();
        mt3.start();
    }
}
class MyThread3 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程开始执行");
            System.out.println(this.getName() +":" + i);
            // 使线程休眠1秒
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println("线程被中断了");
            }
            // 线程礼让
            // Thread.yield();
            System.out.println("线程继续执行");
        }
    }
}
1.7.2 实现Runnable接口
# 创建线程:类实现Runnable接口,并实现run方法.
 * 创建线程的另一种方法是声明实现Runnable接口的类,该类然后实现run方法,
 * 然后可以分配该类的实例,在创建Thread时作为一个参数来传递并调用start()方法启动线程.
# 接口方式的线程的作用:
 * A:可以避免由于Java单继承带来的局限性(继承其他类的同时可以实现线程接口);
 * B:适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想.实现线程接口的类只创建一个对象,但是可以将这个对象作为多个线程实例的参数,则多个线程操作一个对象中的数据.

案例代码

public class My_Runnable_Demo01 {
    public static void main(String[] args) {
        MyThread4 mt = new MyThread4();
        Thread t1 = new Thread(mt, "线程1");
        t1.start();
        Thread t2 = new Thread(mt, "线程2");
        t2.start();
    }
}
class MyThread4 implements Runnable {
    // 重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // 这种方式创建的线程不能直接获取线程的名字,可以通过Thread.currentThread()获取当前执行的线程
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
1.7.3 匿名内部类的方式实现线程
public class My_Thread_Demo05 {
    public static void main(String[] args) {
        // 方式1
        // 匿名子类和匿名接口方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }).start();
        // 方式2
        // 匿名子类实现
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }.start();
        // 方式3
        // 输出的结果是python的,匿名子类对象的方法将接口中的run方法覆盖掉
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("java"+":"+i);
                }
            }
        }) {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("python"+":"+i);
                }
            }
        }.start();
    }
}

2.多线程同步

2.1 多线程安全问题
#CPU的一次操作必须是原子性的:当操作数据不是原子性(多个语句操作同一个数据),可能出现一个线程在完成数据操作的所有语句前,其他的线程就来操作数据;
 * 如:线程A和线程B,操作共享数据的有a和b两条语句,
 * 当A操作完数据的a语句还没操作语句b时,线程B进来操作数据,
 * 由于A的b语句操作没有完成,共享数据没有发生变化,当B线程执行完b语句时,A线程再执行使用的还是之前的数据,则线程A和线程B的操作的数据的结果相同.
# 线程的执行具有随机性和一定的延迟.
2.2 多线程安全问题判断依据
# A:是否是多线程;
# B:是否存在共享数据;
# C:是否多条语句操作共享数据.
2.3 多线程安全解决方法
# 同步代码块:锁对象是任意对象,线程间共享的对象
  * 格式:
        synchronized(锁对象){
            需要被同步的代码块;
        }
# 同步方法:锁对象是this
    在方法上添加synchronized关键字修饰
# 静态同步方法:锁对象是该类的.class对象
    在方法上添加synchronized关键字修饰
2.4 线程同步的特点
# 前提:
    多线程环境;
    多个线程使用同一个锁对象(不同的执行路径要使用相同的锁对象)
# 好处:
    解决多线程安全问题
# 弊端:
    当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率.
2.5 线程同步方法选用
# 如果锁对象是this,就可以考虑使用同步方法;
# 否则能使用同步代码块的尽量使用同步代码块.
2.6 案例代码
/*
 * 模拟买电影票
 */
public class My_Thread_Test03 {
    public static void main(String[] args) {
        SellTickets2 s1 = new SellTickets2();
        Thread t1 = new Thread(s1, "窗口1");
        Thread t2 = new Thread(s1, "窗口2");
        Thread t3 = new Thread(s1, "窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}
class SellTickets2 implements Runnable {
    // 定义票数
    private static int tickets = 100;
    private Object obj = new Object();
    // 使用同步代码块
    /*@Override
    public void run() {
        synchronized (obj) {
            while( tickets > 0) {
                System.out.println(Thread.currentThread().getName()+"窗口正在出售第"+(tickets--)+"张票");
            }
        }
    }*/
    // 使用同方法
    @Override
    public void run() {
        sellTicket();
    }
    public synchronized void sellTicket() {
        while( tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"窗口正在出售第"+(tickets--)+"张票");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值