小议多线程

本文详细介绍了多线程的基本概念,探讨了并发与并行的区别,单线程的局限性,以及多线程如何提高执行效率和线程间的独立性。涵盖Java中Thread类、Runnable接口的应用,深入剖析了线程运行原理、内存模型、常用方法和线程安全问题的解决方案,包括锁对象和线程池的使用。
摘要由CSDN通过智能技术生成

多线程的基本概念,及其优点

并发

几个程序在一段时间内交替进行

并行

几个程序在一段时间内同时进行

进程

我们的软件都是存储在硬盘中的,当我们需要打开一个软件时,系统会将软件中我们需要用的数据传递到内存中。(因为内存中数据的传输速度比硬盘快很多倍),程序从硬盘进入到内存中开始运行这一过程叫做进程
在这里插入图片描述

线程

当我们启动程序的一个功能时,他就会从内存中开辟一条路径通往cpu,这个路径就叫做线程。cpu就会顺着这个路径来运行程序。
线程是进程的一个执行单元

单线程的弊端

当程序运行过程中一旦出现问题,程序将立即停止运行,不论后续的程序是否会受到这个问题的影响

多线程

一个程序往往有很多功能,当他的几个功能同时执行时,就会开辟多条到cpu的路径。这多条路径就是多线程

多线程的优点

1.执行效率高
2.多个线程之间互不影响

线程的使用

1.分时调度:所有线程轮流使用cpu,平均分配进程的使用时间
2.抢占式调度:优先给优先级高的线程使用cpu,当线程级别相同是,则随机分配。
java中线程就是采用抢占式调度的方法
java中当我们运行我们编写的程序时,jvm会执行main方法,main方法会进入到栈内存
jvm会在栈内存开辟一条路径通向cpu,此时cpu就可以通过这个路径
来执行此线程,main线程又叫做主线程

java中多线程的使用方法既运行原理

Thread类

首先我们创建Thread类的子类,在子类中重写run方法。也就是设置线程的任务,这个线程是用来干什么的。
在主函数中创建Thread类的子类对象,调用子类对象的start方法。即可启动多线程。
不可调用run方法,如果调用run方法,则只是简单的运行run方法。而不是开辟一个新线程

public class demo01thread extends Thread{       //创建一个类继承Thread
    @Override
    public void run() {     //重写run方法
        for (int i = 0; i < 5; i++) {      //新线程的任务
            System.out.println("我是新线程"+i);
        }
    }
}

主程序

public class Demo01Threadmain {
    public static void main(String[] args) {
        demo01thread de = new demo01thread();   //创建对象
        de.start();             //启动新线程
        for (int i = 0; i < 5; i++) {       //主函数任务
            System.out.println("我是主线程"+i);
        }
    }
}

Rannable接口

创建一个Rannable的实现类,在实现类中重写run方法(设置线程任务),在主函数中创建实现类对象,再创建Thread类含参对象,传入被重写了run方法的Ranable的实现类作为参数。用Thread类对象调用start方法

public class demo implements Runnable {
    @Override
    public void run() {
        System.out.println("我是新线程");
    }
}

主函数

public class Demom {
    public static void main(String[] args) {
        System.out.println("我是主线程");
        demo de = new demo();
        Thread th = new Thread(de);
        th.start();
    }
}

多线程运行原理

当我们使用多线程时,系统内部的运行是这样的
jvm先运行到主函数,此时开辟主线路,cpu在主线程上运行,在运行过程中,碰到了被调用的start方法,此时将会开辟一条新的线路到cpu。这两条线路争夺cpu的使用权运行。

多线程内存运行原理

main函数先进栈开始运行,在main函数进栈运行的过程中,执行到创建了Thread的继承类对象,进入内存创建对象,然后调用start方法,在调用start方法之前,系统一直都是单线程运行。当我们调用start方法时系统会开辟新的栈空间,Start方法会进入新的栈,两个栈开始争夺cpu的使用权运行

多线程常用的类方法

Thread.current()

获取当前线程

public class demo02Tg extends Thread{
    @Override
    public void run() {     //线程任务
        System.out.println("我是新线程");    
        System.out.println(Thread.currentThread()); //获取当前线程
    }
}

主函数

    public static void main(String[] args) {
        demo02Tg de = new demo02Tg();
        de.start();             //运行新线程
        System.out.println("hello");
        System.out.println(Thread.currentThread());     //获取当前线程,也就是主线程
        System.out.println("java");
    }

getname()

获取线程的名字
Thread类方法,因此必须继承了Thread类或者Thread类的子类才可以使用

public class demo02Tg extends Thread{
    @Override
    public void run() {     //线程任务
        System.out.println("我是新线程");
        System.out.println(Thread.currentThread()); //获取当前线程
        System.out.println(getName());      //获取当前线程的名字
    }
}

主函数

    public static void main(String[] args) {
        demo02Tg de = new demo02Tg();
        de.start();             //运行新线程
        System.out.println("hello");
        System.out.println(Thread.currentThread().getName());     //获取当前线程,也就是主线程的名字
        System.out.println("java");
    }

setName()

改变线程名字
Thread类方法,因此必须继承了Thread类或者Thread类的子类才可以使用

public class demo03sn extends Thread {
    public demo03sn(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("我是新线程,这是我的新名字");
        System.out.println(Thread.currentThread().getName());
    }
}

主函数

    public static void main(String[] args) {
        demo03sn de = new demo03sn("A");
        de.start();
        System.out.println("我是主线程");
        Thread.currentThread().setName("B");
        System.out.println("我也有了新名字"+Thread.currentThread().getName());
    }

多线程出现的问题及其解决办法

安全问题

但一个数据每次被调用都会发生该变时
此时如果多线程共享了这个数据,由于多线程在运行时互不干扰,就可能造成在一个线程调用他时,还没来得及改变,又被另一个线程调用,造成安全问题

解决办法

借用锁对象

synchronize(锁对象){
共享程序块
}
被synchronize包裹的程序块,只能有一个线程在运行
锁对象可以为任意对象,但是要对可能出现安全问题的线程使用相同的锁对象
原理:cpu在线程中运行的过程中遇到synchronize就会检查是否有
锁对象,如果有锁对象那么就会拿到锁对象,进入到同步
执行当中。同步执行结束,cpu归还锁对象。(可以抽象理解
锁对象为一把钥匙,必须有钥匙才可进入同步执行)
当下一个线程在运行的过程中,运行到synchronize也会检查是否
有锁对象,如果没有就会等待,其他线程归还锁对象
弊端:程序运行过程中频繁的判断锁,拿走锁,归还锁程序效率低

public class bksyn implements Runnable {
    Object o=new Object();          //定义锁对象
    int book=20;                    //定义初始变量20本书

    @Override
    public void run() {
        while(true){            //死循环,一直卖书
            if(book>0){
                synchronized (o){       //共享代码块
                    System.out.println(Thread.currentThread().getName()+"卖出了第"+book+"本书");
                    book--;
                }
            }
        }
    }
}

主函数

    public static void main(String[] args) {
        bksyn bk = new bksyn();
        Thread th = new Thread(bk);             //创建Thread对象 bk作为参数
        Thread th1 = new Thread(bk);
        Thread th2 = new Thread(bk);
        th.start();                             //开启三个线程
        th1.start();
        th2.start();
    }

调用被synchronize修饰的方法

创建一个被synchronize修饰的方法,方法内部为多线程共享数据代码块,在重写的run方法中调用此方法
原理:与上一个方法的原理相同,只不过这种方法的锁对象,默认为实现类本身

public class bksyn implements Runnable {
    Object o=new Object();          //定义锁对象
    int book=20;                    //定义初始变量20本书

    @Override
    public void run() {
        while(true){            //死循环,一直卖书
            if(book>0){
                sell();
            }
        }
    }
    public synchronized void sell(){        //创建方法,被synchronize修饰共享数据,避免安全问题
        System.out.println(Thread.currentThread().getName()+"卖出了第"+book+"本书");
        book--;
    }
}
    public static void main(String[] args) {
        bksyn bk = new bksyn();
        Thread th = new Thread(bk);             //创建Thread对象 bk作为参数
        Thread th1 = new Thread(bk);
        Thread th2 = new Thread(bk);
        th.start();                             //开启三个线程
        th1.start();
        th2.start();
    }

lock类

lock类有两个方法
1.lock()获取锁
2.unlock()释放锁
使用方法
1.在多线程的类内部创建lock的实现类Reentrantlock
2.在线程可能出现安全问题的代码块前调用lock方法
3.在线程可能出现安全问题的代码块后调用unlock方法
原理:与上述方法原理相同,只不过是手动来获取和释放锁对象

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class bksyn implements Runnable {
    
    Lock l = new ReentrantLock();      //创建lock的实现类

    int book=20;                    //定义初始变量20本书

    @Override
    public void run() {
        while(true){            //死循环,一直卖书
            if(book>0){
                l.lock();       //获取锁
                System.out.println(Thread.currentThread().getName()+"卖出了第"+book+"本书");
                book--;
                l.unlock();     //释放锁
            }
        }
    }
}

主函数

public class bksynm {
    public static void main(String[] args) {
        bksyn bk = new bksyn();
        Thread th = new Thread(bk);             //创建Thread对象 bk作为参数
        Thread th1 = new Thread(bk);
        Thread th2 = new Thread(bk);
        th.start();                             //开启三个线程
        th1.start();
        th2.start();
    }
}

线程的几种状态

运行

cpu正在该线程中运行

阻塞

cou在该线程中运行到同步代码块,但是没有获取到锁对象

睡眠

线程主动放弃对cpu的使用权,但是在指定时间后会自动醒来

等待

一般出现在同步代码块中,锁对象主动脱离线程,从而线程失去对cpu的控制权。锁对象将转移到处于阻塞状态的线程

唤醒

将处于等待状态线程唤醒,使他重新获取锁对象,继续运行下面的代码

等待与唤醒代码演示

public class bkwnl {
    Object o=new Object();          //借助来保证锁对象唯一
}
public class bkwn extends bkwnl implements Runnable{
    private bkwnl o;                //保证锁对象唯一,创建他们的继承对象

    public bkwn(bkwnl o) {          //有参构造,用参数赋值给继承对象来作为锁对象
        this.o = o;
    }

    @Override
    public void run() {
        synchronized (o){
            System.out.println(Thread.currentThread().getName()+"进入等待状态");
            try {
                o.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"继续运行");
        }
    }
}
public class bkwn1 implements Runnable{
    private bkwnl o;

    public bkwn1(bkwnl o) {         //有参构造,将参数作为锁对象
        this.o = o;
    }

    @Override
    public void run() {
        synchronized (o){
            System.out.println(Thread.currentThread().getName()+"我来唤醒你");
            o.notify();
        }
    }
}

主函数

public class bkwnm {
    public static void main(String[] args) {
        bkwnl bkl = new bkwnl();        //创建对象作为他们的参数
        bkwn bk = new bkwn(bkl);
        bkwn1 bk1 = new bkwn1(bkl);
        Thread th1 = new Thread(bk);
        Thread th2 =new Thread(bk1);
        th1.start();
        th2.start();
    }
}

线程池

因为创建出来的一个线程只能用一次很不方便
于是在jdk1.5之后,java内置了线程池,方便我们来使用

线程池原理

线程池内部其实是一个集合,集合中储存的是线程,当我们需要使用线程时,线程池内部的集合就会调用remove方法,将线程池删除出来供我们使用,我们使用完之后他会自动添加回去,从而达到反复使用

线程池的使用方法

线程池工厂(Executors)
1.使用线程池工厂的Executors里边提供的静态方法newFixedThreadPool(返回值用ExecutorService接收)生产出来一个含有指定数量的线程池
ExecutorService 线程池名称=Executors.newFixedThreadPool(数量)
2.创建类实现Rannable接口,重写run方法。(创建线程任务)
3.Executors的submit方法来来传递线程任务
线程池名称.submit(创建实现类对象);

public class demof implements Runnable {
    @Override
    public void run() {
        System.out.println("我是新线程");
    }
}

主函数

public class demofm {
    public static void main(String[] args) {
        ExecutorService ex=Executors.newFixedThreadPool(2);         //创建一个线程池,线程池里面有两个线程
        demof de = new demof();                 //创建线程任务的实现类接口
        ex.submit(de);                          //传递线程任务,并开启线程
        ex.submit(de);
    }
}

使用完之后也可用线程池的shutdown方法来销毁线程池(一般不建议执行)
线程池中的线程可以重复利用,因为他在用完之后会传递回去

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值