首页 > 其他分享 >多线程基础

多线程基础

时间:2023-07-19 20:33:21浏览次数:36  
标签:run thread Thread void 基础 线程 多线程 public

多线程

程序、进程和线程

  1. 程序 就是一系列有序执行的指令集合

  2. 进程 是程序在某个数据集合上的一次运行活动,也是操作系统进行资源分配和保护的基本单位。

  3. 进程就是程序的一次执行过程,程序是静态的,它作为系统中的一种资源是永远存在的。而进程是动态的,它是动态的产生,变化和消亡的,拥有其自己的生命周期。

  4. 线程由进程创建,是进程的一个实体。

  5. 一个进程中可以有多个线程,它们共享这个进程的资源。

单线程和多线程

  1. 单线程:同一时刻,只允许执行一个线程

  2. 多线程:同一时刻,可以执行多个线程

并行和并发

  1. 并发(concurreny):并发是指一个处理器同时处理多个任务。

    指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。

    img

  2. 并行(parallel)是指多个处理器或者是多核的处理器同时处理多个不同的任务

    并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

    img

举例:是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。

参考来源

线程基本使用

创建线程的两种方式

  1. 继承 Thread 类,重写 run 方法

  2. 实现 Runnable 接口,重写 run 方法

    image

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {

        // 创建Cat对象, 可以当成一个线程使用
        Cat cat = new Cat();
//        cat.run(); // 当成普通方法执行,执行完后才会接着后面的for, 串行执行
        cat.start(); // 启动线程——>最终会执行cat的run()方法
        /*
            public synchronized void start() {
                start0(); // native (本地)方法,由JVM调用, 底层是c/c++实现
                // 真正实现多线程的效果,是start0(),而不是run方法
            }
         */

        for (int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            Thread.sleep(1000);
        }
    }
}

class Cat extends Thread {
    int times = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("小猫喵喵叫 " + times++ + " 线程名:" +  Thread.currentThread().getName() );
            // 让该线程休眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 80) {
                break;
            }
        }
    }
}
//******************************************************************************
public class Thread02 {
    public static void main(String[] args) {

        Dog dog = new Dog();
        // dog.start() // 不能调用start() Runnable只有一个run() 方法
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable { // 通过实现Runnable接口,开发线程

    int count = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("小狗汪汪叫。。" + ++count + "  线程名:" + Thread.currentThread().getName());
            try {
                //休眠一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }

        }
    }
}

image

  • java 是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了

  • 从 Java 的设计来看,通过继承 Thread 或者实现 Runnable 接口来创建线程本质上没有区别,Thread 类本身就实现了 Runnable 接口

  • 实现 Runnable 接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。

/**
* 模拟线程,使用了设计模式:静态代理
*/

package com.learning.javase.thread.thread01;

public class Thread03 {
    public static void main(String[] args) {

        Tiger tiger = new Tiger();
        // dog.start() // 不能调用start() Runnable只有一个run() 方法,没有start方法
        ThreadProxy threadProxy = new ThreadProxy(tiger); // ThreadProxy 有start方法
        threadProxy.start();
    }
}


// 线程代理类,模拟了一个极简的Thread类
class ThreadProxy implements Runnable{
    private Runnable target = null;

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }

    }

    public void start() {
        start0(); // 真正实现多线程的方法
    }

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

class Animal {}
class Tiger extends Animal implements Runnable { // 通过实现Runnable接口,开发线程

    int count = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("老虎嗷嗷叫。。" + ++count + "  线程名:" + Thread.currentThread().getName());
            try {
                //休眠一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }

        }
    }
}

线程的如何理解

image

image

线程退出

  1. 当线程完成任务后,会自动退出
  2. 还可以通过使用变量来控制 run 方法退出的方式停止线程,即通知方式
public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();

        // 如果希望main线程去控制t1线程的终止,必须可以修改loop
        // 让t1 退出 run 方法,从而终止 t1线程---》 通知方式

        // 让主线程休眠10秒再通知
        System.out.println("主线程休眠10s");
        Thread.sleep(10000);
        t.setLoop(false);
    }
}

class T extends Thread {

    private boolean loop = true;

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
    @Override
    public void run() {
        int count = 0;
        // 设置一个控制变量
        while (loop) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("A Thread 运行中。。。" + count++);
        }
    }
}

线程常用方法线程常用方法

static Thread	currentThread()
Returns a reference to the currently executing thread object.
    
String	getName()
Returns this thread's name.
void	setName(String name)
Changes the name of this thread to be equal to the argument name.
        
void	run()
If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.   
    
void	start()
Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
// 底层会创建新的线程,   
    
int	getPriority()
Returns this thread's priority.
    
void	setPriority(int newPriority)
Changes the priority of this thread.
    
void	interrupt()
Interrupts this thread.
 

static void	sleep(long millis)
Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

void	join()
Waits for this thread to die.
// 线程插队,插队的线程一旦插队成功,则肯定先执行完插入的现成的所有的任务

static void	yield()
A hint to the scheduler that the current thread is willing to yield its current use of a processor.
// 线程的礼让,让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或者通知方式结束

  2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

    常见的守护线程:垃圾回收机制

    public class Daemon01 {
        public static void main(String[] args) throws InterruptedException {
            MyDaemon myDaemon = new MyDaemon();
    
            // 如果希望主线程结束后,子线程自动结束
            // 只需要将子线程设置为守护线程
            myDaemon.setDaemon(true);
            // 设置好守护线程,在启动
            myDaemon.start();
    
    
            for (int i = 0; i < 10; i++) {
                System.out.println("work.......");
                Thread.sleep(1000);
            }
        }
    }
    
    class MyDaemon extends Thread {
        @Override
        public void run() {
            for (;;) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("hahahahahah........");
            }
        }
    }
    

线程的生命周期

JDK 中用 Thread.State 枚举表示了线程的几种状态

public enum State {
    NEW,
    RUNNABLE,
	BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

image

线程状态转化图

image

线程的同步

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

  2. 线程同步也可以理解为:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

    同步的具体方法 - synchronized

    1. 同步代码块
    synchronized(对象){	// 得到对象的锁,才能操作同步代码
        // 需要被同步的代码
    }
    
    1. synchronized 关键字还可以放在方法声明中,表示整个方法为同步方法
       public synchronized void m() {
           // 需要被同步的代码
       }
    

举例

        // 这时synchronized 的是 this 对象,
        public synchronized void sell() { // 同一时刻只能有一个线程执行sell方法
            if (tickets <= 0) {
                System.out.println("售票结束");
                loop = false;
                return;
            }
       
    
    
    // 同步方法(静态)的锁为当前类本身
        //  加在 SellTicked1.class
        public synchronized static void m1() {
    
        }
    
        // 如果在静态方法中实现一个同步代码块
        public static void m2() {
            synchronized (SellTicket1.class){
                System.out.println("hello");
            }
        }
    

互斥锁

  1. 在 Java 中,引入了对象互斥锁的概念,来保证共享数据操作的完整性

  2. 每个对象都对应于一个可以称为 “互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

  3. 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问

  4. 同步的局限性:导致程序的执行效率降低

  5. 同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是同一个对象)

  6. 同步方法(非静态)的锁为当前类本身

    注意:

    • 同步方法如果没有 static 修饰,默认锁对象为 this
    • 如果方法使用 static 修饰,默认锁对象:当前类.class

线程死锁

案例

妈妈:你先写完作业,才让你玩手机

小明:你先让我玩手机,我才完成作业

应用案例,模拟线程死锁

package com.learning.javase.thread.synch;

public class DeadLock01 {
    public static void main(String[] args) {

        // 模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("线程A");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("线程B");
        A.start();
        B.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        // 下面业务逻辑分析
        // 1. 如果flag为true, 线程A就会先得到/持有 o1 对象锁,尝试 去获取 o2 对象锁、
        // 如果线程A 得不到 o2 对象所,就会 blocked
        // 2. 如果 flag 为 false, 线程B就会先得到/持有o2 对象锁,尝试 去获取 o1 对象锁
        // 如果线程B 得不到 o1 对象所,就会 blocked
        if (flag){
            synchronized (o1) { // 对象互斥锁,下面是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) { // 这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入2");
                }
            }
        } else {

            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + " 进入4");
                }
            }
        }
    }
}


释放锁

下面的操作不会释放锁

  1. 线程执行同步代码块或同步方法时,程序调用了 Thread.sleep()、Thread.yield()方法展厅当前线程的执行,不会释放锁

  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将线程挂起,该线程不会释放锁

    应该尽量避免使用suspend()resume()来控制线程,方法不在推荐使用

标签:run,thread,Thread,void,基础,线程,多线程,public
From: https://www.cnblogs.com/ai135/p/17566649.html

相关文章

  • Python基础day48
    伪类选择器<style>/*未访问时候显示的*/a:link{color:#FF0000;}/*鼠标移动到链接上*/a:hover{color:#FF00FF}/*选定的链接鼠标点击时出现*/a:active{c......
  • Java多线程:关于锁
    目录互斥访问资源互斥状态的要求:atomic、volatile操作系统互斥锁mutex的缺点偏向锁、轻量级锁、重量级锁减小锁的粒度共享锁-读锁、排他锁/互斥锁-写锁避免死锁的锁特性:重入锁学习ConcurrentHashMap的锁思想结语互斥访问资源加锁的本质是,为了竞争一个资源访问互斥状态,保证线程安......
  • 协程与多线程的区别
    多线程和协程处理任务的效率取决于具体的应用场景和实现方式。一般来说,协程比多线程更高效常见比较多线程:优点:多线程可以同时执行多个任务,适用于需要并行执行多个阻塞或计算密集型任务的场景。可以充分利用多核处理器的能力,提高整体的计算性能。......
  • Git基础命令
    全局配置用户名称和电子邮件gitconfig--globaluser.name"runoob"gitconfig--globaluser.email"[email protected]"初始化仓库gitinit添加文件到工作区gitadd*.cgitaddREADMEgitcommit-m'初始化项目版本'克隆一个仓库gitclone<repo>......
  • Java基础 变量、常量、作用域
    Java基础变量、常量、作用域变量-变量是什么:就是可以变化的量!-Java是一种强类型的语言,每个变量都必须声明其类型-Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域**注意事项:1每个变量都有类型,类型可以是基本类型,也可以是引用类型2......
  • Java基础入门
    一、注释方式标识符单行注释//多行注释/**/文档注释/***/二、基础1、进制进制前缀二进制0b八进制0十进制无十六进制0x2、数据类型typevarName[=value][{,varName[=value]}];bytenum1=127;shortnum2=32767;intn......
  • Linux基础命令记录
    基础命令详解1.cd:切换工作路径#cd默认回到宿主目录下#cd /opt切换到根下opt下2.ifconfig:查看更改ip地址安装包为:net-tools启动关闭指定网卡#ifconfigeth0down#ifconfigeth0up添加/删除临时子网卡#ifconfigaddens3410.254.254.74#ifcon......
  • Mysql基础6-常用数据库函数
    一、字符串函数1、常见Mysql内置字符串函数concat(s1,s2,s3,...):字符串拼接,将s1,s2,s3...等拼接成一个字符串lower(str):将字符串str全部转为小写upper(str):将字符串str全部转为大写lpad(str,n,pad):左填充,将字符串pad对str的左边进行填充,达到n个字符串长度rpad(str,n,......
  • C# 基础数据类型
    一. 在C#中,基础数据类型的取值范围和所占空间字节数如下:1.bool:-取值范围:true或false-空间字节数:12.byte:-取值范围:0到255-空间字节数:13.sbyte:-取值范围:-128到127-空间字节数:14.char:-取值范:Unicode字符集中的任意字......
  • 优化基础3——最短路径算法和蚁群算法
    1.复习了一下迪杰斯特拉和弗洛伊德算法具体参考[最短路径问题]—Dijkstra算法最详解-知乎(zhihu.com)Floyd算法详解通俗易懂-知乎(zhihu.com)迪杰斯特拉解决不了负边权问题,假如确定了一个点2,将他加入了visited集合此时有一个点3到点2的边是负边权,实际权重更小了,但是......