多线程

话不多说,看代码

1.什么是多线程

众所周知CPU单线程的东西,也就是说在同一时间内程序只能去做一件事情,但很多时候比如说多人买票、龟兔赛跑、游戏开发等都需要在同一时间内完成多个东西,因此就有了多线程的概念。

2.多线程的作用

在有了多线程之后,我们可以让程序在同一时间内做多个事情,比如程序一边读取一边处理输出,程序一边渲染动画一边处理数据等

3.多线程的本质

理论上说,多线程是并发的,需要多核同时工作才行,但早期的电脑是单核的,为了使电脑能处理多样事情,CPU会在第一个线程和第二个线程种反复的执行,由于CPU速度很快因此我们是看不到程序有卡顿的现象

4.代码的实现

在Java种有三种方式实现多线程,这里演示其中的两种

1.创建一个类 继承Thread对象 并且重写run方法

package com.SatrThead.Test02;

public class Demo01 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我只子线程");
}
} public static void main(String[] args) { Demo01 demo01 = new Demo01(); demo01.start(); //必须是start方法
for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程");
} }
}

2.继承Runnable接口 代理线程

package com.SatrThead.Test02;

public class Demo02 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我只子线程");
}
} public static void main(String[] args) { Demo02 demo02 = new Demo02(); //创建Runnable代理
new Thread(demo02).start(); //执行线程 for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程");
} }
}

在使用多线程之前,我们要明白main方法是程序种的主线程,对此我们可以用以下代码去理解多线程

package com.SatrThead.Test01;

public class Demo02 implements Runnable{

    private int ticket = 10;//初始化票数

    @Override
public void run() {
while(ticket > 0){
//输出判断哪个线程拿到了第几张票 并且让票数递减
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
new Thread(new Demo02(),"小明").start();
new Thread(new Demo02(),"小李").start();
new Thread(new Demo02(),"小张").start();
new Thread(new Demo02(),"老师").start();
}
}

由此可得,线程创建出来过后并不是马上就执行的,具体的执行时间是要根据CPU的调度

如果你细心肯定会发现,程序并不完善,有时候同一张票会被多个人拿到,这是我们不想要的结果 后续会解决

5.静态代理模式

面对初学者,可能会对Runnable的实现方式不太理解,实际上这是代理模式的设计,对此我们可以随意的去创建一个接口通过创建的接口去学习代理模式

interface Marry{
void happyMarry();
}

简单明了的一个接口,里面只有一个抽象方法就是庆祝结婚

在创建两个类去继承接口

class People implements Marry{

    @Override
public void happyMarry() { } } class Company implements Marry{
@Override
public void happyMarry() { }
}

在这两个方法种,重写一下庆祝婚礼方法,在People中可以随便写,但在代理类Company里面就不能随意写了

class Company implements Marry{

    private People target;

    public Company(People p){
this.target = p;
} public Company(){} private void before(){
System.out.println("结婚开始前 婚庆公司在准备中ing....");
} private void after(){
System.out.println("结婚结束后 婚前公司撤离 糟糕忘记收钱了!!!");
} @Override
public void happyMarry() {
before();
target.happyMarry();
after();
}
}

在编写Company的时候,为了能让Company代理People去Marry但不是”取代“我去Marry因此里面有些东西是Company类去做,但核心还是People去做在,因此我们在主方法可以这么写

package com.SatrThead.Test02;

public class Demo03 {

    public static void main(String[] args) {
People people = new People();
new Company(people).happyMarry();
} } interface Marry{ void happyMarry(); } class People implements Marry{ @Override
public void happyMarry() {
System.out.println("结婚?结婚是不可能的,这辈子都不可能结婚的");
} } class Company implements Marry{ private People target; public Company(People p){
this.target = p;
} public Company(){} private void before(){
System.out.println("结婚开始前 婚庆公司在准备中ing....");
} private void after(){
System.out.println("结婚结束后 婚前公司撤离 糟糕忘记收钱了!!!");
} @Override
public void happyMarry() {
before();
target.happyMarry();
after();
}
}

这里是不是和我们的Runnable实现方法一模一样?其实它也是这么封装的

6.Lamda表达式

首先我编写了一个接口,接口中只有一个抽象方法,那么我们可以实例化接口并且编写它的抽象方法

package com.SatrThead.Test02;

public class Demo04 {

    public static void main(String[] main){

        LambdaTest lambdaTest = new LambdaTest() {
@Override
public void printHello() {
System.out.println("Hello~");
}
}; } } interface LambdaTest{
void printHello();
}

从JDK1.8开始Java可以使用Lamda表达式,也就是所谓的箭头函数来实现仅有一个抽象方法的接口实例化

package com.SatrThead.Test02;

public class Demo04 {

    public static void main(String[] main){

        LambdaTest lambdaTest = new LambdaTest() {
@Override
public void printHello() {
System.out.println("Hello~");
}
}; LambdaTest lambdaTest2 = ()->{
System.out.println("hello~~");
}; } } interface LambdaTest{
void printHello();
}

这是对抽象方法编写的一种简化,当然对于以上代码完全可以省区花括号(仅限抽象方法只有一条语句)

package com.SatrThead.Test02;

public class Demo04 {

    public static void main(String[] main){

        LambdaTest lambdaTest = new LambdaTest() {
@Override
public void printHello() {
System.out.println("Hello~");
}
}; LambdaTest lambdaTest2 = ()->System.out.println("hello~~"); } } interface LambdaTest{
void printHello();
}

如果要传递参数呢?? 很显然我们可以在括号内传递参数

package com.SatrThead.Test02;

public class Demo04 {

    public static void main(String[] main){

        LambdaTest lambdaTest2 = (String name)->System.out.println(name + " hello~~");
lambdaTest2.printHello("StarVk"); } } interface LambdaTest{
void printHello(String name);
}

当然对于只有一个参数的我们还可以更简便 我们甚至可以把参数省略掉

package com.SatrThead.Test02;

public class Demo04 {

    public static void main(String[] main){

        LambdaTest lambdaTest2 = name->System.out.println(name + " hello~~");
lambdaTest2.printHello("StarVk"); } } interface LambdaTest{
void printHello(String name);
}

如果有多个参数 那么只能老老实实的写括号了

package com.SatrThead.Test02;

public class Demo04 {

    public static void main(String[] main){

        LambdaTest lambdaTest2 = (name,name2)->System.out.println(name + " kill " + name2);
lambdaTest2.kill("StarVk","Mike"); } } interface LambdaTest{
void kill(String name,String name2);
}

7.线程停止

关于线程停止,我认为Java给我们提供了方法,但又没有完全提供

通常来说,Java不建议我们使用官方的方法来手动结束线程,通常是以一个While循环来终止线程,实际上线程并没有停止,只是通过flag把它变为了空的线程

package com.SatrThead.Test03;

public class Demo01 implements Runnable{

    private boolean flag = true;

    @Override
public void run() { while(flag){
System.out.println("我在这,我在这");
}
} public void stop(){
flag = false;
} public static void main(String[] args) { Demo01 demo01 = new Demo01();
new Thread(demo01).start();//启动线程 for (int i = 0; i < 100; i++) {
System.out.println("我是cpu,让他停止好嘛"+i);
System.out.println("我是主线程 我现在不想停止demo01线程");
System.out.println("那我过0.01秒再问问");
}
System.out.println("我是cpu,让他停止好嘛");
System.out.println("那就让他停止把");
demo01.stop(); //停止线程 } }

8.线程休眠

在以往例子中,我有写过线程休眠的例子,通过Thread的sleep方法去让线程在置顶毫秒内休眠,之后会重新准备被CPU调度

注:不能在循环中对线程休眠,否则休眠结束后CPU调度的还是原来的线程,sleep()抱锁休眠

package com.SatrThead.Test01;
public class Demo02 implements Runnable{ private int ticket = 10; @Override
public void run() {
while(ticket > 0){
buy();
}
} public void buy(){
if(ticket < 0)
return;
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) { Demo02 demo02 = new Demo02();
new Thread(demo02,"小李").start();
new Thread(demo02,"小明").start();
new Thread(demo02,"小张").start();
new Thread(demo02,"老师").start();
}
}

9.线程礼让

当有两个线程A和线程B,CPU先调度线程A,但线程A很想让线程B调度,因此线程A执行yield()方法重新回到开始等待被CPU调度,此时,CPU会重新在线程A和线程B之中选择一个去调度,也就是说要么礼让成功要么礼让不成功

对此可以创建线程A和线程B,先启动A线程在启动B线程,A作为礼让线程

package com.SatrThead.Test03;

public class Demo03 {
public static void main(String[] args) {
myThreadA myThreadA = new myThreadA();
myThreadB myThreadB = new myThreadB(); myThreadA.start();
myThreadB.start();
}
} class myThreadA extends Thread{
@Override
public void run() {
System.out.println("我是线程A 我想礼让线程B");
Thread.yield();
System.out.println("我要结束了不知道礼让成功没 ai");
}
} class myThreadB extends Thread{
@Override
public void run() {
System.out.println("我是线程B 我想要先运行 我不管!");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("哼!");
}
}

10.强制执行线程

有时候CPU会和人一样转不过来,把最不重要线程先执行了,此时我们可以通过join方法去插队

注:join方法插队后会一直执行此线程知道线程结束

package com.SatrThead.Test03;

public class Demo02 implements Runnable {
@Override
public void run() {
for (int i = 0;i<1000;i++){
System.out.println("至尊VIP驾到 让开"+i);
}
} public static void main(String[] args) throws InterruptedException {
Demo02 demo02 = new Demo02();
Thread thread = new Thread(demo02); thread.start();
for (int i = 0; i < 100; i++) {
if(i==50){
thread.join(); //当循环到第50次的时候插队
}
System.out.println("我是主线程 我最大"+i);
}
}
}

11.线程状态

线程有五大状态

* 刚创建好没启动的的**初始状态**
* 启动了等待CPU调用的**等待状态**
* 被CPU调用的**运行状态**
* 遇到延时等方法的**堵塞状态**
* 执行自闭后的**死亡状态**

在Thread类中有一个类是State类,我们可以获取这个类取查看线程的执行状态

package com.SatrThead.Test03;

public class Demo04 implements Runnable{

    @Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("我还在跑"+i);
if(i == 3){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
} public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Demo04());
Thread.State state = thread.getState(); System.out.println(state); thread.start();
state = thread.getState();
System.out.println(state); while(state != Thread.State.TERMINATED){
System.out.println(state);
state = thread.getState();
Thread.sleep(100); //主线程跑太快了不方便观察 加个延迟
} }
}

12.线程的优先级

在Java中我们可以提高线程的优先级从而让某个线程更有可能的被CPU调度

注意:要先设置优先级在启动线程

package com.SatrThead.Test03;

public class Demo05 implements Runnable{

    @Override
public void run() {
System.out.println(Thread.currentThread().getName()+"在运行:"+Thread.currentThread().getPriority());
} public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()); Demo05 d1 = new Demo05();
Demo05 d2 = new Demo05();
Demo05 d3 = new Demo05();
Demo05 d4 = new Demo05();
Demo05 d5 = new Demo05(); Thread t1 = new Thread(d1,"张三");
Thread t2 = new Thread(d2,"李四");
Thread t3 = new Thread(d3,"王五");
Thread t4 = new Thread(d4,"小明");
Thread t5 = new Thread(d5,"小刚"); //先设置优先级后启动
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(3);
t3.setPriority(9);
t4.setPriority(6);
t5.setPriority(3); t4.start();
t1.start();
t5.start();
t3.start();
t2.start(); }
}

13.守护线程

Java线程中分为守护线程和用户线程,JVM保证在结束前执行完所有的用户线程,但不会管守护线程

可以通过setDaemon()方法设置线程为守护线程,即JVM讲不会取管这个线程是否会执行完毕

package com.SatrThead.Test03;

public class Demo06 {

    public static void main(String[] args) {
God god = new God();
People people = new People(); Thread threadPeople = new Thread(people);
Thread threadGod = new Thread(god);
threadGod.setDaemon(true); threadGod.start();
threadPeople.start();
} } class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我会一直守护着你");
}
}
} class People implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("我已经活了"+i+"天");
}
System.out.println("Goodbye~");
}
}

14.线程同步机制

多线程在很大程度上,给程序提供很强大的功能,但多线程也是不安全的,如果多个线程同时操作同一个资源那么就会出现资源抢夺的问题,这个是危险的,就好比张三有100w,他取银行取钱,于此同时张三女朋友在家通过手机银行也在张三账户上取钱,张三与他的女友在同一时间取了60w如果不对两个取钱的线程就行同步就会引发多取出的问题

在了解线程同步之前,得先有锁和队列得概念

每个对象都有一把锁,他藏在对象头之中

Java可以通过synchronized关键词修饰方法来获取对象得锁,通过synchronized修饰得方法获取得锁是this的

当然也可以通过

synchronized(obj){

}

来获取obj对象的锁,前提是要弄清楚锁哪,一般都是锁线程之间的公共资源,即会被操作的代码段

当一个线程获取到锁之后,其他线程执行同代码段的时候就会就行排队,等待当前线程释放了锁之后才能轮到下一个线程获取锁(列队)

在第8小点中买票的例子,会发现一张票可能会被多个人抢到,导致线程不安全,原因就是线程A和线程B同时看到第七张票并且都拿到了第七张票,对此通过加锁的方式可以这样解决

package com.SatrThead.Test01;
public class Demo02 implements Runnable{ private int ticket = 10; @Override
public void run() {
while(ticket > 0){
buy();
}
} public synchronized void buy(){
if(ticket < 0)
return;
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) { Demo02 demo02 = new Demo02();
new Thread(demo02,"小李").start();
new Thread(demo02,"小明").start();
new Thread(demo02,"小张").start();
new Thread(demo02,"老师").start();
}
}

在四个线程同时想要使用buy方法进行买票时,通过锁机制,让他们进行排队买票

零基础入门学习Java之多线程的更多相关文章

  1. 《零基础入门学习Python》【第一版】视频课后答案第001讲

    测试题答案: 0. Python 是什么类型的语言? Python是脚本语言 脚本语言(Scripting language)是电脑编程语言,因此也能让开发者藉以编写出让电脑听命行事的程序.以简单的方 ...

  2. 零基础入门学习Python(1)--我和Python的第一次亲密接触

    前言 最近在学习Python编程语言,于是乎就在网上找资源.其中小甲鱼<零基础入门学习Python>试听了几节课,感觉还挺不错,里面的视频都是免费下载,小甲鱼讲话也挺幽默风趣的,所以呢,就 ...

  3. 093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 03 static关键字(下)

    093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  4. 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 02 封装的代码实现

    088 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 02 封装的代码实现 本文知识点:Java封装的代码实现 说明:因为时间紧张,本人写博客过程中只 ...

  5. 080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则

    080 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 05 单一职责原则 本文知识点:单一职责原则 说明:因为时间紧张,本人写博客过程中只是 ...

  6. 057 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 04 案例:求整型数组的数组元素的元素值累加和

    057 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 04 案例:求整型数组的数组元素的元素值累加和 本文知识点:求整型数组的数组元素的元素值累加和 案例:求整型数 ...

  7. 056 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 03 一维数组的应用

    056 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 03 一维数组的应用 本文知识点:数组的实际应用 程序开发中如何应用数组? 程序代码及其运行结果: 不同数据类 ...

  8. 055 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 02 数组的概念

    055 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 02 数组的概念 本文知识点:数组的概念 数组的声明创建.初始化 在学习数组的声明创建.初始化前,我们可以和之 ...

  9. 054 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 01 数组概述

    054 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 01 数组概述 本文知识点:数组概述 为什么要学习数组? 实际问题: 比如我们要对学生的成绩进行排序,一个班级 ...

  10. 051 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 13 Eclipse下程序调试——debug入门1

    051 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 13 Eclipse下程序调试--debug入门1 本文知识点: 程序调试--debug入门1 程序 ...

随机推荐

  1. nginx导致获取客户端访问ip都是nginx服务器的地址问题解决

    java 获取用户ip的方法 /** * 获得客户端 ip * @param request * @return */ public String getRemortIP(HttpServletReq ...

  2. S3C2440移植linux3.4.2内核之内核框架介绍及简单修改

    目录 uboot启动内核分析 简单配置内核 编译内核 设置机器ID 修改晶振 uboot启动内核分析   进入cmd_bootm.c,找到对应的bootm命令对应的do_bootm(): int do ...

  3. 如何使用 Helm 在 K8s 上集成 Prometheus 和 Grafana|Part 3

    在本教程的前两部分,我们分别了解和学习了Prometheus 和 Grafana 的基本概念和使用的前提条件,以及使用 Helm 在 Kubernetes 上安装 Prometheus. 在今天的教程 ...

  4. 基于html5开发的Win12网页版,抢先体验

    据 MSPoweruser 报道,Windows 11虽然刚刚开始步入正轨,但最新爆料称微软已经在开启下一个计划,Windows 12 的开发将在 去年3 月份开始.德国科技网站 Deskmodder ...

  5. 函数传参中,形参类型为何使用const char*,而不是用char*

    1.当传递常量字符串给 char* 类型的形参时,C++ 编译器可能会发出警告,因为 char* 可以用于修改字符串内容.而使用 const char* 类型,则指示调用者不应该修改传入的字符串内容, ...

  6. jenkins构建报错: Send build artifacts over SSH' changed build result to UNSTABLE

    原因包括: ssh配置的用户没有相关的权限. 最好是配置root用户

  7. [转帖]如何在一个Docker中同时运行多个程序进程?

    https://cloud.tencent.com/developer/article/1683445 我们都知道Docker容器的哲学是一个Docker容器只运行一个进程,但是有时候我们就是需要在一 ...

  8. [转帖]tidb-lightning 逻辑模式导入

    https://docs.pingcap.com/zh/tidb/stable/tidb-lightning-configuration 本文档介绍如何编写逻辑导入模式的配置文件,如何进行性能调优等内 ...

  9. [转帖]Rust在windows下安装以后cargo build Error: linker `link.exe` not found

    D:\rust\runoob-greeting\greeting>cargo build error: linker `link.exe` not found | = note: 系统找不到指定 ...

  10. [转帖]alertmanager的使用

    https://www.jianshu.com/p/654d59325550 一.Alertanager的安装 1.下载   下载altermanager 2.安装 # 不同的平台下载不同的安装包 w ...