三郎之——Java多线程
Posted 后端三郎@ZYJ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三郎之——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多线程的主要内容,如果未能解决你的问题,请参考以下文章