首页 > 编程语言 >Java多线程基础

Java多线程基础

时间:2023-04-20 13:44:49浏览次数:47  
标签:Java Thread void 基础 class 线程 run 多线程 public

1、创建线程类的方法

1.1、继承Thread类

  • 当一个类继承了Thread类,该类就可以当做线程使用
  • 我们会重写run方法,写上自己的业务代码
  • run方法 是 Thread类实现了 Runnable接口的run方法

1.1.1、入门案例

/**
 * @author Carl
 * @version 1.0
 */
public class Thread01{
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}
class Cat extends Thread{
    int timeCount = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("我是一只小猫咪" + (++ timeCount));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (timeCount == 8){
                break;
            }
        }
    }
}

1.1.2、入门案例线程示意图

image.png

1.1.3、为什么调用的是start方法

  • run方法就是一个普通的方法,没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
  • start()方法调用start0() 方法后,该线程并不会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度。
  • start0()方法是本地方法,是JVM调用,底层是C/C++
  • 真正实现多线程的效果的是start0()方法,而不是run

1.2、实现Runnable接口

1.2.1、入门代码

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}
class Dog implements Runnable{

    int count = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("小狗汪汪叫" + (++ count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 8){
                break;
            }
        }
    }
}

1.2.2、底层原理

  • 底层原理使用的是静态代理模式
public class Thread02 {
    public static void main(String[] args) {

        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}
class Animal{}
class Tiger extends Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}
class ThreadProxy implements Runnable{

    private Runnable runnable = null;
    @Override
    public void run() {
        if (runnable != null){
            runnable.run();
        }
    }
    public ThreadProxy(Runnable runnable) {
        this.runnable = runnable;
    }
    public void start(){
        start0();
    }

    private void start0() {
        run();
    }
}

1.2.3、练习案例

  • 编写一个程序,创建两个线程,一个线程每隔一秒输出“hello world”,输出10次,一个线程每隔1秒输出“hi”,输出5次退出
package com.haiyang.threaduse;

/**
 * @author Carl
 * @version 1.0
 */
public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);

        thread1.start();
        thread2.start();
    }
}
class T1 implements Runnable{

    int count = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("hi" + Thread.currentThread().getName() + "-" + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            }
        }

    }
}
class T2 implements Runnable{

    int count = 0;
    @Override
    public void run() {
        while (true){
            System.out.println("hello world" + Thread.currentThread().getName() + "-" + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 15){
                break;
            }
        }

    }
}


1.3、继承Thread和实现Runnable接口的区别

  • 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上是没有区别的,从jdk帮助文档我们可以看到Thread类就实现了Runnable,底层还是去调用了start()方法,start()方法又去调用的start0()方法
  • 实现Runnable接口方式更加适应多个线程共享一个资源的情况,并且避免了单继承的局限性,建议使用Runnable

2、线程的终止

  • 当线程完成任务以后,会自动关闭
  • 还可以通过使用遍历来控制run方法的退出的方式停止线程,即通知方式

2.1、案例

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
        Thread.sleep(5000);
        t.setFlag(false);

    }
}
class T extends Thread{
    private int countNum = 0;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(100);
                System.out.println("hello world!" + countNum);
                countNum ++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (countNum == 80){
                break;
            }
        }
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

3、线程常用的方法

3.1、第一组

  • setName : 设置线程的名称,使之与参数的name相同
  • getName : 返回线程的名称
  • start : 使用该线程开始执行;Java虚拟机底层调用线程 start0方法
  • run : 调用线程对象run方法
  • setPriority :更改线程的优先级
  • getPriority : 获取线程的优先级
  • sleep :在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  • intterupt : 中断线程,如果现在正在休眠,则会中断它的休眠。

3.2、第二组

  • yield : 线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  • join : 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务。
package com.haiyang.method;

/**
 * @author 杨磊
 * @version 1.0.0
 * @ClassName ThreadMethod01.java
 * @Description 
 * 案例 :
 *  main线程创建一个子线程,每隔1秒输出hello 输出20次 主线程每隔1秒,输出hi 输出20次 要求两个线程同时执行,当主线程输出5次后,让子线程运行完毕,主线程在继续
 * @createTime 2022年01月05日 20:43:00
 */
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.start();
        for (int i = 1; i <= 20; i++) {
            Thread.sleep(1000);
            System.out.println("主线程吃了" + i + "个包子");
            if (i == 5){
                System.out.println("主线程让子线程先吃完包子");
                t1.join(); // 这里相当于t1线程先执行完毕
                System.out.println("子线程吃完了,主线程开始吃..");
            }
        }
    }
}
class T1 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程吃了" + i + "个包子");
        }
    }
}

3.3、用户线程和守护线程

  • 用户线程 : 也叫工作线程,当线程的任务执行完或通知方式结束
  • 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
package com.haiyang.method;

/**
 * @author Carl
 * @version 1.0.
 */
public class ThreadMethod03 {

    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        // 设置子线程为守护线程
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程 haha。。。");
            Thread.sleep(1000);
        }
    }
}
class MyDaemonThread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hehe......");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4、线程的生命周期

  • 线程状态。线程可以处于以下状态之一:
    • NEW** : 尚未启动的线程处于此状态。**
    • RUNNABLE** : 在Java虚拟机中执行的线程处于此状态。**
      • 可分为 就绪状态和运行状态
    • BLOCKED** : 被阻塞等待监视器锁定的线程处于此状态。**
    • WAITING** : 正在等待另一个线程执行特定动作的线程处于此状态。**
    • TIMED_WAITING** : 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。**
    • TERMINATED** : 已退出的线程处于此状态。**

image.png

5、线程的同步

5.1、线程的同步机制

  • 在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。

5.2、同步具体方法-Synchronized

5.2.1、同步代码块

synchronized(对象) { // 得到对象的锁,才能操作同步代码
    // 需要被同步代码
}

5.2.2、同步方法

  • synchronized 还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String name){
    // 需要被同步的代码
}

5.3、卖票案例

/**
 * @author Carl
 * @version 1.0
 */
public class SellTicket {
    public static void main(String[] args) {
        SellTicket03 sellTicket01 = new SellTicket03();


        new Thread(sellTicket01).start();
        new Thread(sellTicket01).start();
        new Thread(sellTicket01).start();


    }
}

// 实现接口的方式,使用 synchronized 实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean flag = true;

    public synchronized void sell() {
        if (ticketNum <= 0) {
            System.out.println("售票结束!");
            flag = false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口" + Thread.currentThread().getName() + " 卖出一张票,剩余票数:" + (--ticketNum));
    }

    @Override
    public void run() { // 同步方法,在同一时刻只能有一个线程执行run方法
        while (flag) {
            sell();
        }
    }
}

5.4、同步原理

5.5、互斥锁

  • Java语言中,引入了对象互斥的概念,来保证共享数据操作的完整性
  • 每一个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象。
  • 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问。
  • 同步的局限性:导致程序执行效率降低
  • 同步方法(非静态方法)的锁可以是this,也可以是其他对象(要求是同一对象)
  • 同步方法(静态方法)的锁为当前类本身

5.5.1、注意事项

  • 同步方法如果没有使用static修饰:默认对对象为this
  • 如果方法使用static修饰,默认锁对象为 当前类.class
  • 实现的落地步骤
    • 需要先分析上锁的代码
    • 选择同步代码块或同步方法
    • 要求多个线程的锁对象为同一个即可

6、线程的死锁

6.1、基本介绍

  • 多个线程都占用对方的锁资源,但都不肯想让,导致了死锁,在编程是一定要避免死锁的发生。

7、释放锁

7.1、释放锁的情况

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return.
  • 当前线程在同步代码块、同步方法中出现了为处理的Error或者Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

7.2、不会释放锁

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用该线程的suspend()方法将线程挂起,该线程不会释放锁。

标签:Java,Thread,void,基础,class,线程,run,多线程,public
From: https://www.cnblogs.com/kxqblogs/p/17336494.html

相关文章

  • 搜索引擎基础语法
     搜索语法大全1.intitle搜索范围限定在网页标题上面网页标题通常是对网页内容提纲挈领式的归纳。把查询内容范围限定在网页标题中,有时能获得意想不到的结果语法结构:内容+空格intitle:你要查找的信息(此信息会被限定在网页标题内)例如:web学习intitle:安全注意:intitle:和后......
  • java线程同步和异步的区别
    在Java中,同步和异步是指多线程并发执行中的两种不同的机制。同步指的是线程之间的协作方式,即当一个线程在执行一个任务时,其他线程必须等待该线程执行完该任务后才能执行同一段代码。同步机制通常使用synchronized关键字或Lock对象进行实现,以保证多线程共享的资源同步访问。异步指的......
  • java - 缩放新的图片
    /***缩放新的图片*/publicstaticStringscaleImg(StringwaterLogoPath,StringwaterLogoPathTmp,Stringsuffix,ImgWHwh){try{suffix=suffix.replace(".","");Filesrcfile=newFile(water......
  • java Map 怎么遍历
    评://最常规的一种遍历方法,最常规就是最常用的,虽然不复杂,但很重要,这是我们最熟悉的,就不多说了!!publicstaticvoidwork(Map<String,Student>map){Collection<Student>c=map.values();Iteratorit=c.iterator();for(;it.hasNext();){System.out.pri......
  • JAVA: 如何显示比当前DATE时间,提前一个月的时间 还有提前20天
    评:Datedate=newDate();//当前日期SimpleDate()Formatsdf=newSimpleDateFormat("yyyy/MM/dd");//格式化对象Calendarcalendar=Calendar.getInstance();//日历对象calendar.setTime(date);//设置当前日期calendar.add(Calendar.MONTH,-1);//月份减一Sy......
  • 解决Eclipse建立Maven项目后无法建立src/main/java资源文件夹的办法
    评:建立好一个Maven项目后,如果JavaResources资源文件下没有src/main/java文件夹,并且在手动创建这个文件时提示“已存在文件”。这说明,在这个项目配置中已经有了src/main/java这个文件夹,至于为什么不显示,我暂时也还不清楚,希望谁明白了跟我下,谢了。(已解决)[b]第二种方法:这是最......
  • java.lang.IllegalArgumentException: Illegal group reference
    评:在使用String的replaceFirst(regex,replacement)的时候出现java.lang.IllegalArgumentException:Illegalgroup原因是第一个参数支持正则表达式,replacement中出现“$”,会按照$1$2的分组模式进行匹配,当编译器发现“$”后跟的不是整数的时候,就会抛出“非法的组引用”的异常......
  • 12-HTML基础回顾
    title:12-HTML基础回顾本文主要内容html的常见元素html元素的分类html元素的嵌套关系html元素的默认样式和CSSResethtml常见面试题html的常见元素html的常见元素主要分为两类:head区域的元素、body区域的元素。下面来分别介绍。1、head区域的......
  • 2、C基础
    CompilerExplorerCpp_Primer_Practice1、一些指令2、类型系统"内存中一定大小的操作单元"抽象为"类型系统"1、基础数据类型:byte、short、int,内存单元的2^n2、其他类型:结构体,表示不规则内存单元3、数组:相同类型的多个内存单元3、函数"指令段"抽象为"函数"......
  • JavaScript 九九乘法表
    方法一:观察规律:第一个数每行都是自增1。我们发下第二个数都是从1开始,依次递增1,永远不大于前面的数。前面数字每自增一次,后面数字自增一轮。我们可以用双重for循环,外层初始值设为i,i从1开始,到9结束,自增1内层从初始值设为j,j从1开始,小于等于外层的i,自增1九九乘法表代码如下:for......