Java多线程的应用
一、概述
提到线程不得不提进行。因为线程是进程的一个执行单元。下面对线程和进程分别进行介绍。
1、进程
进程是当前操作系统执行的任务,是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。一般而言,现在的操作系统都是多进程的。
进程的执行过程是线状的, 尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。
2、线程
线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。即:每个进程中至少包含一个线程。
线程本身是在CPU上执行的,CPU的每一个核在同一时刻只能执行一个线程,但CPU在底层会对线程进行快速的轮询切换。
3、线程的特点
线程在执行任务的过程大概可以分为2大块:
- 在CPU上执行
- 和计算机的硬件进行交互。当线程和硬件进行交互(例如读取文件)是不占用CPU的。
- 提高CPU利用率。理论上,当线程个数足够多的时候,CPU的利用率是能够到达100%。
- 一个程序的主函数所在的类默认是一个单独的线程。
二、JAVA中如何定义线程
1、通过继承Thread,重写run方法,将要执行的逻辑放在run方法中,然后创建线程对象调用start方法来开启线程。示例如下:
- public class ThreadDemo {
- public static void main(String[] args) {
- TDemo t1 = new TDemo("A");
- // 启动线程
- // start方法中会给线程做很多的配置
- // 配置完成之后会自动调用run方法执行指定的任务
- t1.start();
- // t1.run();
- TDemo t2 = new TDemo("B");
- t2.start();
- // t2.run();
- }
- }
- class TDemo extends Thread {
- private String name;
- public TDemo(String name) {
- this.name = name;
- }
- // 打印0-9
- // 线程要执行的任务就是放在这个方法中
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- System.out.println(name + ":" + i);
- }
- }
- }
2、实现Runnable,重写run方法,然后利用Runnable对象来构建Thread对象,调用start方法来启动线程。示例如下:
- public class RunnableDemo {
- public static void main(String[] args) {
- RDemo r = new RDemo();
- // 包装 - 装饰设计模式
- Thread t = new Thread(r);
- t.start();
- }
- }
- class RDemo implements Runnable {
- @Override
- public void run() {
- for (int i = 0; i < 10; i++) {
- System.out.println(i);
- }
- }
- }
3、实现Callable<T>,重写call方法,通过线程池定义线程。示例如下:
- public class CallableDemo {
- public static void main(String[] args) throws InterruptedException, ExecutionException {
- CDemo c = new CDemo();
- // 执行器服务 执行器助手
- ExecutorService es = Executors.newCachedThreadPool();
- Future<String> f = es.submit(c);
- System.out.println(f.get());
- es.shutdown();
- }
- }
- // 泛型表示要的结果类型
- class CDemo implements Callable<String> {
- @Override
- public String call() throws Exception {
- return "SUCCESS";
- }
- }
三、多线程的并发安全问题
1、线程之间是相互抢占执行,而且抢占是发生在线程执行的每一步;当线程重新抢回执行权之后,会沿着上次被抢占位置继续向下执行,而不是从头开始执行。
2、由于线程的抢占而导致出现了不合理的数据的现象:多线程的并发安全问题。
四、线程中的锁机制
1、概述
为了解决线程并发问题,引入了synchronized代码块,亦即同步代码块。同步代码块需要一个锁对象。
2、锁对象及其特点
锁对象要求被当前的所有线程都认识。共享资源,方法去中的资源和this都可以作为锁对象。
当使用this作为锁对象的时候,要求利用同一个Runnable对象来构建不同的Thread对象。
示例如下:利用多线程实现卖票机制
- package cn.tedu.thread;
- import java.io.FileInputStream;
- import java.util.Properties;
- // 利用多线程机制模拟卖票场景
- public class SellTicketDemo {
- public static void main(String[] args) throws Exception {
- // 利用properties做到改动数量但是不用改动代码的效果
- Properties prop = new Properties();
- prop.load(new FileInputStream("ticket.properties"));
- int count = Integer.parseInt(prop.getProperty("count"));
- // 利用ticket对象做到所有的线程共享一个对象
- Ticket t = new Ticket();
- t.setCount(count);
- // 表示四个售票员在分别卖票
- Thread t1 = new Thread(new Seller(t), "A");
- Thread t2 = new Thread(new Seller(t), "B");
- Thread t3 = new Thread(new Seller(t), "C");
- Thread t4 = new Thread(new Seller(t), "D");
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
- // 定义了线程类表示售票员
- class Seller implements Runnable {
- private Ticket t;
- public Seller(Ticket t) {
- this.t = t;
- }
- @Override
- public void run() {
- // 锁对象 --- 需要指定一个对象作为锁来使用
- while (true) {
- // 由于所有的Seller线程都在卖票t,所以t是被所有线程都认识的
- // synchronized (t) {
- // 由于所有的Seller线程都是Seller类产生的,所以Seller类也是被所有线程都认识的
- // synchronized (Seller.class) {
- // synchronized (Thread.class) {
- synchronized ("abc") {
- if (t.getCount() <= 0)
- break;
- try {
- // 让当前线程陷入休眠
- // 时间单位是毫秒
- // 不改变线程的执行结果
- // 只会把线程的执行时间拉长
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 票数减少1张
- t.setCount(t.getCount() - 1);
- // currentThread()获取当前在执行的线程
- // 获取线程的名字
- String name = Thread.currentThread().getName();
- System.out.println(name + "卖了一张票,剩余" + t.getCount());
- }
- }
- }
- }
- class Ticket {
- private int count;
- public int getCount() {
- return count;
- }
- public void setCount(int count) {
- this.count = count;
- }
- }
3、线程的同步和异步
同步:在同一时刻内资源/逻辑只被一个线程占用/执行。
异步:在同一时刻内资源/逻辑可以被多个线程抢占使用。
4、多线程死锁
由于多个线程之间的锁形成了嵌套而导致代码无法继续执行,这种现象称之为死锁。
我们只能尽量避免出现死锁,在实际开发中,会做死锁的检验;如果真的出现了死锁,会根据线程的优先级打破其中一个或者多个锁。
死锁的示例如下:
- package cn.tedu.thread;
- public class DeadLockDemo {
- static Printer p = new Printer();
- static Scan s = new Scan();
- public static void main(String[] args) {
- // 第一个员工
- Runnable r1 = new Runnable() {
- @Override
- public void run() {
- synchronized (p) {
- p.print();
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (s) {
- s.scan();
- }
- }
- }
- };
- new Thread(r1).start();
- // 第二个员工
- Runnable r2 = new Runnable() {
- @Override
- public void run() {
- synchronized (s) {
- s.scan();
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (p) {
- p.print();
- }
- }
- }
- };
- new Thread(r2).start();
- }
- }
- // 代表打印机的类
- class Printer {
- public void print() {
- System.out.println("打印机在吱呦吱呦的打印~~~");
- }
- }
- // 代表扫描仪的类
- class Scan {
- public void scan() {
- System.out.println("扫描仪在哼哧哼哧的扫描~~~");
- }
- }
五、线程的优先级
1、Java中将线程的优先级分为1-10共十个等级。
2、理论上,数字越大优先级越高,那么该线程能抢到资源的概率也就越大;但实际上,相邻的两个优先级之间的差别非常不明显;如果想要相对明显一点,至少要相差5个优先级。
线程优先级示例如下:
- public class PriorityDemo {
- public static void main(String[] args) {
- Thread t1 = new Thread(new PDemo(), "A");
- Thread t2 = new Thread(new PDemo(), "B");
- // 在默认情况下,线程的优先级都是5
- // System.out.println(t1.getPriority());
- // System.out.println(t2.getPriority());
- // 设置优先级
- t1.setPriority(1);
- t2.setPriority(10);
- t1.start();
- t2.start();
- }
- }
- class PDemo implements Runnable {
- @Override
- public void run() {
- String name = Thread.currentThread().getName();
- for (int i = 0; i < 10; i++) {
- System.out.println(name + ":" + i);
- }
- }
- }
六、线程的等待唤醒机制
1、利用标记为以及wait、notify、notifyAll方法来调用线程之间的执行顺序;
2、wait、notify、notifyAll和锁有关,用那个对象作为锁对象使用,那么就用该锁对象来调用wait、notify。
等待和唤醒示例如下:
- package cn.tedu.thread;
- public class WaitNotifyAllDemo {
- public static void main(String[] args) {
- Product p = new Product();
- new Thread(new Supplier2(p)).start();
- new Thread(new Supplier2(p)).start();
- new Thread(new Consumer2(p)).start();
- new Thread(new Consumer2(p)).start();
- }
- }
- // 生产者
- class Supplier2 implements Runnable {
- private Product p;
- public Supplier2(Product p) {
- this.p = p;
- }
- @Override
- public void run() {
- while (true) {
- synchronized (p) {
//因为线程被抢断后,会沿着停止出继续执行,因为用while循环强制对其进行判断,满足条件时才能执行
//不满足条件就让其等待- while (p.flag == false){
- try {
- // 让当前线程陷入等待
- p.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- // 计算本次生产的商品数量
- int count = (int) (Math.random() * 1000);
- p.setCount(count);
- System.out.println("生产者生产了" + count + "件商品~~~");
- p.flag = false;
//当多个线程执行时,要唤醒所有的线程,否则可能连续唤起一个线程,导致程序执行混乱- p.notifyAll();
- }
- }
- }
- }
- // 消费者
- class Consumer2 implements Runnable {
- private Product p;
- public Consumer2(Product p) {
- this.p = p;
- }
- @Override
- public void run() {
- while (true) {
- synchronized (p) {
- while (p.flag == true){
- try {
- p.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- int count = p.getCount();
- p.setCount(0);
- System.out.println("消费者消费了" + count + "件商品~~~");
- p.flag = true;
- // 唤醒在等待的线程
- p.notifyAll();
- }
- }
- }
- }
七、线程的状态
线程从创建到开始消亡一般会经历如下几种状态:
八、守护线程
1、概述
守护其他的线程被称为守护线程,只要被守护的线程结束,那么守护线程就会随之结束。
2、守护线程的特点
- 一个线程要么是守护线程,要么是被守护线程
- 守护线程可以守护其他的守护线程
- 在Java中,最常见的一个守护线程是GC
守护线程的示例如下:
- package cn.tedu.thread;
- public class DaemonDemo {
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(new Monster(), "小怪1号");
- Thread t2 = new Thread(new Monster(), "小怪2号");
- Thread t3 = new Thread(new Monster(), "小怪3号");
- Thread t4 = new Thread(new Monster(), "小怪4号");
- // 设置为守护线程
- t1.setDaemon(true);
- t2.setDaemon(true);
- t3.setDaemon(true);
- t4.setDaemon(true);
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- for (int i = 10; i > 0; i--) {
- System.out.println("Boss掉了一滴血,剩余" + i);
- Thread.sleep(50);
- }
- }
- }
- //守护boss的小怪线程
- class Monster implements Runnable {
- @Override
- public void run() {
- String name = Thread.currentThread().getName();
- for (int i = 1000; i > 0; i--) {
- System.out.println(name + "掉了一滴血,剩余" + i);
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
总结:sleep和wait的区别
1、sleep:在使用的时候需要指定休眠时间,单位是毫秒,到点自然醒。在无锁状态下,会释放CPU;在有锁状态下,不释放CPU。
sleep方法是一个静态方法,被设计在了Thread类上。
2、wait:可以指定等待时间也可以不指定。如果不指定等待时间则需要被唤醒。wait必须结合锁使用,当线程在wait的时候会释放锁。wait方法设计在了Object类上。
九、线程产生和结束的场景
1、线程产生的场景
- 系统自启动:开机默认启动的程序;
- 用户请求:QQ好友聊天;
- 线程之间的启动:App软件之间带有的插件。
2、线程结束的场景
- 寿终正寝:线程自然结束
- 他杀:被其他线程kill
- 意外:线程因为报错崩溃而退出
十、JAVA虚拟机方法区和线程的关系
1、类是存储在方法区中的,方法区是被所有的线程共享的空间。
2、每一个线程独有一个栈内存。
Java多线程的应用的更多相关文章
- 40个Java多线程问题总结
前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...
- Java多线程基础知识篇
这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
- Java多线程系列--“JUC锁”04之 公平锁(二)
概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...
- Java多线程--让主线程等待子线程执行完毕
使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...
- Java多线程 2 线程的生命周期和状态控制
一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...
- java 多线程 1 线程 进程
Java多线程(一).多线程的基本概念和使用 2012-09-10 16:06 5108人阅读 评论(0) 收藏 举报 分类: javaSE综合知识点(14) 版权声明:本文为博主原创文章,未经博 ...
- 一起阅读《Java多线程编程核心技术》
目录 第一章 Java多线程技能 (待续...)
- 第一章 Java多线程技能
1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...
- java从基础知识(十)java多线程(下)
首先介绍可见性.原子性.有序性.重排序这几个概念 原子性:即一个操作或多个操作要么全部执行并且执行的过程不会被任何因素打断,要么都不执行. 可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到 ...
随机推荐
- Python 数据的输入
一.单个输入 a=input("输入提示语句")#默认a的类型是字符串 b=input() 二.一行输入两个/三个数据,数据之间用空格间隔开 #a,b的数据类型都是整数 a,b=m ...
- INUX下抓取当前登录用户登录密码的工具:mimipenguin
前有Mimikatz,今有mimipenguin,近日国外安全研究员huntergregal发布了工具mimipenguin,一款Linux下的密码抓取神器,可以说弥补了Linux下密码抓取的空缺. ...
- 【剑指Offer面试编程题】题目1504:把数组排成最小的数--九度OJ
题目描述: 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323. 输入: 输 ...
- topthink/think-swoole 扩展包的使用 之 WebSocket
嗯~ o(* ̄▽ ̄*)o,没错欢迎收看继续爬坑系列233...话不多说直接开撸 今天的题材是websocket,没有特殊说明的话默认环境都和此系列第一篇文章中申明一致,此后不再赘述. websocke ...
- redis-String字符串
存储类型:可以用来存储字符串.整数.浮点数. 常用命令: 1. SET key value [EX seconds] [PX milliseconds] [NX|XX] 1.1 EX seconds ...
- Redis的增删改查 c# key value类型和hash map 类型
using Newtonsoft.Json; using StackExchange.Redis; using System; using System.Collections.Generic; us ...
- Vue.js模板语法介绍
Vue.js模板.指令 模板语法概述 1.如何理解前端渲染? 把数据填充到HTML标签中,一般我们使用Ajax将数据从后台查询出,结合模板() 2.前端渲染方式 2.1.原生js拼接字符串 使 ...
- 实践 Network Policy 【转】
为了演示 Network Policy,我们先部署一个 httpd 应用,其配置文件 httpd.yaml 为: httpd 有三个副本,通过 NodePort 类型的 Service 对外提供服务. ...
- 009、Java中超过了int的最大值或最小值的结果
01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...
- 6.安装telnet服务
yum list telnet* 列出telnet相关的安装包 yum install telnet-server 安装telnet服务 yum install telnet.* 安装telnet客户 ...