精华内容
下载资源
问答
  • ./consul agent -dev 只能127.0.0.1可以访问 ./consul agent -dev -client 0.0.0.0 -ui 指定ip可以访问 mysql 8.X.X版本个ip限制访问 随笔记录,由于客户要求数据库不同ip访问,查了很,多数都是ip或者所有ip...

    建立一个数据表

    CREATE TABLE `clicks` (

    `ip` INT UNSIGNED NOT NULL ,

    `time1` INT UNSIGNED NOT NULL ,

    `time2` INT UNSIGNED NOT NULL ,

    PRIMARY KEY ( `ip` )

    ) ENGINE = MYISAM

    获取ip和当前时间插入到数据表

    INSERT INTO clicks (ip,time2) VALUES (INET_ATON('{$_SERVER["REMOTE_ADDR"]}'),UNIX_TIMESTAMP()) ON DUPLICATE KEY UPDATE time1=time2, time2=UNIX_TIMESTAMP()

    再次访问时进行验证

    SELECT INET_NTOA(ip) FROM clicks WHERE ip=INET_ATON('{$_SERVER["REMOTE_ADDR"]}') AND time1+3600-time2+3600<=UNIX_TIMESTAMP();

    在规定的时间内则允许,否则拒绝访问。

    新版vue-cli输入本地ip不能访问,只能用localhost才可以访问?

    问:新版vue-cli输入本地ip不能访问,只能用localhost才可以访问? 答:修改config/index.js配置,将host: 'localhost',改为host: '0.0.0.0', ...

    设置nginx禁止IP直接访问,只能通过指定的域名访问

    nginx的版本是1.2.1. 设置配置文件disableip.conf: server {     listen 80;     server_name _;     return500; } 这是 ...

    关于javascript中限定时间内防止按钮重复点击的思路

    前面的话 有一天心血来潮,1分钟内重复点击了多次博客园首页的刷新博文列表的刷新按钮.果不其然,ip当时就被禁用了.后来,重启自己的路由器,重新获取ip才可以访问博客园主页.那么,设置一个限定时间内(比 ...

    如何让用户只能访问特定的数据库&lpar;MSSQL&rpar;

    背景 客户的SQL Server实例上有多个厂商的数据库,每个数据库由各自的进行厂进行商维护, 为了限定不同厂商的维护人员只能访问自己的数据库,现需要给各个厂商限定权限,让他们登录SQL Server ...

    网站用域名能访问,用域名IP不能访问的原因分析

    原因分析:一般虚拟主机是不能直接输入IP进行访问的 因为一个IP下有很多网站 ,只能用域名进行访问.如果想IP也能访问,必须网站有独立的IP地址,不是共享IP.如果是IIS的话,要把主机头对应的域名去 ...

    MSSQL只能访问特定的数据库

    让用户只能访问特定的数据库(MSSQL) 背景 客户的SQL Server实例上有多个厂商的数据库,每个数据库由各自的进行厂进行商维护, 为了限定不同厂商的维护人员只能访问自己的数据库,现需要给各个厂 ...

    tomcat服务器输入localhost可以访问,ip无法访问解决办法

    最近在开发项目中,遇到的一个问题是: 在 tomcat中发布一个web项目,但是发布成功后,只能用http://localhost:8080/fm访问项目,不能用 http://127.0.0.1:8 ...

    consul UI用127可以访问,指定ip无法访问

    ./consul agent -dev    只能127.0.0.1可以访问 ./consul agent -dev  -client 0.0.0.0 -ui  指定ip可以访问

    mysql 8&period;X&period;X版本多个ip限制访问

    随笔记录,由于客户要求数据库不同ip访问,查了很多,多数都是ip段或者所有ip可以访问: select user,host from user;可以查看某些用户可以访问的ip:但只能设置一个用户一条记 ...

    随机推荐

    css样式之background详解(格子效果)

    background用法详解: 1.background-color 属性设置元素的背景颜色 可能的值 color_name            规定颜色值为颜色名称的背景颜色(比如 red) he ...

    PreparedStatement

    PreparedStatement > 它是Statement接口的子接口: >强大之处: 防SQL攻击: 提高代码的可读性.可维护性: 提高效率! l 学习PreparedStateme ...

    尝试使用word发布博客

    尝试使用WORD2010发布博客   使用博客园博客的主要原因在于能够使用live writer,不用每次都打开网页,当然博客园的大牛很多   如果可以使用方法word,当让更爽,格式的问题将不再是问 ...

    戏(细)说Executor框架线程池任务执行全过程(上)

    一.前言 1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦.要执行任务的人只需把Task描述清楚,然后提交即可.这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不 ...

    Vbox安装oracle-linux报错:VT-x features locked or unavailable in M

    1.安装完Vbox后,通过vbox来安装oracle-linux时报“VT-x features locked or unavailable in MSR”: 2.报错原因:CPU没有开启虚拟化支持 ...

    【HDOJ】2717 Catch That Cow

    bfs. /* 2717 */ #include #include #include #include ...

    java的常见异常与错误总结

    算术异常类:ArithmeticExecption 空指针异常类:NullPointerException 类型强制转换异常:ClassCastException 数组负下标异常:NegativeAr ...

    UEP-find查询

    实体类: @Entity @Table(name = "xxxxx") public class WzInitializeStoreInfo extends EntityBean{ ...

    【备忘】Idea的那些事

    说到Java的IDE,似乎eclipse和Idea是目前的主流.然而,OO的课程组却一直在推荐使用eclipse,于是很多人就这样错过了Idea这样强大的IDE工具.本文将会对于Idea和Idea的一 ...

    展开全文
  • 然而,就不能让同一个窗口内部使用个 UI 线程吗?阅读本文将收获一份 Win32 函数SetParent及相关函数的使用方法。WPF 同一个窗口中跨线程访问 UI 有多种方法:前者使用的是 WPF 原生方式,...

    WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验。如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程。然而,就不能让同一个窗口内部使用多个 UI 线程吗?

    阅读本文将收获一份 Win32 函数 SetParent 及相关函数的使用方法。

    WPF 同一个窗口中跨线程访问 UI 有多种方法:

    前者使用的是 WPF 原生方式,做出来的跨线程 UI 可以和原来的 UI 相互重叠遮挡。后者使用的是 Win32 的方式,实际效果非常类似 WindowsFormsHost,新线程中的 UI 在原来的所有 WPF 控件上面遮挡。另外,后者不止可以是跨线程,还可以跨进程。

    准备必要的 Win32 函数

    完成基本功能所需的 Win32 函数是非常少的,只有 SetParent 和 MoveWindow。

    [DllImport("user32.dll")]

    public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);

    [DllImport("user32.dll", SetLastError = true)]

    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    SetParent 用于指定传统的窗口父子关系。有多传统呢?呃……就是 Windows 自诞生以来的那种传统。在传统的 Win32 应用程序中,每一个控件都有自己的窗口句柄,它们之间通过 SetParent 进行连接;可以说一个 Button 就是一个窗口。而我们现在使用 SetParent 其实就是在使用传统 Win32 程序中的控件的机制。

    MoveWindow 用于指定窗口相对于其父级的位置,我们使用这个函数来决定新嵌入的窗口在原来界面中的位置。

    启动后台 UI 线程

    启动一个后台的 WPF UI 线程网上有不少线程的方法,但大体思路是一样的。我之前在 如何实现一个可以用 await 异步等待的 Awaiter 一文中写了一个利用 async/await 做的更高级的版本。

    为了继续本文,我将上文中的核心文件抽出来做成了 GitHubGist,访问 Custom awaiter with background UI thread 下载那三个文件并放入到自己的项目中。

    AwaiterInterfaces.cs 为实现 async/await 机制准备的一些接口,虽然事实上可以不需要,不过加上可以防逗比。

    DispatcherAsyncOperation.cs 这是我自己实现的自定义 awaiter,可以利用 awaiter 的回调函数机制规避线程同步锁的使用。

    UIDispatcher.cs 用于创建后台 UI 线程的类型,这个文件包含本文需要使用的核心类,使用到了上面两个文件。

    在使用了上面的三个文件的情况下,创建一个后台 UI 线程并获得用于执行代码的 Dispatcher 只需要一句话:

    // 传入的参数是线程的名称,也可以不用传。

    var dispatcher = await UIDispatcher.RunNewAsync("Background UI");

    在得到了后台 UI 线程 Dispatcher 的情况下,无论做什么后台线程的 UI 操作,只需要调用 dispatcher.InvokeAsync 即可。

    我们使用下面的句子创建一个后台线程的窗口并显示出来:

    var backgroundWindow = await dispatcher.InvokeAsync(() =>

    {

    var window = new Window();

    window.SourceInitialized += OnSourceInitialized;

    window.Show();

    return window;

    });

    在代码中,我们监听了 SourceInitialized 事件。这是 WPF 窗口刚刚获得 Windows 窗口句柄的时机,在此事件中,我们可以最早地拿到窗口句柄以便进行 Win32 函数调用。

    private void OnSourceInitialized(object sender, EventArgs e)

    {

    // 在这里可以获取到窗口句柄。

    }

    嵌入窗口

    为了比较容易写出嵌入窗口的代码,我将核心部分代码贴出来:

    class ParentWindow : Window

    {

    public ParentWindow()

    {

    InitializeComponent();

    Loaded += OnLoaded;

    }

    private async void OnLoaded(object sender, RoutedEventArgs e)

    {

    // 获取父窗口的窗口句柄。

    var hwnd = (HwndSource) PresentationSource.FromVisual(this);

    _parentHwnd = hwnd;

    // 在后台线程创建子窗口。

    var dispatcher = await UIDispatcher.RunNewAsync("Background UI");

    await dispatcher.InvokeAsync(() =>

    {

    var window = new Window();

    window.SourceInitialized += OnSourceInitialized;

    window.Show();

    });

    }

    private void OnSourceInitialized(object sender, EventArgs e)

    {

    var childHandle = new WindowInteropHelper((Window) sender).Handle;

    SetParent(childHandle, _parentHwnd.Handle);

    MoveWindow(childHandle, 0, 0, 300, 300, true);

    }

    private HwndSource _parentHwnd;

    [DllImport("user32.dll")]

    public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);

    [DllImport("user32.dll", SetLastError = true)]

    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

    }

    具体执行嵌入窗口的是这一段:

    private void OnSourceInitialized(object sender, EventArgs e)

    {

    var childHandle = new WindowInteropHelper((Window) sender).Handle;

    SetParent(childHandle, _parentHwnd.Handle);

    MoveWindow(childHandle, 0, 0, 300, 300, true);

    }

    最终显示时会将后台线程的子窗口显示到父窗口的 (0, 0, 300, 300) 的位置和大小。可以试试在主线程写一个 Thread.Sleep(5000),在卡顿的事件内,你依然可以拖动子窗口的标题栏进行拖拽。

    L3Byb3h5L2h0dHBzL3dhbHRlcmx2LmNvbS9zdGF0aWMvcG9zdHMvMjAxOC0wNy0xMS0yMS0zMS0wNy5wbmc=.jpg

    当然,如果你认为外面那一圈窗口的非客户区太丑了,使用普通设置窗口属性的方法去掉即可:

    await dispatcher.InvokeAsync(() =>

    {

    var window = new Window

    {

    BorderBrush = Brushes.DodgerBlue,

    BorderThickness = new Thickness(8),

    Background = Brushes.Teal,

    WindowStyle = WindowStyle.None,

    ResizeMode = ResizeMode.NoResize,

    Content = new TextBlock

    {

    Text = "walterlv.github.io",

    HorizontalAlignment = HorizontalAlignment.Center,

    VerticalAlignment = VerticalAlignment.Center,

    Foreground = Brushes.White,

    FontSize = 24,

    }

    };

    window.SourceInitialized += OnSourceInitialized;

    window.Show();

    });

    L3Byb3h5L2h0dHBzL3dhbHRlcmx2LmNvbS9zdGF0aWMvcG9zdHMvMjAxOC0wNy0xMS0yMS0zMy01NS5wbmc=.jpg

    WPF 同一窗口内的多线程 UI(VisualTarget)

    WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验.如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程.然而,就不能让 ...

    C&num;关闭一个窗口的同时打开另一个窗口

    在.net的WinForm程序中,如果是直接起动的Form作为主窗口,那么这个主窗口是不能关闭的,因为它维护了一个Windows消息循环,它一旦关闭了就等于声明整个应用程序结束,所以新打开的窗口也就被 ...

    IDEA一个窗口打开多个项目

    首先IDEA没有Eclipse的Workspace的概念,且IDEA推荐是一个窗口对应着一个Project. 然后经过研究你会发现IDEA其实是由一个主进程来维护这些窗口的,所以即使你开了很多个窗口, ...

    拒绝卡顿——在WPF中使用多线程更新UI

    原文:拒绝卡顿--在WPF中使用多线程更新UI 有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会造成界面卡顿,如下就是一个简单的示例: public partial class MainW ...

    C&num; Wpf异步修改UI,多线程修改UI(二)

    1.使用定时器异步修改 这是相对比较简单的方法 在Wpf中定时器使用DiapatcherTimer,不使用Timer原因: 在一个应用程序中,Timer会重复生成time事件,而DispatcherT ...

    WPF里面多线程访问UI线程、主线程的控件

    如果出现以下错误:调用线程无法访问此对象,因为另一个线程拥有该对象. 你就碰到多线程访问UI线程.主线程的控件的问题了. 先占位.

    C&plus;&plus;程序员面试题目总结&lpar;涉及C&plus;&plus;基础、多线程多进程、网络编程、数据结构与算法&rpar;

    说明:C++程序员面试题目总结(涉及C++基础知识.多线程多进程.TCP/IP网络编程.Linux操作.数据结构与算法) 内容来自作者看过的帖子或者看过的文章,个人整理自互联网,如有侵权,请联系作者 ...

    Android多线程更新UI的方式

    Android下,对于耗时的操作要放到子线程中,要不然会残生ANR,本次我们就来学习一下Android多线程更新UI的方式. 首先我们来认识一下anr: anr:application not rep ...

    Python多线程多进程那些事儿看这篇就够了~~

    自己以前也写过多线程,发现都是零零碎碎,这篇写写详细点,填一下GIL和Python多线程多进程的坑~ 总结下GIL的坑和python多线程多进程分别应用场景(IO密集.计算密集)以及具体实现的代码模块 ...

    随机推荐

    编写高质量代码&colon;改善Java程序的151个建议&lpar;第1章&colon;JAVA开发中通用的方法和准则&lowbar;&lowbar;&lowbar;建议16~20&rpar;

    建议16:易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP,Ruby,Groovy.Javascript等,这些入侵者都有一个共同特征:全是同一类语言-----脚本语言,它 ...

    ASP&period;NET生成WORD文档,服务器部署注意事项

    网上转的,留查备用,我服务器装的office2007所以修改的是Microsoft Office word97 - 2003 文档这一个. ASP.NET生成WORD文档服务器部署注意事项 1.Asp ...

    pt-query-digest使用介绍【转】

    本文来自:http://isadba.com/?p=651 一.pt-query-digest参数介绍. pt-query-digest --user=anemometer --password=an ...

    Rotate List &vert;&vert; LeetCode

    /** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * } ...

    JQ 全选、全不选

    $(document).ready(function() { $("#isalldebt").click(function() { if ($(this).attr("c ...

    海量数据的二度人脉挖掘算法(Hadoop 实现)

    最近做了一个项目,要求找出二度人脉的一些关系,就好似新浪微博的“你可能感兴趣的人” 中,间接关注推荐:简单描述:即你关注的人中有N个人同时都关注了 XXX . 在程序的实现上,其实我们要找的是:若 U ...

    Android开发重修

    Android程序开发的重新学习 #####Mars课程学习笔记20140926 1.Service主要用于完成耗时较长的操作,没有图形化界面. 2.Content Provider数据的提供者,是A ...

    Route-map简介

    Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE MicrosoftInternetExplorer4 /* Style Definiti ...

    HDU 4185 Oil Skimming 【最大匹配】

    题目大意: 给你一张图,图中有 '*' , '.' 两点,现在每次覆盖相邻的两个 '#' ,问最多能够覆盖几次. 解题分析: 无向图二分匹配的模板题,每个'#'点与周围四个方 ...

    展开全文
  • 由于因特网的用户数量较,所以因特网在命名时采用的是层次树状结构的命名方法。任何一个连接在因特网上的主机或路由器,都有一个唯一的层次结构的名字,即域名(domain name)。这里,“域”(domain)是名字空间中一...

    DNS:域名系统DNS(Domain Name System)是因特网使用的命名系统,用来把便于人们使用的机器名字转换成为IP地址。
    1、因特网的域名结构
    由于因特网的用户数量较多,所以因特网在命名时采用的是层次树状结构的命名方法。任何一个连接在因特网上的主机或路由器,都有一个唯一的层次结构的名字,即域名(domain name)。这里,“域”(domain)是名字空间中一个可被管理的划分。
    域名只是逻辑概念,并不代表计算机所在的物理地点。分为三大类:
    (1)国家顶级域名:采用ISO3166的规定。如:cn代表中国,us代表美国,uk代表英国,等等。国家域名又常记为ccTLD(cc表示国家代码contry-code)
    (2)通用顶级域名:最常见的通用顶级域名有7个,即:com(公司企业),net(网络服务机构),org(非营利组织),int(国际组织),gov(美国的政府部门),mil(美国的军事部门)。
    (3)基础结构域名(infrastructure domain):这种顶级域名只有一个,即arpa,用于反向域名解析,因此称为反向域名。
    发展:
    Hosts文件 ---------------需要不停地添加域名
    1.周期性任务 ---------------在指定时间自动的去写(自动化)
    2.server ---------------东西太多(性能)
    3.分布式数据库 -------------全球各地分放
    查找方式:
    递归 ------计算机只发送一次请求
    迭代 -----多次
    实际解析分为两段,一段递归一段迭代
    客户端和本地DNS服务器之间属于递归查询
    DNS服务器和DNS服务器之间属于迭代查询
    在这里插入图片描述
    域名解析过程:当客户机中输入www.baidu.com时,这时客户机首先会查询本地浏览器缓存,如果有则调用这个映射关系,完成域名解析,如果没有则查看hosts文件,如果有则调用,如果没有则查看DNS解析器缓存,如果有则返回,如果没有,则请求参数中设置的DNS服务器,称为本地DNS服务器,请求本地DNS服务器,如果解析的域名在本地DNS服务器的配置区域资源中,则立即返回,此结果为权威性答案。如果查询的域名不在本地DNS服务器的配置区域资源中,但是在DNS服务器缓存中含有该域名的映射,则调用这个IP地址,完成域名解析,此结果不含有权威性。如果还没有查询到,就会根据本地DNS服务器的设置进行下一步操作,如果未启用转发模式,则本地DNS就把请求发送给13台根服务器,根服务器会根据收到的请求发送给本地服务器负责管理(.com)这个域名是的一个顶级域名服务器的IP,本地DNS服务器收到这个IP后会联系负责.com的这台服务器,如果这台服务器也不能解析,则会发送负责管理(qq.com)的二级域名服务器的IP,本地DNS收到后会联系这个IP的服务器,重复操作,直到找到www.bai.com的主机,如果为转发模式,则会转发给上一级的DNS服务器,由上一级的DNS服务器解析,如果也不能解析则转发给上上级DNS服务器或者发送给根服务器,以此循环。找到最后把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。
    DNS 服务类型:
    主DNS服务器:数据修改(接受用户请求返回数据) master
    辅助dns服务器: 定期请求数据同步 slave
    缓存dns服务器: 只缓存dns数据 hint
    转发服务器:缓存服务器去掉缓存功能 forward

    配置dns主从服务器,解析qq.com域下的所有主机,www.qq.com aaa.qq.com bbb.qq.com …

    主服务器

    Vim /etc/named.conf
    在这里插入图片描述
    Cp /var/named/named.localhost /var/named/named.qq.com
    Vim /var/name/named.baidu.com
    在这里插入图片描述
    访问需要停止防火墙:
    systemctl stop firewalld
    setenforce 0

    从服务器的搭建

    #yum install bind -y
    访问需要停止防火墙:
    systemctl stop firewalld
    setenforce 0
    (2)#vim /etc/named.conf
    options {
    listen-on port 53 { 192.168.80.130; };
    directory “/var/named/slaves”;
    };
    zone “baidu.com” IN {
    type slave;
    file “named.baidu”;
    masters { 172.24.8.129; }; 注意:主服务的ip
    };
    (3)#systemctl restart named
    在这里插入图片描述
    (4)重启服务
    systemctl restart named

    客户端

    Vim /etc/resolv.conf
    将nameserver的IP换为主服务器的IP地址
    关闭防火墙
    systemctl stop firewalld
    setenforce 0
    在这里插入图片描述
    输入域名进行解析
    在这里插入图片描述
    在从服务器中检验
    在这里插入图片描述

    展开全文
  • 厚积薄发打卡Day25 :狂神说Java之线程详解(全网最全_代码+笔记) 概述 课程大纲: 线程简介 线程实现(重点) 线程状态 线程同步(重点) 线程通信问题 高级主题 线程、进程、线程 线程简介: 任务: ...

    概述

    视频来源:【狂神说Java】多线程详解
    强烈推荐,👍👍👍

    课程大纲:

    • 线程简介

    • 线程实现(重点)

    • 线程状态

    • 线程同步(重点)

    • 线程通信问题

    • 高级主题

    线程、进程、多线程

    线程简介:

    多任务:

    • 现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事情。

    多线程:

    • 原来是一条路,慢慢因为车太多了,道路堵塞,效率极低。为了提高使用的效率,能够充分利用道路,于是加了多个车道。从此,妈妈再也不用担心道路堵塞了。

    程序 -> 进程 -> 线程

    可以简单理解为 进程就是进行中的程序,而多条线程共同组成一个进程

    关于程序和进程区别的概念

    • 程序是指令和数据的集合,可以作为目标文件保存在磁盘中,或者作为段存放在内存地址空间中。
    • 进程是程序运行的一个具体的实例,程序总是运行在某个进程的上下文中。

    普通方法调用与多线程:

    • 调用run()方法:

      • 当作普通方法,此时只有主线程 一条 依次执行路径
    • 调用start()方法:

      • 开启多线程,多条执行路径,主线程与子线程并行交替执行

    Process与Thread

    • 说起进程,就不得不说下程序:
      • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
    • 进程则:
      • 是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
    • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。(真正执行的线程
      • main 为主线程
    • ▶️ 注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

    核心概念:

    • 线程就是独立的执行路径;
    • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
    • main()称之为主线程,为系统的入口,用于执行整个程序;
    • 在一个进程中,如果开辟了多个线程,线程的运行由调度器(中央处理器(central processing unit,简称CPU))安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
    • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制(需要控制其依序执行);
    • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
    • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

    线程的创建

    Thread , Runnable ,Callable

    三种创建方式

    • Thread Class ----> 继承Thread类(重点
    • Runnable接口 ----> 实现Runnable接口(重点
    • Callable 接口 ----> 实现Callable接口(了解)

    1.Thread

    • 简单看看源码这个中的run()与start()方法
    public
        class Thread implements Runnable {
            ...
                /**
        如果这个线程是使用单独的Runnable运行对象构造的,则Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
        线程的Thread应该覆盖此方法。
        */
                @Override
                public void run() {
                if (target != null) {
                    target.run();
                }
            }
    
            /**
            导致此线程开始执行; Java虚拟机调用此线程的run方法。
    		结果是两个线程同时运行:当前线程(从调用返回到start方法)和另一个线程(执行其run方法)。不止一次启动线程是不合法的。 另外,一旦线程完成执行就可能不会重新启动。
            */
            public synchronized void start() {
                if (threadStatus != 0)
                    throw new IllegalThreadStateException();
                group.add(this);
    
                boolean started = false;
                try {
                    start0();
                    started = true;
                } finally {
                    try {
                        if (!started) {
                            group.threadStartFailed(this);
                        }
                    } catch (Throwable ignore) {
                    }
                }
            }
            ...
        }
    
    • 自定义线程类继承Thread类
    • 重写run()方法,编写线程执行体
      • 多线程执行的内容
    • 创建线程对象,调用start()方法启动线程
      • 多线程启动
    • 线程不一定立即执行,CPU(佛系)安排调度
    //创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
    public class TestThread01 extends Thread {
    
        @Override
        public void run(){
            //run方法 线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("我在看代码----"+i);
            }
        }
    
        public static void main(String[] args) {
            //main线程 主线程
            //创建线程对象
            TestThread01 testThread01 = new TestThread01();
            //开启线程
            testThread01.start();
    
            //执行主线程:
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习多线程----"+i);
            }
        }
        /***
         * 总结:
         * 注意,线程开启不一定立即执行,由cpu调度随机(佛系)执行
         */
    }
    

    运行结果:(开启线程 与 主线程 交替执行)

    ...
    我在看代码----193
    我在看代码----194
    我在看代码----195
    我在看代码----196
    我在看代码----197
    我在学习多线程----92
    我在看代码----198
    我在看代码----199
    我在学习多线程----93
    我在学习多线程----94
    我在学习多线程----95
    我在学习多线程----96
    ...
    
    案例:下载图片
    //练习Thread,实现多线程同步下载图片
    public class TestThread02 extends Thread {
    
        private String url; //网络图片地址
        private String name; //保存的文件名
    
        public TestThread02(String url,String name){
            this.url = url;
            this.name = name;
        }
    
        @Override
        public void run(){
            WebDownloader webDownloader = new WebDownloader();
            webDownloader.downloader(url,name);
            System.out.println("下载了文件名为:"+name );
        }
    
        public static void main(String[] args) {
            TestThread02 t1 = new TestThread02("https://img-blog.csdnimg.cn/20210115150524854.PNG","1.png");
            TestThread02 t2 = new TestThread02("https://img-blog.csdnimg.cn/20210115150542689.PNG","2.png");
            TestThread02 t3 = new TestThread02("https://img-blog.csdnimg.cn/20210115150559183.PNG","3.png");
    
            t1.start();
            t2.start();
            t3.start();
        }
    
    
    }
    
    //下载器
    class WebDownloader{
        //下载方法
        public void downloader(String url,String name){
            try {
                FileUtils.copyURLToFile(new URL(url),new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader方法出现问题");
            }
        }
    }
    
    下载了文件名为:2.png
    下载了文件名为:3.png
    下载了文件名为:1.png
    
    //打印结果为非1、2、3的顺序,说明不是依次执行,而是开了线程执行同时执行
    

    2.Runnable

    实现Runnable接口

    • 定义MyRunnable类实现Runnable接口

    • 实现run()方法,编写线程执行体

    • 创建线程对象,调用start()方法启动线程

      推荐使用Runnable对象,因为Java单继承的局限性

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    
    • 例子
    //创建线程方式2:实现runnable接口,重写run方法,
    //执行线程需要丢入runnable接口实现类,调用start方法
    public class TestThread03 implements Runnable{
    
        @Override
        public void run(){
            //run方法 线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("我在看代码----"+i);
            }
        }
    
        public static void main(String[] args) {
            //创建runnable接口实现类对象
            TestThread03 t3 = new TestThread03();
            //创建线程对象,通过西安城对象来开启线程,代理的方式
            new Thread(t3).start();
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习多线程---"+i);
            }
    
        }
    }
    
    ...
    我在看代码----144
    我在学习多线程---95
    我在看代码----145
    我在学习多线程---96
    我在看代码----146
    我在学习多线程---97
    我在学习多线程---98
    我在看代码----147
    我在学习多线程---99
    我在看代码----148
    我在学习多线程---100
    我在看代码----149
    ...
    //同样交替执行
    
    • 网图实例修改用实现Runnable接口的方式

      public class TestThread02WithRunnable implements Runnable {
      	...
              
          public static void main(String[] args) {
              TestThread02 t1 = new TestThread02("https://img-blog.csdnimg.cn/20210115150524854.PNG", "1.png");
              TestThread02 t2 = new TestThread02("https://img-blog.csdnimg.cn/20210115150542689.PNG", "2.png");
              TestThread02 t3 = new TestThread02("https://img-blog.csdnimg.cn/20210115150559183.PNG", "3.png");
      
              //不同点:
              new Thread(t1).start();
              new Thread(t2).start();
              new Thread(t3).start();
          }
      
      }
      

    小结:

    • 继承Thread类

      • 子类继承Thread类具备多线程能力
      • 启动线程:子类对象.start()
      • 不建议使用:避免OOP单继承局限性
    • 实现Runnable接口

      • 实现接口Runnable具有多线程能力
      • 启动线程:传入目标对象+Thread对象.start()
      • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
      //-份资源
      startThread4 station = new StartThread4();
      //多个代理
      new Thread(station,name:"小明").start();
      new Thread(station,name:"老师").start();
      new Thread(station,name:"小红").start()
      

    3.Callable

    实现callable接口实现多线程(了解即可)

    1. 实现Callable接口,需要返回值类型

    2. 重写call方法,需要抛出异常

    3. 创建目标对象

    4. 创建执行服务:

      ExecutorService ser=Executors.newFixedThreadPool(1);

    5. 提交执行:

      Future<Boolean>result1=ser.submit(t1);

    6. 获取结果

      boolean r1=result1.get()

    7. 关闭服务:

      ser.shutdownNow();

      • 演示:利用callable改造下载图片案例
      //线程创建方式三:实现callable接口
      public class TestCallable implements Callable<Boolean> {
          @Override
          public Boolean call() throws Exception {
              WebDownloader webDownloader = new WebDownloader();
              webDownloader.downloader(url, name);
              System.out.println("下载了文件名为:" + name);
              return true;
          }
      
      
          private String url; //网络图片地址
          private String name; //保存的文件名
      
          public TestCallable(String url, String name) {
              this.url = url;
              this.name = name;
          }
      
          public static void main(String[] args) throws ExecutionException, InterruptedException {
              TestCallable t1 = new TestCallable("https://img-blog.csdnimg.cn/20210115150524854.PNG", "1.png");
              TestCallable t2 = new TestCallable("https://img-blog.csdnimg.cn/20210115150542689.PNG", "2.png");
              TestCallable t3 = new TestCallable("https://img-blog.csdnimg.cn/20210115150559183.PNG", "3.png");
      
              //创建执行服务:
              ExecutorService ser = Executors.newFixedThreadPool(3);
      
              //提交执行:
              Future<Boolean> r1 = ser.submit(t1);
              Future<Boolean> r2 = ser.submit(t2);
              Future<Boolean> r3 = ser.submit(t3);
      
              //获取结果:
              boolean rs1 = r1.get();
              boolean rs2 = r2.get();
              boolean rs3 = r3.get();
      
              //关闭服务:
              ser.shutdownNow();
          }
      
      
          //下载器
          class WebDownloader {
      		//...同上
          }
      
      }
      
    8. 同样实现了多线程的效果,只不过略有区别:

      1. 可以定义返回值
      2. 可以抛出异常

    初识线程并发:

    举个栗子:

    //多个线程同时操作同一个对象
    //买火车票的例子
    public class TestThread04 implements Runnable {
    
        //票数
        private int ticketNums = 10;
    
        @Override
        public void run() {
            while (true) {
                if (ticketNums <= 0) {
                    break;
                }
                //模拟延时:不然执行太快看不到线程不安全的效果:
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
            }
        }
    
        public static void main(String[] args) {
            TestThread04 ticketThread = new TestThread04();
            new Thread(ticketThread ,"小明").start();
            new Thread(ticketThread ,"老师").start();
            new Thread(ticketThread ,"黄牛党").start();
    
        }
    }
    

    模拟结果(其中一次):

    老师--->拿到了第10张票
    黄牛党--->拿到了第9张票
    小明--->拿到了第10张票
    黄牛党--->拿到了第8张票
    老师--->拿到了第6张票
    小明--->拿到了第7张票
    老师--->拿到了第5张票
    小明--->拿到了第3张票
    黄牛党--->拿到了第4张票
    老师--->拿到了第1张票
    小明--->拿到了第2张票
    黄牛党--->拿到了第0张票
    

    仔细一看:出大问题

    出大问题:(线程并发问题)

    1. 多个线程操作同一个资源的情况下

    2. 线程不安全,数据紊乱,操作脏数据


    再举个例子:龟兔赛跑-Race(用线程模拟龟兔赛跑)

    1.首先来个赛道距离,然后要离终点越来越近
    2.判断比赛是否结束
    3.打印出胜利者
    4.龟兔赛跑开始
    5.故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
    6.终于,乌龟赢得比赛

    //模拟龟兔赛跑
    public class Race implements Runnable {
    
        //胜利者
        private static String winner;
    
        @Override
        public void run() {
            for (int i = 0; i <=100; i++) {
                //强制模拟兔子休息:兔子会在剩下10步的时候骄傲,偷懒睡觉(睡个100ms)
                if ("兔子".equals(Thread.currentThread().getName()) && i>=90){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                //判断比赛是否结束:
                boolean flag = gameOver(i);
                if (flag){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"-->跑了"+ i +"步");
            }
        }
    
        //判断是否完成了比赛:
        private boolean gameOver(int steps){
            //判断是否由胜利者:
            if (winner != null){
                return true;
            }else if (steps >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is --->"+ winner);
                return true;
            }else {
                return false;
            }
    
        }
    
        public static void main(String[] args) {
            Race race = new Race();
    
            new Thread(race,"乌龟").start();
            new Thread(race,"兔子").start();
        }
    }
    //被迫睡觉了之后就一定是 乌龟赢,大家可以试试
    

    静态代理模式:

    • 演示:实现静态代理模式(Thread)

    • 用婚庆公司帮客户安排婚礼的例子:

      • Wayne:结婚对象
      • WeddingCompany:婚庆公司
      public class TestStacticProxy {
      
          public static void main(String[] args) {
              WeddingCompany weddingCompany = new WeddingCompany(new Wayne());
              weddingCompany.MarryHappily();
          }
      }
      
      interface Marry{
          void MarryHappily();
      }
      
      
      //结婚对象:
      class Wayne implements Marry{
      
          @Override
          public void MarryHappily() {
              System.out.println("⭐⭐⭐Wayne要结婚了贼开心⭐⭐⭐");
          }
      }
      
      //婚庆公司:协助结婚对象结婚
      class WeddingCompany implements Marry{
      
          private Marry target;
      
          public WeddingCompany(Marry target) {
              this.target = target;
          }
      
          @Override
          public void MarryHappily() {
              //结婚前工作
              before();
              //婚礼中协助客户结婚:
              this.target.MarryHappily();
              //结婚后工作:
              after();
          }
      
          private void after() {
              System.out.println("结婚后收尾款...");
          }
      
          private void before() {
              System.out.println("结婚前制定婚礼方案...");
          }
      }
      //打印结果:
      /**
      结婚前制定婚礼方案...
      ⭐⭐⭐Wayne要结婚了贼开心⭐⭐⭐
      结婚后收尾款...
      */
      
    • 上述例子简单总结静态代理模式:需扩展静态代理模式

      1. 真实对象和代理对象都要实现同一个接口

        • 上述例子Wayne 和WeddingCompany 都实现了 Marry接口
      2. 代理对象要代理真实角色

        • class WeddingCompany implements Marry{
          	//代理谁 --> 真实的目标角色
              private Marry target;
              ...
              @Override
              public void MarryHappily() {
                  ...
                  //在方法中调用真实对象的方法:
                  this.target.MarryHappily();
          		...
              }
              ... 
          }
          
      3. 优点:

        1. 代理对象可以实现许多真实对象实现不了的事情
          • 结婚对象 只负责结婚,不会筹划婚礼,交给婚庆公司做
        2. 真实对象专注自己的事情
          • 结婚对象好好结婚,代码的专一。
    • 为啥要突然讲到这个静态代理模式?

      • 重点来了
      //将上述静态代理的(main)代码稍作合并:
      //  代理对象            真实对象
      new WeddingCompany(new Wayne()).MarryHappily();
      
      // 代理对象         真实对象:runnable
      new Thread(()->System.out.println("这是线程开启")).start();
      
      • 综上:
        • Thread类是Runnable的代理对象,其本质是通过静态代理模式开启线程的。

    Lamda表达式:

    • λ \lambda λ 希腊字母表中排序第十一位的字母,英语名称为Lambda

    • 避免匿名内部类定义过多

    • 其实质属于函数式编程的概念

      (params)->expression[表达式]	
      	(params)->statement[语句]
      		(params)->{statements}
      

      a ->System.out.println("I like lambda -->" +a)

      new Thread (0->System.out.println(“多线程学习。。。")).start();
      
    • 为什么要使用lambda表达式

      • 避免匿名内部类定义过多
      • 可以让你的代码看起来很简洁
      • 去掉了一堆没有意义的代码,只留下核心的逻辑。
    • 也许你会说,我看了Lambda表达式,不但不觉得简洁,反而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多了,看习惯了,就好了。

    理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在

    函数式接口的定义:

    • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

      public interface Runnable{
          public abstract void run();
      }
      
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

    • 演示:推导lambda表达式

      /**
       推导lambda表达式
       */
      public class TestLambda1 {
      
          //3.静态内部类
          static class Like2 implements ILike{
      
              @Override
              public void lambda() {
                  System.out.println("I like lambda_static");
              }
          }
      
          public static void main(String[] args) {
              ILike like = new Like();
              like.lambda();
      
              like = new Like2();
              like.lambda();
      
              //4.局部内部类
              class Like3 implements ILike{
                  @Override
                  public void lambda() {
                      System.out.println("I like lambda_inner");
                  }
              }
      
              like = new Like3();
              like.lambda();
      
              //5.匿名内部类:没有类的名称,必须借助接口或者父类
              like = new ILike() {
                  @Override
                  public void lambda() {
                      System.out.println("I like lambda_anonymousInner");
                  }
              };
              like.lambda();
      
              //6.用lambda简化:
              like =()->{
                System.out.println("I like lambda_withLambda");
              };
              like.lambda();
          }
      }
      //1.定义一个函数式接口
      interface ILike{
          void lambda();
      }
      //2.实现类
      class Like implements ILike{
      
          @Override
          public void lambda() {
              System.out.println("I like lambda_ori");
          }
      }
      
      I like lambda_ori
      I like lambda_static
      I like lambda_inner
      I like lambda_anonymousInner
      I like lambda_withLambda
      

      简化lambda表达式:

      public class TestLambda2 {
          public static void main(String[] args) {
              Dancer dancer = null;
      
              dancer = (int a) -> {
                  System.out.println("i like dancing-->" + a);
              };
              /* dancer = a -> {
                  System.out.println("i like dancing-->" + a);
              };
              */
              dancer.dance(5);
          }    
      }
      
      interface Dancer {
          void dance(int a);
      }
      

    线程状态:

    五大状态:

    在这里插入图片描述

    • new(新生):
      • Thread t= new Thread(),线程对象一旦创建就进入到了新生状态
    • 就绪状态
      • 当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行
    • 运行状态
      • 进入运行状态,线程真正执行线程体的代码块run方法中的代码块
    • 阻塞状态
      • 当调用sleep、wait或同步锁时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行
    • dead(中断或死亡):
      • 线程中断或者结束,一旦进入死亡状态,就不能再次启动

    线程方法

    方法说明
    setPriority(int riewPriority)更改线程的优先级
    static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
    void join()等待该线程终止
    static void yield()暂停当前正在执行的线程对象,并执行其他线程
    void interrupt()中断线程,别用这个方式
    boolean isAlive()测试线程是否处于活动状态

    停止线程:

    • 不推荐使用JDK提供的stop()、destroy()方法。【已废弃】

    • 推荐线程自己停止下来

    • 建议使用一个标志位进行终止变量:当flag=false,则终止线程运行。

      • 举个例子:
      //测试线程stop
      //1.建议线程正常停止 --> 利用次数,不建议死循环:如龟兔赛跑时break了
      //2.建议使用标志位 -->设置一个标志位
      //3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
      public class TestThreadStop  implements Runnable{
          //1.设置一个标志位:
          private boolean flag = true;
      
          @Override
          public void run() {
              int i = 0;
              while (true){
                  if (!flag){
                      break;
                  }
                  System.out.println("run...Thread-->" + i++);
              }
          }
      
          //2.设置一个公开的方法停止线程,转换标志位
          public void stop(){
              this.flag = false;
          }
      
          public static void main(String[] args) {
              TestThreadStop testThreadStop = new TestThreadStop();
              new Thread(testThreadStop).start();
              for (int i = 0; i < 100; i++) {
                  System.out.println("main-->"+i);
                  if (i==90){
                      //调用stop方法切换标志位,让线程停下:
                      testThreadStop.stop();
                      System.out.println("⭐线程该停止了⭐");
                  }
              }
      
          }
      }
      

    线程休眠:

    • sleep(时间)指定当前线程阻塞的毫秒数;

    • sleep存在异常InterruptedException;

    • sleep时间达到后线程进入就绪状态;

    • sleep可以模拟网络延时,倒计时等。

    • 每一个对象都有一个锁,sleep不会释放锁;

      • 例子:
      //sleep可以放大问题的发生性:详见卖票例子(TestThread04)
      //模拟倒计时:
      public class TestThreadSleep {
      
          public static void main(String[] args) {
              try {
                  countDown();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      
          //模拟倒计时:
          public static void countDown() throws InterruptedException {
              int num = 10;
              while (true){
                  Thread.sleep(1000);
                  System.out.println(num--);
                  if (num <= 0){
                      break;
                  }
              }
          }
      }
      

    线程礼让:

    • 礼让线程,让当前正在执行的线程暂停,但不阻塞

    • 将线程从运行状态转为就绪状态

    • 让cpu重新调度,礼让不一定成功!看CPU心情(天大地大cpu最大😂)

      • 公车准备给老太太让座,但是公车上有无老太太是个随机事件

      //测试礼让线程
      //礼让不一定成功,看cpu心情:看你有没机会让
      public class TestThreadYield {
      
         public static void main(String[] args) {
             MyYield myYield = new MyYield();
      
             new Thread(myYield,"A").start();
             new Thread(myYield,"B").start();
         }
      
      }
      
      class MyYield implements Runnable{
      
         @Override
         public void run() {
             System.out.println(Thread.currentThread().getName()+" :线程开始执行");
             Thread.yield();//礼让
             System.out.println(Thread.currentThread().getName()+" :线程停止执行");
         }
      }
      

      礼让成功:

      B :线程开始执行
      A :线程开始执行
      B :线程停止执行
      A :线程停止执行
      

    线程强制执行(插队)

    • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

    • 可以想象成插队

    • 举例:

      //测试join方法:插队 
      public class TestThreadJoin implements Runnable {
          
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  System.out.println("线程VIP来了"+i);
              }    
          }
      
          public static void main(String[] args) throws InterruptedException {
              //启动我们的线程:
              TestThreadJoin testThreadJoin = new TestThreadJoin();
              Thread thread = new Thread(testThreadJoin);
              thread.start();
              //主线程
              for (int i = 0; i < 50; i++) {
                  if (i==25){
                      thread.join();
                  }
                  System.out.println("main --> "+i);
              }
          }
      }
      
      //插队成功
      ...
      main --> 22
      main --> 23
      线程VIP来了2
      main --> 24
      线程VIP来了3
      线程VIP来了4
      线程VIP来了5
      ...
      线程VIP来了96
      线程VIP来了97
      线程VIP来了98
      线程VIP来了99
      main --> 25
      main --> 26
      main --> 27
      ...
      

    线程状态观测:

    • Thread.state

      线程状态。线程可以处于以下状态之一:

      • NEW (新生)
        • 尚未启动的线程处于此状态。
      • RUNNABLE (运行)
        • 在Java虚拟机中执行的线程处于此状态。
      • BLOCKED (阻塞)
        • 被阻塞等待监视器锁定的线程处于此状态。
      • WAITING (阻塞)
        • 正在等待另一个线程执行特定动作的线程处于此状态。
      • TIMED_WAITING (阻塞)
        • 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
      • TERMINATED(死亡)
        • 已退出的线程处于此状态。

      一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

    • //观察测试线程的状态
      public class TestThreadState {
      
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new Thread(() -> {
                  for (int i = 0; i < 5; i++) {
                      try {
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  System.out.println("⭐⭐⭐⭐TIME TO STOP⭐⭐⭐⭐");
              });
      
              //观察状态:
              Thread.State state = thread.getState();
              System.out.println("新建时->"+state); //此时新建,应该是new
      
              //观察启动后:
              thread.start();//启动线程
              state = thread.getState();  //给state重新赋值,节省空间
              System.out.println("启动后->"+state);
      
              while (state != Thread.State.TERMINATED){
                  //只要线程不终止,就一直在输出状态
                  /*
                   这里有点意思,如果改成10 or 1000 是不一样的结果,以后研究研究(挖坑)
                   */
                  Thread.sleep(10);
                  state = thread.getState();  //更新线程状态
                  System.out.println("循环输出->"+state);
              }
      
              //会报错,因为死亡的线程不会再运行(IllegalThreadStateException)
              //thread.start();
          }
      }
      
      

    线程优先级

    • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

    • 线程的优先级用数字表示,范围从1~10.

      Thread.MIN_PRIORITY = 1;
      Thread.MAX_PRIORITY = 10;
      Thread.NORM_PRIORITY = 5;
      
    • 使用以下方式改变或获取优先级
      getPriority().setPriority(intxxx)

    • 举例:

      //测试线程的优先级
      public class TestThreadPriority {
      
          public static void main(String[] args) {
              //主线程默认优先级:
              System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
      
              MyPriprity myPriprity = new MyPriprity();
              Thread t1 = new Thread(myPriprity);
              Thread t2 = new Thread(myPriprity);
              Thread t3 = new Thread(myPriprity);
              Thread t4 = new Thread(myPriprity);
              Thread t5 = new Thread(myPriprity);
              Thread t6 = new Thread(myPriprity);
      
              //先设置优先级,再启动
              t1.start();
      
              t2.setPriority(1);
              t2.start();
      
              t3.setPriority(4);
              t3.start();
      
              t4.setPriority(Thread.MAX_PRIORITY);
              t4.start();
      
              t5.setPriority(8);
              t5.start();
      
              t6.setPriority(7);
              t6.start();
          }
      }
      
      
      class MyPriprity implements Runnable{
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
          }
      }
      
    • //其中一次的结果
      main-->5      //main 入口,主线程必先启动
      Thread-0-->5
      Thread-2-->4
      Thread-3-->10   //最高优先级,但不一定是首先调度
      Thread-1-->1
      Thread-4-->8
      Thread-5-->7
      
    • 结果最高优先级的线程,反而不会每次首次运行。

      优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了。这都是看CPU的调度

    守护线程:

    • 线程分为用户线程守护线程
      • 用户线程:main
      • 守护线程:gc
    • 虚拟机必须确保用户线程执行完毕
    • 虚拟机不用等待守护线程执行完毕
      • 如:后台记录操作日志,监控内存,垃圾回收等待.
    //测试守护线程
    //上帝保护你
    public class TestThreadDaemon {
    
        public static void main(String[] args) {
            God god = new God();
            You you = new You();
    
            Thread thread = new Thread(god);
            thread.setDaemon(true);  //默认是false表示的是用户线程,一般的线程都是用户线程
    
            thread.start();//上帝(守护线程)启动
    
            new Thread(you).start();//你(用户线程)启动
        }
    }
    
    //上帝
    class God implements Runnable{
        @Override
        public void run() {
            while (true){
                System.out.println("上帝保护着你...");
            }
        }
    }
    
    //你
    class You implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 36500; i++) {
                System.out.println("你一生都开心地活着");
            }
            System.out.println("========= Goodbye! World! ========");  //Hello World
        }
    }
    
    • 运行结果:

      1. 当 ”goodbye world“打印后不久,守护线程也会停止

      2. 用户线程结束后守护线程不用管了。

      3. 好家伙,一百年结果两秒送走。

    线程的同步机制:

    线程同步:

    • 并发

      • 并发:同一个对象被多个线程同时操作
        • 抢火车票
        • 银行取钱
    • 现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,

      • 比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个来.
    • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步.线程同步其实就是一种等待机制:

      • 多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
    • 队列与锁

      • 撤硕排队,得上锁才安全,才能用撤硕

      • 经典老番:

        线程休眠:

        • 每一个对象都有一个锁,sleep不会释放锁
    • 处理并发问题:队列 + 锁

      • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized

      • 当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可.存在以下问题:

        • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
        • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
        • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.
      • 🐟与熊掌不可兼得,同理多线程

        • 安全与性能问题必有取舍

          想要安心上撤硕必须得锁门

    三大 线程不安全案例

    • 车站买票

      //线程不安全:买票例子
      //线程不安全,输出结果有买重票有负数票
      public class UnsafeBuyTicket {
          public static void main(String[] args) {
              BuyTicket station = new BuyTicket();
              new Thread(station,"抢票的我").start();
              new Thread(station,"买票的你们").start();
              new Thread(station,"可恶的黄牛党").start();
          }
      }
      
      class BuyTicket implements Runnable{
      
          //票
          private int ticketNums = 10;
          //外部停止方式
          boolean flag = true;
      
          @Override
          public void run() {
              //买票
              while (true){
                  if (flag){
                      try {
                          buy();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }else {
                      break;
                  }
              }
          }
      
          private void buy() throws InterruptedException {
              //判断是否有票
              if (ticketNums <= 0){
                  flag = false;
                  return;
              }
              //模拟延时,放大问题
              Thread.sleep(100);
              //买票
              System.out.println(Thread.currentThread().getName()+"--> 拿到第 "+ ticketNums--+" 张票");
          }
      }
      
      • 运行结果会出现重复以及负数的票
        • 此处以买到重复票为例:
      买票的你们--> 拿到第 10 张票
      抢票的我--> 拿到第 9 张票
      可恶的黄牛党--> 拿到第 8 张票
      买票的你们--> 拿到第 7 张票
      可恶的黄牛党--> 拿到第 5 张票
      抢票的我--> 拿到第 6 张票
      抢票的我--> 拿到第 3 张票
      买票的你们--> 拿到第 2 张票
      可恶的黄牛党--> 拿到第 4 张票
      可恶的黄牛党--> 拿到第 1 张票
      买票的你们--> 拿到第 1 张票
      抢票的我--> 拿到第 1 张票
      
    • 银行取钱

      //不安全的取钱
      //两个人去银行取钱
      public class UnsafeTakeMoney {
      
          public static void main(String[] args) {
              Account account = new Account(100, "创业基金");
              Withdrawal wayne = new Withdrawal(account, 50, "wayne");
              Withdrawal partner = new Withdrawal(account, 100, "partner");
      
              wayne.start();
              partner.start();
          }
      }
      
      class Account{
          //账户余额(用int模拟)
          int money;
          //卡名
          String name;
      
          public Account(int money, String name) {
              this.money = money;
              this.name = name;
          }
      }
      
      //银行:模拟取款
      class Withdrawal extends Thread{
      
          //账户
          Account account;
          //取钱数:
          int withdrawalMoney;
          //手上有钱:
          int ownMoney;
      
          public Withdrawal(Account account,int withdrawalMoney,String name){
              super(name);
              this.account = account;
              this.withdrawalMoney = withdrawalMoney;
      
          }
      
          @Override
          public void run() {
              //判断有没有钱
              if (account.money - withdrawalMoney < 0){
                  System.out.println(Thread.currentThread().getName()+" ->钱不够,取不了");
                  return;
              }
              //放大问题的发生性
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //卡内余额 = 账号现存 - 取数金额
              account.money = account.money - withdrawalMoney;
              //手中现金 = 之前手中的钱 + 取出的钱
              ownMoney = ownMoney + withdrawalMoney;
      
              System.out.println(account.name +"--余额为 : "+account.money);
              //this.getName() 等价 Thread.currentThread().getName()
              System.out.println(this.getName() + "手里的钱:"+ ownMoney);
          }
      }
      
      • 运行结果:

        • 会出现只有100 但是取出了150的效果
        创业基金--余额为 : -50
        创业基金--余额为 : -50
        partner手里的钱:100
        wayne手里的钱:50
        
    • ArrayList 与 Vector

      //测试List是否安全:
      public class UnsafeList {
          @Test
          public void  testArraylist() throws InterruptedException {
              List<String> list = new ArrayList<>();
              for (int i = 0; i < 10000; i++) {
                  new Thread(()->{
                      list.add(Thread.currentThread().getName());
                  }).start();
              }
              Thread.sleep(3000);
              System.out.println(list.size());
          }
      
          @Test
          public void  testVector() throws InterruptedException {
              List<String> list = new Vector<>();
              for (int i = 0; i < 10000; i++) {
                  new Thread(()->{
                      list.add(Thread.currentThread().getName());
                  }).start();
              }
              Thread.sleep(3000);
              System.out.println(list.size());
          }
      
          /**
           * 休眠的意义在于将所有创建的线程都添加进数组,
           * 防止cpu调用到主线程,然后输出数组长度
           */
          @Test
          public void  testTheUsageOfSleep_Arraylist() throws InterruptedException {
              List<String> list = new ArrayList<>();
              Thread thread = new Thread();
              for (int i = 0; i < 10000; i++) {
                  thread =  new Thread(()->{
                      list.add(Thread.currentThread().getName());
                  });
                  thread.start();
              }
              Thread.sleep(3000);
              System.out.println(list.size());
              System.out.println(thread.getState());
          }
          /**
           * 线程状态为TERMINATED 才是添加完成后数组的总长
           * 线程状态为RUNNABLE 则是中途调用了主线程,统计数组长度(还没add完)
           */
          @Test
          public void  testTheUsageOfSleep_Vector() throws InterruptedException {
              List<String> list = new Vector<>();
              Thread thread = new Thread();
              for (int i = 0; i < 10000; i++) {
                  thread =  new Thread(()->{
                      list.add(Thread.currentThread().getName());
                  });
                  thread.start();
              }
              Thread.sleep(3000);
              System.out.println(list.size());
              System.out.println(thread.getState());
          }
      }
      
      • 运行结果:
        • 当线程运行完后:
          • Arraylist.size() 总是小于 循环次数
          • Vector.size() 可以加满

    同步方法:

    • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:

      • synchronized方法

      • synchronized块

        • 同步方法:

          public synchronized void method(int args)

    • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,

      • 方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
      • 缺陷:若将一个大的方法申明为synchronized将会影响效率
    • 方法里面需要修改的内容才需要🔒,否则锁太多会浪费资源,影响性能。

      弹幕:上撤硕可以一起看,但不能一起上,得加🔒

    同步块:

    • 同步块:synchronized(Obj){}
    • Obj称之为同步监视器
      • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]
    • 同步监视器的执行过程
      1. 第一个线程访问,锁定同步监视器,执行其中代码.
      2. 第二个线程访问,发现同步监视器被锁定,无法访问.
      3. 第一个线程访问完毕,解锁同步监视器.
      4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

    修改上述不安全的案例:

    • 车站买票:

      • 增加同步方法,加synchronized关键字即可
      class BuyTicket implements Runnable{
      
          //票
          private int ticketNums = 10;
          //外部停止方式
          boolean flag = true;
      
          @Override
          public void run() {
              //买票
              while (true){
                  if (flag){
                      try {
                          // sleep得放在buy外边
                          // 因为放buy里面,会导致第一个线程在run里不断调用buy,直接买完所有票,放外面,直接sleep阻塞了,下一个线程就有机会了
                          Thread.sleep(100);
                          buy();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }else {
                      break;
                  }
              }
          }
      
          private synchronized void buy() throws InterruptedException {
              //判断是否有票
              if (ticketNums <= 0){
                  flag = false;
                  return;
              }
              //模拟延时,放大问题
              Thread.sleep(100);
              //买票
              System.out.println(Thread.currentThread().getName()+"--> 拿到第 "+ ticketNums--+" 张票");
          }
      }
      
      • 运行结果:

        买票的你们--> 拿到第 10 张票
        可恶的黄牛党--> 拿到第 9 张票
        抢票的我--> 拿到第 8 张票
        可恶的黄牛党--> 拿到第 7 张票
        买票的你们--> 拿到第 6 张票
        可恶的黄牛党--> 拿到第 5 张票
        抢票的我--> 拿到第 4 张票
        可恶的黄牛党--> 拿到第 3 张票
        买票的你们--> 拿到第 2 张票
        可恶的黄牛党--> 拿到第 1 张票
        
        //依次买票
        
    • 银行取钱:

      • 增加同步代码块,将账户锁起来,而不是用synchronized🔒银行
      //取钱
      //synchronized对象是this,此处为“银行”,是没用的
      // 因此需要用代码块将account锁起来即可
      @Override
      public void run() {
          //🔒的对象是修改的对象,需要增删改的对象
          synchronized (account) {
              //判断有没有钱
              if (account.money - withdrawalMoney < 0) {
                  System.out.println(Thread.currentThread().getName() + " ->钱不够,取不了");
                  return;
              }
              //放大问题的发生性
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //卡内余额 = 账号现存 - 取数金额
              account.money = account.money - withdrawalMoney;
              //手中现金 = 之前手中的钱 + 取出的钱
              ownMoney = ownMoney + withdrawalMoney;
      
              System.out.println(account.name + "--余额为 : " + account.money);
              //this.getName() 等价 Thread.currentThread().getName()
              System.out.println(this.getName() + "手里的钱:" + ownMoney);
          }
      }
      
    • ArrayList加锁

      • 用代码块加锁

        @Test
        public void  testSyncArraylist() throws InterruptedException {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{
                    synchronized (list){
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            Thread.sleep(3000);
            System.out.println(list.size());
        }
        

    死锁:

    • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.

      • 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.

      弹幕人才:

      面试官:“你给我讲下什么是死锁,我就给你通过面试”

      求职者:”你先给我通过面试,我才给你讲死锁“

    死锁避免方法

    • 产生死锁的四个必要条件:

      1. 互斥条件:一个资源每次只能被一个进程使用。

        • 各只有一份的口红和镜子
      2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

        • 都🔒住了自己现有的口红/镜子
      3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

        • 抢不到对方的镜子/口红
      4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

        • 镜子🔒与口红🔒相互调用

        上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

    Lock锁

    • 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
    • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
    • ReentrantLock 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
    //测试Lock锁:用卖票例子简单举例
    public class TestLock {
        public static void main(String[] args) {
            TestLock2 testLock2 = new TestLock2();
    
            new Thread(testLock2, "APPLE").start();
            new Thread(testLock2, "BANANA").start();
            new Thread(testLock2, "CAT").start();
        }
    }
    
    class TestLock2 implements Runnable {
    
        int ticketNum = 10;
    
        //定义LOCK锁
        private final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);//需要在🔒外面加sleep才能多线程执行,不然一直都在执行同一线程
                    lock.lock();
                    if (ticketNum > 0) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "-->" + ticketNum--);
                    } else {
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    //运行结果:
    BANANA-->10
    CAT-->9
    APPLE-->8
    BANANA-->7
    CAT-->6
    APPLE-->5
    BANANA-->4
    CAT-->3
    APPLE-->2
    BANANA-->1
    

    在锁外面加sleep才能体现多线程执行,不然总是同一线程在跑

    synchronized与Lock的对比

    • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放

    • Lock只有代码块锁,synchronized有代码块锁和方法锁

    • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

    • 优先使用顺序:

      • Lock > 同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

    线程协作:

    生产者消费者问题

    • 应用场景:生产者和消费者问题

      • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费.
      • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止.
      • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.
      Producer:生产者
      数据缓存区
      Consumer:消费者
    • 线程通信-分析
      这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之
      间相互依赖,互为条件.

      • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
      • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
      • 在生产者消费者问题中,仅有synchronized是不够的
        • synchronized可阻止并发更新同一个共享资源,实现了同步
        • synchronized不能用来实现不同线程之间的消息传递(通信)
    • 线程通信
      Java提供了几个方法解决线程之间的通信问题

      方法名作用
      wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
      wait(long timeout)指定等待的毫秒数
      notify()唤醒一个处于等待状态的线程
      notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

      ⚠⚠注意⚠⚠:

      均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常
      lllegalMonitorStateException

    解决方式1

    并发协作模型“生产者/消费者模式”—>管程法

    • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);

    • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);

    • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”

      生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

    //测试:生产者消费者模型 --> 利用缓冲区解决:管程法
    //生产者、消费者、产品、缓冲区
    public class TestPC_tube {
    
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
            new Producer(container).start();
            new Consumer(container).start();
        }
    }
    //生产者
    class Producer extends Thread{
        SynContainer container;
    
        public Producer(SynContainer container){
            this.container = container;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("生产了第"+i+"号鸡");
                container.push(new Chicken(i));
            }
        }
    }
    
    //消费者
    class Consumer extends Thread{
        SynContainer container;
    
        public Consumer(SynContainer container){
            this.container = container;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了--> "+container.pop().id + "号鸡");
            }
        }
    }
    
    //产品
    class Chicken{
        int id;//编号
    
        public Chicken(int i) {
            this.id = i;
        }
    }
    
    //缓冲区
    class SynContainer{
    
        //需要一个容器大小
        Chicken[] chickens = new Chicken[10];
        //容器计数器
        int count = 0;
    
        //生产者放入产品
        public synchronized void push(Chicken chicken){
            //如果容器满了,就需要等待消费者消费
            if (count ==  chickens.length){
                //通知消费者消费,生产者等待生产
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果没有满,我们就需要丢入产品
            chickens[count] = chicken;
            count++;
            //可以通知消费者消费了
            this.notifyAll();
        }
    
        //消费者消费产品
        public synchronized Chicken pop(){
            //判断能否消费
            if (count == 0){
                //等待生产者生产,消费者等待消费
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果可以消费
            count--;
            Chicken chicken = chickens[count];
    
            //吃完一个,通知生产者生产
            this.notifyAll();
            return chicken;
        }
    }
    

    解决方式2

    并发协作模型“生产者/消费者模式”—>信号灯法

    以演员录制电视节目(综艺)给观众观看为例子

    //信号灯法:通过标志位解决
    public class TestPC_Blinker {
        public static void main(String[] args) {
            Show show =  new Show();
    
            new Actor(show).start();
            new Audience(show).start();
        }
    }
    
    //生产者-->演员
    class Actor extends Thread{
        Show show;
        public Actor (Show show){
            this.show = show;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i%2 == 0){
                    this.show.record("这就是街舞");
                }else {
                    this.show.record("青春有你");
                }
            }
        }
    }
    //消费者-->观众
    class Audience extends Thread{
        Show show;
        public Audience (Show show){
            this.show = show;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                show.watch();
            }
        }
    }
    //产品 -->电视节目
    class Show extends Thread{
        //演员录制,观众等待
        //观众观看,演员等待
        String program;//录制的节目
        boolean flag  = true;
    
        //节目录制
        public synchronized void record(String program){
            if (!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员录制了:"+program);
            //通知观众观看
            this.notifyAll();
            this.program = program;
            this.flag = !this.flag;
        }
    
        //观众观看
        public synchronized void watch(){
            if (flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观众观看了-->"+program);
            //通知演员录制:
            this.notifyAll();
            this.flag = !this.flag;
        }
    }
    
    ...
    演员录制了:青春有你
    观众观看了-->青春有你
    演员录制了:这就是街舞
    观众观看了-->这就是街舞
    演员录制了:青春有你
    观众观看了-->青春有你
    演员录制了:这就是街舞
    观众观看了-->这就是街舞
    演员录制了:青春有你
    观众观看了-->青春有你
    演员录制了:这就是街舞
    观众观看了-->这就是街舞
    演员录制了:青春有你
    观众观看了-->青春有你
    演员录制了:这就是街舞
    观众观看了-->这就是街舞
    ...
    

    使用线程池

    • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
    • 好处:
      • 提高响应速度(减少了创建新线程的时间)
      • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
      • 便于线程管理(…)
        • corePoolSize:核心池的大小
        • maximumPoolSize:最大线程数
        • keepAliveTime:线程没有任务时最多保持多长时间后会终止
    • JDK5.0起提供了线程池相关APl:
      • ExecutorService
      • Executors
    • ExecutorService:
      • 真正的线程池接口。
        • 常见子类ThreadPoolExecutor
      • void execute(Runnable command):
        • 执行任务/命令,没有返回值,一般用来执行Runnable
      • Futuresubmit(Callabletask):
        • 执行任务,有返回值,一般又来执行Callable
      • void shutdown():
        • 关闭连接池
    • Executors:
      • 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    //测试线程池
    public class TestPool {
        public static void main(String[] args) {
            //1.创建服务,创建线程池
            //newFixedThreadPool 参数为线程池的大小
            ExecutorService service = Executors.newFixedThreadPool(10);
    
            //执行
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
    
            //2.关闭链接
        }
    }
    
    class MyThread implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    展开全文
  • 【问答题】与 功能类似的 JSTL 标签是()【简答题】安装Tomcat服务器所在的计算机需要事先安装JDK吗?【单选题】下列选项中,( )不是JDBC的工作任务。...【单选题】JaveBean中方法的访问属性必须是()。【简答题】JSP ...
  • 1.3 TCP上的客户-服务器下一个例子是TCP上的客户-服务器事务应用。图1-5给出了客户程序。图1-5 TCP事务的客户程序1. 创建TCP插口和连接到服务器10-17 调用socket函数创建一个TCP插口,然后在Internet插口地址结构中...
  • 时间做调查问卷,客户创建自定义问卷内容,包括题目和选项内容;之后需要导出问卷明细,,,,麻烦来咯于是到网上到处搜索,没有直接结果;于是又找各种相似的,,终于功夫不负有心人然后最终自己写出来了,decode才是核心...
  • 理论上 CZone 会被 RZone 以比访问 GZone 高很的频率进行访问。 CZone 是基于特定的 GZone 场景进行优化的一种单元,它把 GZone 中有些有着”写读时间差现象”的数据和服务进行了的单独部署,这样 RZone 只需要...
  • private,public,protected,默认不写(firendly)1、Class类的访问权限:public:可以供所有的类访问。默认:默认可以称为friendly但是,java语言中是没有friendly这个修饰符的,这样称呼应该是来源于c++。默认的访问...
  • 一、生产运行环境:操作系统:windows server 2008 R2系统WEB应用服务器:IIS 7.0数据库系统:SQL Server 2005二、运行问题2015年5月22日上午11点左右收到信息,不能访问内网,下午5点20左右发现所有网站无法访问;...
  • 狗屎一样的代码如何重构?重构不止是代码整理,它提供了一种高效且受控的代码整理技术。(一)重构原则1、何谓重构对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本...
  • ) 打断:其他线程同一时刻访问代码块。 有序性 指的是程序执行的时候按照代码编写的先后顺序。 有序性的保证 volatile Lock或者synchronized关键字 指令重排 as-if-searies 语义可以保证,不管是否发生指令重排,...
  • 一、什么是重构所谓重构(Refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入...
  • Socket编程一、网络基础知识两台计算机要通过网络进行通信,必须具备:a、唯一的标识(IP地址);b、需要共同的语言(协议);...是以TCP和IP为基础的不同层次上个协议的集合。也称为:TCP/IP协议...
  • JS弹出窗口代码大全(详细整理)如何利用网页弹出各种形式的窗口,我想大家大多都是知道些的,但那种多种多样的弹出式窗口是怎么搞出来的,我们今天就来学习一下: showModalDialog()或是showModelessDialog() 来调用...
  • 他们努力使自己的业务合理化,以便能够对不断变化的客户需求做出快速反应,迅速适应新的市场条件,并通过更迅速的反应和灵活的方式在竞争中取胜。 ​ 在快速加入不成功就失败的数字转型运动的过程中,大多数企业...
  • 代码的每次修改都影响都其他部分的修改,随着混乱的增加,团队生产力持续下降,管理层想提升生产力,因此增加人力,但是新人并不熟悉系统的设计,搞不懂什么样的修改符合设计意图,什么样的修改违背设计意图,因此...
  • 事实上,网站访问的速度不仅仅由虚拟空间决定,因为网站访问是一个互动的过程,所以访问网站的速度主要分为两个方面:虚拟空间和访问者:第一、虚拟空间影响网站访问速度的因素网站程序所在服务器的性能是好是坏。...
  • chmod:命令用于改变linux系统文件或目录的访问权限 find命令: netstat cat 文件名 | grep "关键字" Linux与Linux传输文件用什么协议 rz命令: scp命令: 35:准备一个跟项目相关的表联查的SQL语句 面试前需要...
  • 简单了解下在操作系统中进程和线程的区别:进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。...进程是指操作系统能同时运行个任务(...
  • 程序员必学的代码重构(理论篇)

    热门讨论 2021-03-20 21:02:35
    本文是笔者毕业后的第一篇blog,将从三个方面讨论代码重构。即:1.代码重构是什么;2.常用的重构手法;3.代码中的“坏味道”。本篇blog是《重构,改善代码既有代码的设计》一文的读书笔记,读书笔记与书一起食用效果...
  • 前端代码在服务器上如何配置 内容精选换一换使用Storm客户端提交了业务之后,如何使用IDEA远程调试业务?以调试WordCount程序为例,演示如何进行IDEA的远程调试:调试Storm程序需要先修改指定的服务端参数,并在重启...
  • 线程和进程

    2021-05-23 09:33:30
    很想写点关于进程和线程的东西,我确实很爱他们。但是每每想动手写点关于他们的东西,却总是求全心理作祟,始终动不了手。...我想,只要你不是整天都写那种int main()到底的代码的人,那么或...
  • 功能是通过网络让不同的机器、不同的操作系统能够彼此分享个别的数据,让应用程序在客户端通过网络访问位于服务器磁盘中的数据,是在类Unix系统间实现磁盘文件共享的一种方法。 NFS在文件传送或信息传送过程中依赖...
  • 上一篇介绍了core-site.xml的配置,本篇继续介绍hdfs-site.xml的配置。... 处理所有客户端请求的RPC地址,若在HA场景中,可能有个namenode,就把名称ID添加到进来。该属性的格式为nn-host1:rpc-port。 d
  • 展开全部反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态e5a48de588b662616964757a686964616f31333335326232或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于...
  • 线程的知识点算是比较难啃的骨头,仅凭一篇文章很难把知识点系统的梳理完,所以线程这个章节我分了理论篇和实操篇来解说梳理。该篇为理论篇,认识线程的起点,后续我再抽时间继续跟进! 一、什么是java线程...
  • 这篇文章主要介绍了PHP实现限制IP访问及提交次数的方法,涉及php针对客户端来访IP的获取、判断以及结合session记录IP访问次数等相关操作技巧,需要的朋友可以参考下本文实例讲述了PHP实现限制IP访问及提交次数的方法。...
  • 目录一、线程理论1.1、操作系统的发展1.1.1、批处理操作系统1.1.2、如何提高CPU利用率1.1.3、进程来了1.2、并发和并行1.2.1、并发1.2.2、并行1.3、线程1.3.1、线程出现的原因1.3.2、线程1.3.3、线程工作的原理1.4...
  • 简单介绍:nginx 采用的是进程(单线程) + io路复用(epoll)模型 实现高并发 二、nginx 进程 启动nginx 解析初始化配置文件后会 创建(fork)一个master进程 之后 这个进程会退出 master 进程会 变为...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 69,623
精华内容 27,849
关键字:

多客户访问同一代码段