文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java多线程知识点全面总结

2024-04-02 19:55

关注

Java多线程知识点总结

(1)什么是进程?什么是线程?

进程:

是并发执行程序在执行过程中分配和管理资源的基本单位,当程序进入内存圆形时即为线程

进程三大特点:

并行和并发:

线程:

(2)多线程的运行状态

(1)线程一般具有5种基本状态 :

创建, 就绪, 运行, 阻塞, 终止

在这里插入图片描述

(2)

创建状态:在程序中

(3)线程的创建和使用

(1)线程的创建

在Java 中如果要想实现多线程,那么必须依靠线程的主题类,但这个线程主题类在定义的时候,也需要一类特殊的要求,这个类可以继承 Thread ,实现Runnable 接口或者实现 Callable 接口来完成定义。任何一个类只要继承了 Thread 类就可以成为一个类的主类,同时线程中需要覆写父类终点 run( )方法。

(2)线程的使用

public class JavaDemoA {
    public static void main(String[] args) {
        new MyThread("线程A:").start();
        new MyThread("线程B:").start();
        new MyThread("线程C:").start();
    }
}
class MyThread extends Thread{ //线程的主体类
    private String title;
    public MyThread(String title){
        this.title=title;
    }
    @Override
    public void run() {
        for(int i=1;i<=4;i++){
            System.out.println(this.title+i);
        }
    }
}

(4)Runnable 接口实现多线程

Thread是 Runnable 的子类,继承了 Runnable 的接口,Thread 类的确可以方便实现多线程,但这种方式最大的缺陷就是单继承局限性

Runnable 接口从 JDK1.8 开始成为一个函数接口,可以直接利用lambda表达式来实现线程主体代码,同时在该接口中提供有run()方法进行线程功能定义

Thread构造方法:public Thread(Runnable target).

在Thread类中会保存有target属性,该属性保存的是Runnable的核心业务主体对象

当Thread.start() 方法启动多线程时也会调用Thread.run() 方法,而在 Thread.run() 会判断是否提供有target实例,如果提供则调用真实主体 

public class JavaDemoB {
    public static void main(String[] args) {
        new Thread(new MyThreadB("线程A:")).start();
        new Thread(new MyThreadB("线程B:")).start();
        new Thread(new MyThreadB("线程C:")).start();
    }
}
class MyThreadB implements Runnable{
    private String title;
    public MyThreadB(String title){
        this.title=title;
    }
    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            System.out.println(title+i);
  加粗样式      }
    }
}

(5)Callable接口实现多线程

Runnable 接口实例化多线程可以避免单继承问题,但 Runnable 中的run() 方法不能返回操作结果,为啦解决这样的问题,从JDK1.5开始对于多线程的实现提供了一个新的接口 java.util.concurrent.Callable

Callable接口在定义时需要定义泛型。

通过FutureTask 实现Callable接口和Thread类的联系,并且通过FutureTask类获取Callable接口中call()方法的返回值。 


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class JavaDemoC {
    public static void main(String[] args) throws Exception{
        FutureTask<String> task=new FutureTask<>(new MyThreadC());
        new Thread(task).start();
        System.out.println(task.get());
    }
}
class MyThreadC implements Callable<String> {
    @Override
    public String call() throws Exception {
        for(int i=1;i<=5;i++){
            System.out.println("线程执行:i="+i);
        }
        return "Callable实现多线程";
    }
}

多线程常用操作方法

(1)线程的命名和取得

public Thread (Runnable target,String name) 构造实例化线程对象,接受 Runnable 接口子类对象,同时设置线程名称;

public final void setName(String name) 普通 设置线程名称

public final String getName() 取得线程名字

每当实例化Thread类对象时,都会调用init()方法,并且在没有为线程命名时,自动命名。 


package ThreadTest;
public class JavaDemoD {
    public static void main(String[] args) {
        MyThreadD myThread=new MyThreadD();
        new Thread(myThread,"线程A").start();//线程手动命名
        new Thread(myThread).start();//线程自动命名
        new Thread(myThread,"线程B").start();
    }
}
class MyThreadD implements Runnable{
    @Override
    public void run() {
        //获取当前线程名称
        System.out.println(Thread.currentThread().getName());
    }
}

(2)线程休眠

线程有时候需要减缓执行速度,所以Thread 类提供啦sleep()方法

public static void sleep(long millis) throws InterruptedException设置线程毫秒数,时间一到自动唤醒

public static void sleep(long millis,int nanos) throws InterruptedException设置线程毫秒数,纳秒数,时间一到自动唤醒 


public class JavaDemoE {
    public static void main(String[] args){
        Runnable runnable=(()->{//Runnable接口实例
           for(int i=1;i<=5;i++){
               System.out.println(Thread.currentThread().getName()+i);
               try {
                   Thread.sleep(2000);//强制让线程休眠2秒
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        new Thread(runnable,"线程A:").start();
        new Thread(runnable,"线程B:").start();
    }
    //本程序两个线程对象,每一个线程对象执行时都要休眠一秒,因为多线程的启动和执行都是有操作系统,
    //随机分配,虽然看起来A,B线程同时休眠,但也有先后顺序
}

(3)线程中断

Thread 中提供的线程方法很多都会抛出InterruptedException中断异常,所以线程在执行过程中可以被另一个线程中断。

public boolean isInterrupted( ) 普通方法 判断线程是否被中断

public void interrupt( ) 普通 中断线程执行 


public class JavaDemoF {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            System.out.println("线程累了想休眠10S");
            try {
                Thread.sleep(10000);
                System.out.println("时间到,自然醒来");
            } catch (InterruptedException e) {
                System.out.println("被强制唤醒,继续工作");
            }
        });
        thread.start();//线程执行启动
        Thread.sleep(2000);//先让子线程运行2秒
        if(!thread.isInterrupted()){//如果线程没有中断
            System.out.println("让线程终止休眠");
            thread.interrupt();//线程中断
        }
    }
}

(4)线程强制执行

在多线程并发执行中每个线程对象都会交替执行,如果某个线程对象需要优先完成执行,可以使用 join ()方法强制执行,待其执行完毕后其他线程才会继续执行

public final void join() throws InterruptedException


public class JavaDemoG {
    public static void main(String[] args) {
        Thread threadB=new Thread(new MyThreadH(),"线程B");
        Runnable runnable=(()->{
           for(int i=1;i<=9;i++){
               System.out.println(Thread.currentThread().getName()+i);
               if(i==5){
                   try {
                       threadB.join();//如果i=5,强制执行线程B
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
        });
        new Thread(runnable,"线程A").start();
        threadB.start();
    }
}
class MyThreadH implements Runnable{
    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

(5)线程礼让

多线程在彼此交替执行时往往需要进行资源轮流占用,如果某些不是很重要的线程抢占到资源但又不着急执行时就可以暂时礼让出去,让其他线程先执行。

public static void yield( ) 线程礼让


public class JavaDemoH {
    public static void main(String[] args) {
        Runnable runnable=(()->{//Lambda实例化线程类对象,方便演示
            for(int j=1;j<=15;j++){
                System.out.println(Thread.currentThread().getName()+j);
            }
        });
        new Thread(new MyThreadI(),"礼让线程A").start();
        new Thread(runnable,"非礼让线程B:").start();
        new Thread(runnable,"非礼让线程C:").start();
        new Thread(runnable,"非礼让线程D:").start();
    }
}
class MyThreadI implements Runnable{
    @Override
    public void run() {
        for(int i=1;i<=10;i++){
            System.out.println(Thread.currentThread().getName()+i);
            if(i%3==0){
                Thread.yield();//线程礼让
            }
        }
    }
}

(6)线程优先级

在java线程操作时,所有线程运行前都保持就绪状态,此时那个线程的优先级高就有可能优先被执行

所有创建的线程都是子线程,启动时都是同样的优先级。

主线程是中等优先级 5

public final void setPriority ( int newPriority ) 设置线程优先级

MAX_PRIORITY 最高优先级 10

NORM_PRIORITY 中等优先级 5

MIN_PRIORITY 最低优先级 1

public final int getPriority ( ) 获取线程优先级


public class JavaDemoI {
    public static void main(String[] args) {
        Runnable runnable=(()->{
            for (int i=1;i<=5;i++){
                System.out.println(Thread.currentThread().getName()+i);
            }
        });
        Thread threadA=new Thread(runnable,"线程A");
        Thread threadB=new Thread(runnable,"线程B");
        Thread threadC=new Thread(runnable,"线程C");
        threadA.setPriority(Thread.MIN_PRIORITY);
        threadB.setPriority(Thread.NORM_PRIORITY);
        threadC.setPriority(Thread.MAX_PRIORITY);
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

(7)如何停止线程

(1)停止线程的3个方法(了解

(2)对多线程中 stop (), suspend(), resume() 方法在jdk1.2开始不建议使用,主要是因为这三个方法在操作时会产生死锁的问题。

(3)优雅的停止一个线程

代码演示:


package ThreadTest;
public class JavaDemoO {
    private static boolean flag=true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            long num=0;
            while (flag){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在运行"+num++);
            }
        },"执行线程").start();
        Thread.sleep(200);
        flag=false;
    }
}

(8)后台守护线程

(1)java中的线程分为两类:

代码:

package ThreadTest;
public class JavaDemoP {
    public static void main(String[] args) {
        Thread threadA=new Thread(()->{
            for(int i=1;i<=100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+i);
            }
        },"用户线程");
        Thread threadB=new Thread(()->{
            for(int i=1;i<=200;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+i);
            }
        },"守护线程");
        threadB.setDaemon(true);//设置为守护线程
        threadA.start();
        threadB.start();
    }
}

结果:用户线程结束时,守护线程也结束

在这里插入图片描述

(9)volatile 关键字

在多线程编程中,若干个线程为了可以实现公共资源的操作,往往是复制相应变量的副本,待完成操作后再将副本变量的数据与原始数据进行同步处理,如果开发者不希望通过副本数据进行操作,而是希望可以直接通过原始变量的操作(节省了复制变量副本与同步的时间),则可以在变量声明时使用volatile关键字.

使用volatile关键字

代码:


package ThreadTest;
public class JavaDemoQ {
    public static void main(String[] args) {
        new Thread(new MyThreadQ(),"线程A售票成功_剩余票数:").start();
        new Thread(new MyThreadQ(),"线程B售票成功_剩余票数:").start();
        new Thread(new MyThreadQ(),"线程C售票成功_剩余票数:").start();
        new Thread(new MyThreadQ(),"线程D售票成功_剩余票数:").start();
    }
}
class MyThreadQ implements Runnable{
    private volatile int ticket=50;//直接内存操作
    @Override
    public void run() {
        while (ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            System.out.println(Thread.currentThread().getName()+ticket);
        }
    }
}

结果:

在这里插入图片描述

volatile与synchronized的区别:

线程的同步和死锁

(1)线程同步问题

(1) 同步问题的引出


public class JavaDemoJ {
    private static int ticket=5;//总票数5张
    public static void main(String[] args) {
        Runnable runnable=(()->{
            while (true){//持续卖票
                if(ticket>0){
                    try {
                        Thread.sleep(100);//模拟网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖票成功\t剩余票数:"+ticket--);
                }
                if(ticket<=0){
                    System.out.println(Thread.currentThread().getName()+"卖票失败\t剩余票数:"+ticket);
                    break;
                }
            }
        }) ;
        new Thread(runnable,"售票员A:").start();
        new Thread(runnable,"售票员B:").start();
        new Thread(runnable,"售票员C:").start();
        //假设此时还有一张票,当第一个线程满足售票条件时(还未减少票数),其他线程也可能满足售票条件,
        //就有可能造成同一张票卖出去两次。
    }
}

问题所在:

假设此时还有一张票,当第一个线程满足售票条件时(还未减少票数),其他线程也可能满足售票条件就有可能造成同一张票卖出去两次。

在这里插入图片描述

(2) 解决同步问题

Java使用Synchronized关键字实现同步操作,同步的关键是要给代码上“锁”,而对于锁的操作有两种:同步代码块,同步方法。

(1)同步代码块

同步代码块是指使用synchronized关键字定义的代码块,在代码执行时,往往需要设置一个同步对象,由于线程操作的不确定性所以这时候的同步对象可以选择 this。

将票数判断和票数自减放在同一代码块中,当多线程并发执行时,只允许一个线程执行该部分代码块,就实现啦同步处理操作、


public class JavaDemoK {
    public static void main(String[] args) {
        MyThreadK myThreadK=new MyThreadK();
        new Thread(myThreadK,"线程A售票成功,剩余票数:").start();
        new Thread(myThreadK,"线程B售票成功,剩余票数:").start();
        new Thread(myThreadK,"线程C售票成功,剩余票数:").start();
        new Thread(myThreadK,"线程D售票成功,剩余票数:").start();
    }
}
class MyThreadK implements Runnable{
    private static int ticket=10;//总票数
    @Override
    public void run() {
        while (true){
            synchronized (this){//同步代码块
                if(ticket>0){
                    try {
                        Thread.sleep(100);//模拟网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+(--ticket));
                }else {
                    System.out.println("全部售空");
                    break;
                }
            }
        }
    }
}

结果:

在这里插入图片描述

(2)同步方法解决


public class JavaDemoL {
    public static void main(String[] args) {
        MyThreadL myThreadL=new MyThreadL();
        new Thread(myThreadL,"售票员A售票成功,剩余票数:").start();
        new Thread(myThreadL,"售票员B售票成功,剩余票数:").start();
        new Thread(myThreadL,"售票员C售票成功,剩余票数:").start();
        new Thread(myThreadL,"售票员D售票成功,剩余票数:").start();
    }
}
class MyThreadL implements Runnable{
    private static int ticket=10;
    @Override
    public void run() {
        sale();
    }
    public synchronized void sale() {//同步方法
        while (true){
        if(this.ticket>0){
            try {
                Thread.sleep(100);//网络延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.ticket--;
            System.out.println(Thread.currentThread().getName()+this.ticket);
        } else {
            System.out.println("票已售空");
            break;
        }
    }
    }
}

结果:

在这里插入图片描述

(2)线程死锁问题

(1)死锁问题的引出

代码:


package ThreadTest;
public class JavaDemoJ implements Runnable{
    private book book=new book();
    private money money=new money();
    public JavaDemoJ(){
        new Thread(this).start();
        book.tell(money);
    }
    public void run(){
        money.tell(book);
    }
    public static void main(String[] args) {
        new JavaDemoJ();
    }
}
class book{
    public synchronized void tell(money money){
        System.out.println("你先给我钱,我才给你书!");
        money.get();
    }
    public synchronized void get(){
        System.out.println("收到钱,把书给买家!");
    }
}
class money{
    public synchronized void tell(book book){
        System.out.println("你先给我书,我再给你钱!");
        book.get();
    }
    public synchronized void get(){
        System.out.println("收到书,把钱给卖家!");
    }
}

结果:

在这里插入图片描述

结论:本程序中采用大量同步处理操作,而死锁一旦出现线程将进入等待操作,并且不会向下继续执行。

(3)生产者消费者问题

(1)生产者生产,消费者取出,生产者生产一个,消费者取出一个

未同步代码:


package ThreadTest;
public class JavaDemoM {
    public static void main(String[] args) {
        Message message=new Message();
        new Thread(new Producer(message)).start();
        new Thread(new Consumer(message)).start();
    }
}
class Message{//消息类
    private String title;//标题
    private String content;//内容
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}
class Producer implements Runnable{//定义生产者
    private Message msg=null;
    public Producer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for(int i=1;i<=50;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i%2==0){
                msg.setTitle(i+"英雄");
                msg.setContent("名垂千古");
            }else {
                msg.setTitle(i+"小人");
                msg.setContent("遗臭万年");
            }
        }
    }
}
class Consumer implements Runnable{
    private Message msg;
    public Consumer(Message msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=1;i<=50;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("【"+msg.getTitle()+","+msg.getContent()+"】");
        }
    }
}

错误产生结果:

在这里插入图片描述

问题分析:

数据错位:假设生产者线程还刚在存储空间添加一个数据的标题,还未添加内容,程序就切换到消费者线程,消费者就会把没有生产者没有添加的内容和上一组生产的内容联系在一起导致数据错位。重复操作:生产者线程放入多组数据,消费者线程才开始取出,或者是消费者还没生产数据,消费者就已经重复取出数据。

同步代码解决:


package ThreadTest;
public class JavaDemoN {
    public static void main(String[] args) {
        Message2 msg=new Message2();
        new Thread(new Producer2(msg)).start();
        new Thread(new Consumer2(msg)).start();
    }
}
class Message2 {
    private String title;
    private String content;
    private boolean key=true;
    //true 允许生产不允许消费
    //false 允许消费不允许生产
    public synchronized void set(String title,String content) {//同步方法
        if(this.key==false){//允许消费不允许生产
            try {
                super.wait();//线程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.title = title;
        this.content = content;
        this.key=false;//生产完毕
        super.notify();//唤醒线程
    }
    public synchronized String get() {
        if(this.key==true){//允许生产不允许消费
            try {
                super.wait();//线程等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
       try {
           return title +","+ content;
       }finally {
           this.key=true;
           super.notify();
       }
    }
}
class Producer2 implements Runnable{//定义生产者
    private Message2 msg=null;
    public Producer2(Message2 msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for(int i=1;i<=50;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i%2==0){
                this.msg.set(i+"英雄","万古流芳");
            }else {
                this.msg.set(i+"小人","遗臭万年");
            }
        }
    }
}
class Consumer2 implements Runnable{
    private Message2 msg;
    public Consumer2(Message2 msg){
        this.msg=msg;
    }
    @Override
    public void run() {
        for (int i=1;i<=50;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("【"+msg.get()+"】");
        }
    }
}

正确执行结果:

在这里插入图片描述

原理解释:

将同步操作设置在Message类中,使用synchronized关键字可以使消息内容同步,不会造成消息错位。

在生产者和消费者线程中根据key判断,是否可以生产或者取出,如果不能生产或者取出,可使用 wait( ) 线程等待方法 等待另一方线程取出或者生产后,使用 notify( ) 线程唤醒,继续执行操作。notifyAll( ) 是唤醒全部等待的线程 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。 

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯