JAVA多线程

JAVA多线程

一、概念

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象

进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

  1. 若一个进程同一时间并行多个线程,就是支持多线程的
  2. 线程作为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器,线程切换的开销小
  3. 一个进程中的多个线程共享相同的内存单元/内存地址空间—>他们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能会带来安全的隐患

什么是线程?

  1. 有序严谨的指令集 称为程序
  2. 程序的同时多运行称为进程
  3. 程序中不同的执行路径称为线程

多线程编程简单,效率高,易于资源共享

什么是线程同步?

​ 线程同步是指 在一段程序执行过程中,无论成功还是失败,其他线程都会等待这段程序执行完毕,才会转入其他线程。这样可以保证程序的完整性和安全性

单核CPU和多核CPU的理解

  1. 单核CPU,其实是一种假的多进程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”。但是因为CPU时间单元比较短,因此感觉不出
  2. 如果是多核的话,,才能更好的发挥多线程的效率
  3. 一个java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程

并行与并发

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

多线程的优点

背景:

​ 以单核CPU为例,日使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短。

优点:
  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利用理解和修改
分类:1、守护线程 2、用户线程
  • 他们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
  • Java垃圾回收就是一个典型的守护线程
  • 若JVM中都是守护线程,当前JVM将退出

何时需要多线程

  • 程序需要同时执行两个或多个任务
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写、操作、网络操作、搜索等
  • 需要一些后台运行的程序时

线程的生命周期

​ 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及子类的对象来表示线程,在它一个完整的生命周期中通常要经历五种状态;

  1. 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建装填。
  2. 就绪:处于新建状态的线程被start() 后,将进入线程队列等待CPU时间片,此时他已经具备了运行条件,只是没分配到CPU资源
  3. 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run() 方法定义了线程的操作和功能
  4. 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  5. 死亡:线程完成了它的全部工作或者线程被提前强制性的中止或出现异常导致结束

在这里插入图片描述

二、线程的创建与使用

线程的创建与使用

  1. Java元的JVM允许程序运行多个线程,他通过java.lang.Thread 类来体现
  2. Thread类的特性
    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把闰方法的主题称为线程体
    • 通过该Thread对象的start() 方法啦启动这个线程,而非直接调用run()
/*创建方式一:继承Thread*/
public class ThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("hello world");
    }
}

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

/**
* 创建多线程的方式二:实现Runnable
* 1.创建一个实现类Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法
* 3.创建实现类的对象
* 4.将次对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
*/
class Mythread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(i+Thread.currentThread().getName());
            }
        }
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        Mythread myThread = new Mythread();
        new Thread(myThread).start();
        //创建多个线程
//        new Thread(myThread).start();
    }
}

/**
 * 线程创建方式三:实现Callable接口
 * 与使用Runnable相比,Callable功能更加强大
 * 1.相比run()方法,可以有返回值
 * 2.方法可以抛出异常
 * 3.支持泛型的返回值
 * 4.需要借助FutureTask类,比如获取返回结果
 */
//1.创建一个实现Callable的实现类
class NumThread implements Callable{

    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 ==0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class CallableTest {
    public static void main(String[] args) {
        //3.创建Callable实现类对象
        NumThread numThread = new NumThread();
        //将Callable对象传入futureTask构造器中
        FutureTask futureTask = new FutureTask(numThread);
        //将futureTask传入Thread构造器中
        new Thread(futureTask).start();
        try {
            //获取返回值
            //get()返回值即为futureTask构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

/**
* 新建方式4:使用线程池
* 1、提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创* 建销毁、实现重复利用。类似生活中的公共交通工具
* 2、好处:
* 	a)提高响应速度(减少了创建新线程的时间)
* 	b)降低组员小号(重复利用线程池中的线程,不需要每次都创建)
* 	c)便于线程管理:
* 		corePoolSize:核心池的大小
* 		maximumPoolSize:最大线程数
* 		keepAliveTime:线程没有任务时最多保持多长时间后终止
*/
class numThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 ==0){
                System.out.println(Thread.currentThread().getName() + ":" +i);
            }
        }
    }
}

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//        service1.setCorePoolSize();
//        service1.setKeepAliveTime();
        numThread num = new numThread();
        service.execute(num);//适合适用于Runnable
//        service.submit();//适合用于Callable
        service.shutdown();//关闭连接池

    }
}

Thread方法

  1. static void yield():线程让步
  2. 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
  3. 若队列中没有同优先级的线程,忽略此方法
  4. Join():当某个程序执行流中调用其他线程的join() 方法时,调用线程将被阻塞,直到join() 方法加入的线程执行完为止
    1. 低优先级的线也可以获得执行
  5. static void sleep(long millis):(指定时间:毫秒)
    1. 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队
    2. 抛出InterrupedException异常
  6. stop():强制线程生命期结束,不推荐使用
  7. Boolean isAlive():返回boolean,判断线程是否还活着
  8. void start():启动线程,并执行对象的run() 方法
  9. run():线程在被调度时执行的操作
  10. String getName():返回线程名称
  11. void setName(String name):设置该线程名称
  12. static Thread currentThread():返回当前线程。在Thread子类中就是this,同城用于主线程和Runnable实现类。

三、线程的调度

调度策略

  1. 时间片:
    在这里插入图片描述
  2. 抢占式:高优先级的线程抢占CPU

Java的调度方法

  1. 通优先级线程组成先进先出队列(先到先服务),使用时间片策略
  2. 对高优先级,使用有线调度的抢占式策略

现成的优先级等级

  1. MAX_PRIORITY:10
  2. MIN_PRIORITY:1
  3. NORM_PRIORITY:5 ----> 默认优先级

涉及的方法

  1. getPriority:返回线程优先级
  2. setPriority(int newPriority):改变线程的优先级

说明

  1. 线程创建时继承父线程的优先级
  2. 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

四、线程同步 安全问题

存在的问题

  1. 多个线程执行的不确定性引起执行结果的不稳定性
  2. 多个线程对共享数据 会造成操作的不完整性,会破坏数据

解决方法

  1. 同步代码块

    synchronized (object) {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售出:" + ticket);
            ticket--;
        } else {
            break;
        }
    }
    

    说明:

    1. 操作共享数据的代码,即为被同步的代码
    2. 共享数据:多个线程共同操作变量
    3. 同步监视器:
      • 俗称:锁。任何一个类的对象,都可以充当锁
      • 要求:多个线程必须要共用同一个锁

    好处

    1. 解决了线程的安全问题
    2. 操作同步代码时,只能有一个线程参与,其他线程等待,相当于单线程
  2. 同步方法

    		public void run() {
            while (true){
                   show();
            }
        }
        private synchronized void show(){
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "售出:" + ticket);
                ticket--;
            }
        }
    

    注意:同步方法只适合,方法内只有对共享数据操作的代码

  3. lock锁

    class Ticketwindow implements Runnable{
        private int ticket = 100;
    //    Object object = new Object();
        private ReentrantLock lock = new ReentrantLock();
        @Override
        public void run() {
            while (true){
    //            synchronized (object) {
                try{
                    //调用lock方法 保证在这个过程中是单线程
                    lock.lock();
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "售出:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }finally {
                    //解锁
                    lock.unlock();
                }
    
    //            }
            }
        }
    }
    
    
    
    

五、线程的死锁问题

死锁

  • 不同的线程分别占用对方需要的同步资源不放弃,都砸等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

六、线程的通信

两个线程进行交互

class number implements Runnable{
    private int num = 1;
    @Override
    public void run() {
        while(true){
            synchronized (this) {
                notify();
                if (num <= 100) {
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                }else {
                    break;
                }
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

设计到的三个方法

  • wait():一旦执行此方法,当前线程就进入阻塞装填,并释放同步监视器
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的方法

使用说明

  • 这三个方法必须使用在同步代码块 或者 同步方法中
  • 这三个方法的调用者 必须是 同步代码块或同步方法中的监视器
  • 这三个方法是定义在java.lang.Object类中的

面试题

  1. Synchronized 和 lock异同(面试题)

    • 相同:二者都可以解决线程安全问题

    • 不同:Synchronized机制在执行完相应的代码后自动释放同步监视器

      Lock需要手动启动同步(Lock()),同时结合素同步也需要手动的实现(unlock())

  2. 如何解决线程的安全方式有几种方式?

    • 同步代码块
    • 同步方法
    • Lock锁
  3. sleep() 和 wait() 方法的异同

    • 相同点:一旦执行方法,都可以是的当前线程进入阻塞状态
    • 不同点
      • 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
      • 调用两个方法要求不同:sleep()可以在任何需要的时候调用。wait() 必须使用同步监视器调用
      • 如果两个方法都是用在同步代码块或同步方法中时,sleep()不会释放锁,wait()会释放同步监视器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值