三郎之——Java多线程

Posted 后端三郎@ZYJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三郎之——Java多线程相关的知识,希望对你有一定的参考价值。

大家好,划水的三郎更新多线程了,多多指点,欢迎大家留言讨论

目录

线程概述

        进程介绍

        线程介绍

创建线程的三种方式

        1.继承Thread

        2.实现Runnable接口

        3.实现Callable接口

线程的六种状态

        线程执行状态流程图

        1.NEW

        2.RUNNABLE

        3.BLOCKED

        4.WAITING

        5.TIMED_WAITING

        6.TERMINATED

        状态测试代码块

实现抢票功能

        多线程带来的问题

        解决方法

Java中文文档


线程概述

        在跑步的过程之中做了些什么事情?看进程介绍和线程介绍理解多线程。

进程介绍

         进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。比如:美女去跑步了这一件事的整个过程。

线程介绍

         线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。比如:我们在跑步这个过程中同时做的事情,例如呼吸,听歌,摆POSS等等,这就是多线程。

创建线程的三种方式

1.继承Thread

        重点:继承Thread类,重写run方法,run方法里面是线程体,也就是新线程的入口,调用start方法启动线程。启动方式:线程对象.start方法。

测试过程:创建StudyCSDNThread类,继承Thread类,重写run方法,创建主线程main主函数执行程序,run方法里面是一个for循环,调用start方法之后启动线程,由CPU就行调度,不归我们管了,让CPU自己去分配运行。

package com.example.demo.test;

//继承Thread类
public class StudyCSDNThread extends Thread
    //重写run方法,新线程的入口
    @Override
    public void run() 
        for (int i = 0; i < 5; i++) 
            System.out.println("文章对您有用的话,点赞关注支持一下"+(i+1));
        
    

    public static void main(String[] args) 
        //创建一个线程对象
        StudyCSDNThread studyCSDNThread = new StudyCSDNThread();
        //调用start方法开启线程
        studyCSDNThread.start();

        for (int i = 0; i < 5; i++) 
            System.out.println("三郎学习多线程"+(i+1));
        
    

 运行结果如下:线程由CPU调度,每次执行结果都会不同,不必多疑。

三郎学习多线程1
文章对您有用的话,点赞关注支持一下1
文章对您有用的话,点赞关注支持一下2
文章对您有用的话,点赞关注支持一下3
三郎学习多线程2
文章对您有用的话,点赞关注支持一下4
三郎学习多线程3
文章对您有用的话,点赞关注支持一下5
三郎学习多线程4
三郎学习多线程5

总结:线程调用start方法开启之后未必执行,由CPU进行调度,看CPU心情。

2.实现Runnable接口

        重点:实现Runnable接口,重写run方法,run方法里面是线程体,也就是新线程的入口,调用start方法启动线程。启动方式:new Thread(线程对象).start方法。

测试过程:创建StudyCSDNRunnable类,实现Runnable接口,重写run方法,创建主线程main主函数执行程序,run方法里面是一个for循环,只有Thread类有start方法,通过new Thread调用start方法之后启动线程,由CPU就行调度,不归我们管了,让CPU自己去分配运行。

package com.example.demo.test;

//实现Runnable接口
public class StudyCSDNRunnable implements Runnable
    //重写run方法
    @Override
    public void run() 
        for (int i = 0; i < 5; i++) 
            System.out.println("文章对您有用的话,点赞关注支持一下"+(i+1));
        
    
    //主线程
    public static void main(String[] args) 
        //创建一个线程对象
        StudyCSDNRunnable studyCSDNRunnable = new StudyCSDNRunnable();
        //只有Thread才有start方法,new Thread(),放入我们的线程对象。调用start方法
        new Thread(studyCSDNRunnable).start();
        for (int i = 0; i < 5; i++) 
            System.out.println("三郎学习多线程"+(i+1));
        
    

运行结果如下:跟方式一 一样,线程由CPU进行调度,每次执行接口都可能不一样。

三郎学习多线程1
文章对您有用的话,点赞关注支持一下1
三郎学习多线程2
文章对您有用的话,点赞关注支持一下2
三郎学习多线程3
文章对您有用的话,点赞关注支持一下3
三郎学习多线程4
文章对您有用的话,点赞关注支持一下4
文章对您有用的话,点赞关注支持一下5
三郎学习多线程5

总结:推荐使用Runnable,可摆脱Java单继承的局限性。线程调用start方法开启之后未必执行,由CPU进行调度,看CPU心情。

3.实现Callable接口

        了解即可:实现callable接口,需要返回值,给一个返回值类型String,不写默认Object,重写call方法,会抛出一个异常 throws Exception。

 测试过程:创建StudyCSDNCallable类,实现Callable接口,重写call方法,创建主线程main主函数执行程序,创建线程池,执行线程,获取运行结果,关闭线程池。

package com.example.demo.test;

import java.util.concurrent.*;

//实现callable接口,需要返回值,给一个返回值类型String,不写默认Object
public class StudyCSDNCallable implements Callable<String> 
    private String name;
    private String study;
    //构造方法
    public StudyCSDNCallable(String name, String study) 
        this.name = name;
        this.study = study;
    
    //重写call方法,会抛出一个异常 throws Exception
    @Override
    public String call() throws Exception 
        //输出可以看到是多线程执行,
        System.out.println(study);
        //返回值
        return name+study;
    
    //主线程
    public static void main(String[] args) throws ExecutionException,InterruptedException 
        //传入两个参数,看是否是多线程执行
        StudyCSDNCallable studyCSDNCallable1 = new StudyCSDNCallable("三郎===","3.1学习多线程");
        StudyCSDNCallable studyCSDNCallable2 = new StudyCSDNCallable("三郎===","3.2实现callable");
        //创建一个线程池,放入两个线程(线程池重点内容后续单独出一篇文章解释)
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //执行线程
        Future<String> submit1 = executorService.submit(studyCSDNCallable1);
        Future<String> submit2 = executorService.submit(studyCSDNCallable2);
        //运行结果
        String zyj1 = submit1.get();
        //打印运行结果
        System.out.println(zyj1);
        String zyj2 = submit2.get();
        //打印运行结果
        System.out.println(zyj2);
        //关闭线程池
        executorService.shutdown();

    

运行结果:线程由CPU进行调度,每次执行接口都可能不一样。文中使用的线程数少,可以多试几次,或者多来几个线程。

3.2实现callable
3.1学习多线程
三郎===3.1学习多线程
三郎===3.2实现callable

总结:非重点,此了解即可。

线程的六种状态

很多博客上都是五种状态,但其实是六种,初始状态,运行状态,也称就绪状态,阻塞状态,等待状态,等待状态分为两种,等待指定时间状态和等待状态,还有终止状态。摘自翻译的Java中文官方文档,文档在文章最后可下载,由下载链接。

线程执行状态流程图

        图文详解

1.NEW

        初始状态:尚未启动的线程的线程状态。

2.RUNNABLE

        运行状态(就绪状态):一个可运行的线程的线程状态。 由CPU决定,CPU已经调度的话是运行状态,就绪状态就是CPU还没有进行调度,已经做好被调度的准备。

3.BLOCKED

        阻塞状态:线程阻塞等待监视器锁的线程状态。 

4.WAITING

        等待状态:等待线程的线程状态。 

5.TIMED_WAITING

        等待指定时间状态:具有指定等待时间的等待线程的线程状态。 

6.TERMINATED

       终止状态: 终止线程的线程状态。 线程终止不可再次开启。

状态测试代码块

package com.example.demo.test;

public class StudyCSDNState implements Runnable

    public synchronized static void main(String[] args) throws InterruptedException 
        System.out.println("###---start---###");
        System.out.println("===线程的创建-运行-终止===");
        //创建线程对象
        StudyCSDNState studyCSDNState = new StudyCSDNState();
        //创建Thread方法
        Thread thread = new Thread(studyCSDNState);
        System.out.println("===没有调用start方法前,当前线程的状态"+thread.getState());
        //调用start方法
        thread.start();
        System.out.println("===调用start后线程状态"+thread.getState());
        Thread.sleep(100);
        System.out.println("===线程进入等待状态"+thread.getState());
        Thread.sleep(2000);
        System.out.println("===等待两秒,查看线程状态"+thread.getState());
        System.out.println("###---end---###");
    

    @Override
    public void run() 
        try 
            Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

运行结果如下:

###---start---###
===线程的创建-运行-终止===
===没有调用start方法前,当前线程的状态NEW
===调用start后线程状态RUNNABLE
===线程进入等待状态TIMED_WAITING
===等待两秒,查看线程状态TERMINATED
###---end---###

实现抢票功能

        一共有10张车票,创建了三个不同的对象来抢购。

多线程带来的问题

当多个线程操作同一对象的时候,就会有抢错,负数,抢到同一张等的情况发生,下面代码是抢错的代码,可试验一下。

package com.example.demo.test;

/**
 *多个线程操作同一个对象
 *买火车票为例子
 *
 * 发现问题:多个线程操作同一个对象线程不安全了,数据混乱
 *
 */
public class StudyRunnable2 implements Runnable

    //10张车票
    private Integer fare = 10;
    private Boolean change = true;

    @Override
    public void run() 
        while (change)
            try 
                zyj();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

    public void zyj() throws InterruptedException 
            if(fare<=0)
                change = false;
                return;
            
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName()+"----->拿到了第"+fare--+"票");
    

    public static void main(String[] args) 
        //单线程执行没有问题,多线程执行出现问题
        StudyRunnable2 studyRunnable2 = new StudyRunnable2();
        new Thread(studyRunnable2,"张三").start();
        new Thread(studyRunnable2,"李四").start();
        new Thread(studyRunnable2,"王五").start();
    


解决方法

使用synchronized关键字修饰,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

修改上边的代码,找到zyj()方法,在此方法加上synchronized即可,修改的代码如下

public synchronized void zyj() throws InterruptedException 
            if(fare<=0)
                change = false;
                return;
            
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName()+"----->拿到了第"+fare--+"票");
    

运行结果:运行出来若都是一个人拿到的话,多运行几次,这个CPU决定的。

张三----->拿到了第10票
张三----->拿到了第9票
张三----->拿到了第8票
张三----->拿到了第7票
张三----->拿到了第6票
张三----->拿到了第5票
张三----->拿到了第4票
李四----->拿到了第3票
李四----->拿到了第2票
王五----->拿到了第1票

 总结:使用synchronized会影响效率,不使用会导致数据混乱,在数据安全方面,还是舍弃效率吧,synchronized最好加在修改数据的方法上,可以少影响点效率。

注:产品改需求了............此处省略一万字.........得写代码了,线程池等后续单独出一篇。

Java中文文档

百度网盘链接:https://pan.baidu.com/s/124_gXmqRs5Ng8LUW_Buq8A 
提取码:i6i8

以上是关于三郎之——Java多线程的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程 2.线程安全

Java多线程 4.线程池

什么是JAVA的多线程?

Java多线程 1.认识Java线程

Java多线程 5.栅栏

java 如何实现多线程