Java多线程基础学习

Java多线程基础

1.多线程简介

在了解多线程之前我们要先知道什么是进程和线程:

  • 进程:进程是系统进行调度和分配资源的基本单位,通俗的讲就是一个正在运行和后台运行的程序。我们可以通过任务管理器看到系统有哪些进程在运行。
  • 线程: 线程是CPU调度的最小单位,一个进程可能由多个或一个线程组成。比如我们在看视频是同时还能发弹幕。

核心概念:

  • 线程就是独立的执行路径
  • 在Java程序运行时,即使我们自己没有创建线程,JVM也会创建多个线程,如主线程(main())、GC线程。
  • main()被称为主线程,是正程序的入口,用于执行整个程序
  • 在一个线程中,如果开辟了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的。
  • 对于同一份资源操作时,会出现资源抢夺问题,需要加入并发控制。
  • 线程会带来额外的开销,如CPU调度时间、并发控制开销等。
  • 每个线程在自己的工作内存中交互,内存控制不当会造成数据不一致问题。

2.线程实现方法

创建线程有多种方式,最基本的三种: Thread类、Runnable接口、Callable接口

1.通过继承Thread类:

重写Thread类中的run(),run()方法中执行我们自己的逻辑

package com.zw.tread.base;

/**
 * @author zhouwei
 * @version 1.00
 * @className CreateThreadTest
 * @describe 创建线程测试类
 * @since 2020/4/19
 */
public class ThreadTest extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("我是创建的线程"+ Thread.currentThread().getName()+ "--->i==" + i);
        }
    }
    public static void main(String[] args) {
     // 创建线程 --- 线程创建
     ThreadTest threadTest = new ThreadTest();
     threadTest.setName("superMan");
     // 线程进入就绪状态
     threadTest.start();
     //主线程中的for循环
    for (int n = 0; n < 5; n++) {
        System.out.println("我是主线程"+"--->n==" + n);
    }
    }
}

运行结果如下图,可以看出线程之间是交替执行的,每次执行结果可能都不一样,线程的运行是由调度器安排调度的,并且是被CPU轮流执行的:


image.png

2.通过实现Runnable接口创建线程

package com.zw.tread.base;

/**
 * @author zhouwei
 * @version 1.00
 * @className RunnableTest
 * @describe Runnable接口创建线程
 * @since 2020/4/19
 */
public class RunnableTest  implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("我是创建的线程"+ Thread.currentThread().getName()+ "--->i==" + i);
        }
    }

    public static void main(String[] args) {
        // 创建Runnable接口实现类
        RunnableTest runnableTest = new RunnableTest();
        // 创建线程
        Thread thread = new Thread(runnableTest);
        // 调用线程
        thread.start();
        // 执行主线程for循环
        for (int n = 0; n < 5; n++) {
            System.out.println("我是主线程"+"--->n==" + n);
        }
    }
}

运行结果:


image.png

3.线程状态

下面简单的介绍一下线程的五种状态:

  • 创建状态(new): 线程被创建后,就进入了创建状态。例如:Thread thread = new Thread()。
  • 就绪状态(Runnable): 就绪状态也被称为“可执行状态”。线程对象被创建后,调用其start(),该线程就进入就绪状态了,例如: thread.start()。线程进入就绪状态后就随时可能被CPU调用。
  • 运行状态(Running):当线程获得CPU资源后,线程就进入运行状态,进入运行状态后,才会执行我们所写的逻辑代码。这里需要注意线程只能从就绪状态进入运行状态
  • 阻塞状态(Blocked):阻塞状态是线程由于某种原因失去了CPU资源使用权,暂时停止运行。阻塞情况分为三种:
    • 等待阻塞:通过调用线程wait()方法,让线程等待某种工作的完成在继续执行。
    • 同步阻塞:线程获取synchronized同步锁失败,线程会进入同步阻塞状态。
    • 其他阻塞:其他阻塞产生的情况比较多,通过调用线程的sleep()、join()方法或者发出IO请求时,线程会进入阻塞状态,只有当sleep()超时、join等待线程终止或超时、IO处理完毕是,线程重新进入就绪状态。
  • 死亡状态(Dead):线程执行完成或因为异常退出run()方法。
image.png
image.png

4.线程同步

1.线程并发问题:
在多线程编程中,有些共享数据是不允许多个线程同时访问的,如果同时访问可能会出现数据同步问题。
下面通过买票例子,展现多线程并发问题。

/**
 * @author zhouwei
 * @version 1.00
 * @className ThreadDemo01
 * @describe 线程并发问题,例子买票功能
 * @since 2020/4/20
 */
public class ThreadDemo01 implements Runnable {
    /**
     * 门票总个数
     */
    private int ticketNum = 30;

    @Override
    public void run() {
        while (true){
            if(ticketNum <= 0){
                System.out.println("票买完了。。。");
                break;
            }
            System.out.println("线程" + Thread.currentThread().getName() + "买了一张票,剩余"+ ticketNum--+"张");
            try {
                // 模仿延迟
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        ThreadDemo01 demo01 = new ThreadDemo01();
        new Thread(demo01,"AA").start();
        new Thread(demo01,"BB").start();
        new Thread(demo01,"CC").start();
    }
}

运行结果如果下图,从我们可以看出多线程并发问题,剩余票数出现混乱问题:


image.png

5.线程通讯问题

简介:线程与线程之间不是相互独立的个体,它们彼此之间需要相互通信和协作,最典型的例子就是生产者-消费者问题:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

下面介绍通过Object类中的wait()和notify方法实现线程通信:

例子:定义一个仓库类(Depot),有两个方法一个是生产商品(produce),一个是消费商品(consume),启动两个线程,一个线程值生产,一个线程值消费。

public class WaitAndNotifyTest03 {


    static class Depot {
        /**
         * 商品数量
         */
        private Integer goodNum = 0;

        private synchronized void produce(){
                // 如果商品数量大于0,则等待
                while (goodNum > 0){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int  num = new Double(Math.random() * 100).intValue();
                goodNum =+ num;
                System.out.println("我生产了商品" + num + "个");

                // 通知消费者消费
                notify();

        }

        private synchronized void consume(){
                // 如果商品数量大于0,则等待
                while (goodNum <= 0){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int n = goodNum;
                goodNum =- goodNum;
                // 通知消费者消费
                System.out.println("我消费了商品" + n + "个");
                notify();
        }
    }

    public static void main(String[] args) {
        Depot depot = new Depot();
        new Thread(() ->{
            for(int i=0;i<10;i++){
                depot.produce();
            }
        }).start();
        new Thread(()->{
            for(int i=0;i<10;i++){
                depot.consume();
            }
        }).start();
    }
}

6.线程其他方法

下面介绍一下Thread类的一些常用方法:

  1. setName(String): 该方法可以给线程设置名称,方便查询
  2. setPriority(Integer) : 该方法可以给线程设置优先级,但是线程的执行顺序还是有cpu决定的。
  3. setDeamon(boolean): 该方法设置线程是否由后台运行,false表示是,true表示不是。
  4. interrupt(): 中断线程,并不会真正中断线程,只是修改中断状态
  5. isInterrupted(): 获取线程是否被中断
  6. Interrupted(): 获取线程是否被中断,然后清除状态
  7. supspend()暂停、resume()恢复、stop()停止,这些方法已经过期不建议使用,原因如 supspend()调用这个方法线程并不会释放资源,而是占用资源进入休眠状态,可能会引发死锁。stop()方法同样没有给线程释放资源的机会就结束线程了。
  8. sleep(Integer):线程休眠,并不会释放锁
  9. yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入到就绪状态。即让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。
  10. join():A线程调用B线程的join()方法,将会使A等待B执行,直到B线程终止。如果传入time参数,将会使A等待B执行time的时间,如果time时间到达,将会切换进A线程,继续执行A线程。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。