多线程

话不多说,看代码

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. mysql 字段逗号分割行转列操作

    一.需求 某字段的值为 7654,7698,7782,7788 期望的效果:  二.实现语句 SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('7654,7698,778 ...

  2. lighthouse性能优化分析工具使用

  3. map三层循环遍历,操作数据

    let tempArr = this.oldCityList.map(item => { return { value: item.code, text: item.name, type: it ...

  4. vscode prettier保存代码时自动格式化

    https://blog.csdn.net/qq_37815596/article/details/109225879

  5. C#使用ParseExact方法将字符串转化为日期格式

    private void btn_Convert_Click(object sender, EventArgs e) { #region 针对Windows 7系统 string s = string ...

  6. offline RL | ABM:从 offline dataset 的好 transition 提取 prior policy

    ICLR 2020,6 6 6. 材料: 论文题目:Keep Doing What Worked: Behavior Modelling Priors for Offline Reinforcemen ...

  7. SV Interface and Program 2

    Clocking:激励的时序 memory检测start信号,当start上升沿的时候,如果write信号拉高之后,将data存储到mem中 start\write\addr\data - 四个信号是 ...

  8. Spring————IOC入门学习

    Spring----入门学习 简介 优点 Spring是一个开源的免费的框架(容器)! Spring是一个轻量级,非入侵式的框架 控制反转(IOC),面向切面编程(AOP) 支持对事务的处理,对框架整 ...

  9. 银河麒麟在线升级新版本docker

    银河麒麟在线升级新版本docker 卸载 学习来自: https://cloud.tencent.com/developer/article/1491742 yum remove docker \ d ...

  10. [转帖]TiKV 多副本丢失以及修复实践

    https://tidb.net/blog/ad45bad9#6%E6%80%BB%E7%BB%93 1实验目的 随着tidb使用场景的越来越多,接入的业务越来越重要,不由得想试验下tidb组件的高可 ...