Java多线程

前言

本篇博文是本人回顾多线程写的随笔。本篇博客的结构简单适用于初学者,各种细节根据本人理解所述,内容十分到位,希望对在学习多线程的亲们有所帮助!

主要介绍有以下几点:
1.进程与线程
2.线程的作用
3.JVM的主线程
4.多线程的第一例
5.线程休眠
6.多线程内部类格式
7.线程基本属性
8.异常处理机制与多线程
9.继承Thread类
10.多线程原理
11.同步机制
12.同步方法

多线程


多线程的概述

1.进程与线程

进程就是正在运行的程序

进程是可以独立运行的

进程有自己独立的内存空间,不与其他进程共享数据

一个进程里面可以有n个线程

进程的理解:比如说IDEA编码软件,开启这个软件,IDEA这个进程就会在CPU上跑了(必须开启才叫进程)

线程就是进程中的控制单元

线程是进程中一个子任务执行者

线程不能独立运行,必须依赖进程而运行

同一个进程中的多个线程之间是可以共享数据的

2.线程的作用

多线程提高了程序的执行效率,因为它可以让程序中的多个任务并发执行

阅读一个小例子理解线程:可以把线程比喻成任务执行者(人)

如果我们的进程中只有一个人但是有两个任务,那么这个人需要先执行完第一个任务再去执行第二个任务,这个种就属于串行化的完成任务。

如果我们的进程中有两个人,那么可以给第一个人分配一个任务,给第二人分配一个任务,两个人同时执行任务,这种属于并发

3.JVM的主线程

JVM中有一个主线程,它的任务就是执行main()方法

当程序运行时JVM会为我们分配主线程

主线程的工作时执行mian()方法的内容

当main()结束后,主线程结束

public class Test {
    //程序运行时JVM会为我们分配主线程
    //主线程的任务就是执行main方法中的内容
  	//main方法运行结束,主线程结束
    public static void main(String[] args) {
        //方法体
    }
}

4.多线程的第一例

编写多线程程序的步骤分析

多线程程序实现

1.创建任务对象(创建一个类实现Runnable接口【我们可以把它看成是任务接口】)

2.创建线程对象(Thread)

3.把任务交给线程(Runnable —> Thread)

4.启动线程(start())

1.Runnable接口(任务接口)

Runnable接口表示线程的任务,他只有一个方法 void run() 。

注意:

Runnable不是线程,它只是线程的任务,如同main()方法是主线程的任务一样

2.Thread类(线程类)

Thread类表示线程类,可以把Thread理解成执行任务的人(执行者),在创建Thread对象时我们需要把任务交给Thread对象

每个Thread对象必须与一个Runnable对象绑定,即执行者对象与任务对象绑定在一起

 		//实现Runnable接口的类 我把它简称为任务类
        MyThread task = new MyThread();
        //创建Thread类 执行者(人) 
        //把任务交给人去完成(将任务对象传入人对象的构造方法中)
        Thread people = new Thread(task);
3.启动线程

创建类线程对象并不代表线程就开始执行了,想让线程开始执行任务还需要调用Thread类的start()方法启动线程,这时线程才是正真开始工作

 		//实现Runnable接口的类 我把它简称为任务类
        MyThread task = new MyThread();
        //创建Thread类 执行者(人)
        //把任务交给人去完成(将任务对象传入人对象的构造方法中)
        Thread people = new Thread(task);
        //开始执行任务 (开启线程)
        people.start();
多线程案例分析
1.多线程案例(该案例演示并发)

任务类:实现Runnable接口的类

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("我是新的线程,我正在执行循环打印:测试测试");
        }
    }
}

测试类:主线程(mian)和新创建的线程(new Thread(Runnable target))

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

        MyThread task = new MyThread();
        Thread people = new Thread(task);
        people.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("我是主线程,我正在执行打印:测试测试");
        }
    }
}

执行结果如图所示:

在这里插入图片描述

结果分析:

根据代码的运行规则可以知道,编写的程序是由上往下执行的。但是可以很明显的看到这段代码并没有。

在一个线程中的代码执行规则是从上往下执行,如过有了多个线程同时执行,那么它们之间就会发生抢夺CPU执行自己任务(也就是代码),你抢一下,我抢一下,这种就属于并发

5.线程休眠

Thread类的静态方法:static void sleep(long)方法可以让当前线程休眠指定的毫秒,可以把它放到线程任务中,无论那个线程执行到这行代码多会休眠一会

注意:

该方法声明了InterrupedException异常,所以需要处理

案例说明

测试类:

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

        /**
         * 创建任务类对象,将任务交给执行者
         * 执行者开始执行任务 (开启线程)
         */
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();

        for (int i = 0; i < 30; i++) {
            System.err.println("主线程执行");
            try {
                System.err.println("我是主线程,我需要休眠1秒.....");
                Thread.sleep(1000); //创建休眠定时器,设置休眠时间
                System.err.println("我是主线程,我休眠了1秒,现在我继续开始执行任务了...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

任务类

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("新的线程正在执行任务");
            try {
                System.out.println("我是新的线程,我需要休眠1秒....");
                Thread.sleep(1000); //创建休眠定时器,设置休眠时间
                System.out.println("我是新的线程,我休眠了1秒,现在我继续开始执行任务了...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果
在这里插入图片描述

6.多线程内部类格式

也是创建线程的一种方式,与前面的那一种一样,使用时看个人喜好

代码演示:

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

        //使用内部类的方式创建一个线程,并开启这个线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //定义一个循环,模拟这个新的线程执行的任务
                for (int i = 0; i < 30; i++) {
                    System.out.println("我是使用内部类的方式创建的线程,我正在执行任务");
                }
            }
        });
        thread.start(); //开启线程


        //定义一个循环,模拟主线程执行的任务
        for (int i = 0; i < 30; i++) {
            System.err.println("我的主线程,我正在执行任务");
        }
    }
}

测试结果:
在这里插入图片描述

7.线程基本属性

1.线程名称

Java提供了设置名称的构造方法,一旦确定名称就不能修改,所以Thread并没有提供setName()方法

代码演示

public class Test02 {
    public static void main(String[] args) {
   			//创建任务类
        MyThread myThread = new MyThread();
        //将任务类交给执行者,并给这个执行者起一个名字(这个线程)
        Thread thread = new Thread(myThread,"FirstNewThread");
        //通过Thread提供的非静态方法getName()方法获取线程名字
        String threadName = thread.getName();
        System.out.println("这个线程的名字为:"+threadName); //打印结果为:【这个线程的名字为:FirstNewThread】
    }
} 
2.当前线程

一个任务可能会被多个线程执行,有时候与当前正在执行任务的线程交互,这就需要在任务中获取当前线程。Thread类的静态方法currentThread() 可以获取当前线程

代码演示:当前线程正在执行当前任务

任务类:MyThread

public class MyThread implements Runnable{
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println("新的线程:" + threadName + "正在执行任务");
    }
}

测试类

public class Test02 {
    public static void main(String[] args) {
				MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread,"FirstNewThread");
        thread.start();

        String thisThreadName = Thread.currentThread().getName();
        System.out.println("主线程:" + thisThreadName + "正在执行任务");

    }
}

测试结果

在这里插入图片描述

8.异常处理机制与多线程

当某一个线程抛出异常时,JVM只回终止抛出异常的线程中,而不是终止所有线程。

大白话描述:那个线程抛出了异常,JVM就终止那个线程

Runnable接口中的run()方法并没有声明异常,这说明run()方法只能抛出RuntimeException(这个大家应该回很熟悉:运行时异常),当run(方法抛出异常后JVM回终止该线程)

代码演示

测试类:

public class Test02 {
    public static void main(String[] args) {
       MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread, "FirstNewThread");
        thread.start();


        for (int i = 0; i < 30; i++) {
            System.err.println("主线程:" + Thread.currentThread().getName() + "正在执行任务" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

任务类:MyThread

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread:" + "线程" + Thread.currentThread().getName() + "执行" + i);

            try {
                //设置线程休眠时间,处理异常
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //判断for循环 如果i的值等于8就抛出运行是异常,此时JVM就会终止这个线程
            if (i > 8) {
                throw new RuntimeException();
            }

        }
    }

测试结果:

在这里插入图片描述

结果分析:

根据编写的代码可以知道,在新的线程的run()方法中定义了一个for循环,循环打印语句,运行到了休眠代码时,休眠。try catch掉编译时报的错误,if判断for循环的数据,设定抛出异常,测试类中也是定义了一个for循环休眠打印数据,最后开启进程(运行JVM开启进程),线程开始执行(main和新创建的线程),当新的线程执行到if条件满足条件时抛出运行时异常,JVM终止了这个线程,而主线程并没有受到影响

9.继承Thread类

上面已经介绍到Thread是 执行类,Runnable是任务,创建Thread对象时把Runnable接口类对象指定给Thread,这样执行者就与任务绑定在一起了,

其实在创建Thread类对象时可以不为其指定Runnable接口实现类对象,而且Thread类有自己的默认任务,只不过默认任务是空的而已,但我们可以继承Thread类,然后重写Thread类的run()方法的方式来为线程指定任务

代码演示

测试类

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

        MyThread myThread = new MyThread();
        myThread.setName("FirstNewThread");
        myThread.start();


        for (int i = 0; i < 10; i++) {
            System.err.println("main:" + Thread.currentThread().getName() + "线程正在执行");
        }
    }
}

MyThread类继承Thread类

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread:"+Thread.currentThread().getName()+"线程正在执行");
        }
    }
}

测试结果

在这里插入图片描述

10.多线程原理

1.多任务操作系统

CPU就像是计算机的大脑,所有的进程都是由CPU运行的,支持同时运行多个进程的操作系统就是多任务任务系统,现在所有知名的操作系统都是多任务系统

2.抢占式

阅读一下列子理解抢占式:

假如你现在在你的电脑上开启了多个进程,比如IDEA,网易云音乐,QQ,微信,DBeaver,你在使用的时候好像感觉这几个进程是在同时运行,其实不是,正真工作的只有一个CPU,它在这几个进程之间调整切换(或者说快速切换),通常来说CPU运算的速度是非常快的,如果开启的进程过多时,CPU切换的周期过慢,那么你就会感觉到电脑卡。

还有一种就是如果你的电脑的CPU是多核的,就好比有多个CPU,对于单核CPU开启10个进程,相当于一个人完成10个任务,如果是双核的CPU,就相当于两个人完成10个任务

3.多线程与多任务

多线程和多任务是同一个道理,系统会随机分配CPU给某个线程,这时这个线程得到了唯一的CPU(对于单核CPU而言),运行一段时间后需要主动放弃运行权,让CPU空闲,这时系统再把运行权随机分配给其他线程,如果线程不主动放弃运行权,系统会过一段时间后强行剥夺运行权给其他线程。通常线程使用sleep()等方法主动放弃运行权

11.同步机制

进程之间是不能共享数据的,但是线程可以

线程安全问题

多线程之间共享数据是存在风险的,因为线程间可能会相互干扰,导致共享数据出现问题。

以下介绍几点为线程安全,其他的都为不安全

1.共享对象没有存取能力的,多个线程共享它是安全的

2.对象没有成员变量

3.对象有成员变量,但是成员变量的类型都为长量

4.多线程只对共享对象做取操作,没有存操作,共享也是安全的

同步代码快

同步代码块可以解决线程之间共享数据的问题。

原理是:当某些代码不希望多个线程同步执行时,就让线程排队执行即可,这需要指定一个对象为锁,以及一个区域作为互斥区域

Java中使用同步代码块的语法如下:

synchronized (obj){ //obj表示任何引用类型对象
        ......
  }

互斥区域

同步代码块的大括号指定互斥区域,只有这个区域才需要排队执行

对象锁

obj就是对象锁,它控制着互斥区域,在Java中任何对象都有同步锁定和未锁定状态,而这一切都不需要开发人员做什么,都由JVM自动控制

obj未锁定状态:当obj为未锁定状态时,说明没有线程在执行互斥区域的代码,这时线程对象可以获得对象锁,所谓的对象锁就是把obj的状态从未锁定变成锁定

obj锁定状态:当obj为锁定状态时,其他线程无法执行互斥区域代码,这时线程就进入到等待对象锁状态,我们称之为等待状态,直到前一个线程离开互斥区域,obj从锁定到未锁定,其它的线程才能获得对象锁

多线程经典的卖票案例演示同步代码块的作用
需求:

分四个窗口,去卖30张票(卖多少都可以,我这里是为了能截到所有数据给了一个较小的数据)

演示出现线程安全问题

测试类

public class Test04 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Station(),"一号");
        Thread thread2 = new Thread(new Station(),"二号");
        Thread thread3 = new Thread(new Station(),"三号");
        Thread thread4 = new Thread(new Station(),"四号");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

任务类

public class Station implements Runnable{

    //定义全局变量记录票数
    public static int ticket = 30;

    @Override
    public void run() {
        while (true) {
                //判断票数
                if (ticket > 0) {
                    System.out.println("Station:" + Thread.currentThread().getName() + "售票口,售出了第" + (31-ticket) + "张票");
                    //卖出去了一张票 总票数减一
                    --ticket;
                } else {
                    System.out.println("票已售完");
                    break;
                }
        }
    }
}

测试结果

在这里插入图片描述

看图分析

出现线程安全问题:可能会出现同票,0票,负票,出现线程安全性问题,不是我想要的数据,原因在于有多个线程在操作同一共享数据就是票数

解决方案:

用同步来解决:同步就可以让CPU在某段时间内只让一个线程进来做事情,其他线程不能进来,等你做完了,才能一个个进行做事情,排队单线程的

使用同步代码块解决线程安全问题 (为了方便观察数据我把票数该到30)

测试类

public class Test04 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Station(),"一号");
        Thread thread2 = new Thread(new Station(),"二号");
        Thread thread3 = new Thread(new Station(),"三号");
        Thread thread4 = new Thread(new Station(),"四号");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

任务类

public class Station implements Runnable{

    //定义全局变量记录票数
    public static int ticket = 30;

    @Override
    public void run() {
        while (true) {
            synchronized (Station.class) {
                //判断票数
                if (ticket > 0) {
                    System.out.println("Station:" + Thread.currentThread().getName() + "售票口,售出了第" + (31-ticket) + "张票");
                    //卖出去了一张票 总票数减一
                    --ticket;
                } else {
                    System.out.println("票已售完");
                    break;
                }
            }
        }
    }
}

测试结果
在这里插入图片描述
看图分析

同步代码块完美的解决了线程的安全问题,线程在抢夺CPU执行任务,当一个线程抢夺到CPU并且执行到了互斥区域时,此时的CPU归那一个线程执行,只有当该线程执行完,CPU再次被释放,线程再次抢夺CPU。

12.同步方法

除了同步代码块,Java还提供了同步方法,同步方法的语法如下

public synchronized void fun() {

 }

对象锁:当某个线程调用了某个对象的某个同步方法,那么这个对象就被锁定,其他线程再使用该对象调用任何同步方法都会进入等待状态

排斥区域:某个对象的所有同步方法,而不是你个同步方法,也不是非同步方法

代码演示

测试类

public class Test05 {
    public static void main(String[] args) {
        final SyncClass syncClass = new SyncClass();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"开始....");
                syncClass.fun01();
                System.out.println(Thread.currentThread().getName()+"结束....");

            }
        },"线程一").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"开始....");
                syncClass.fun02();
                System.out.println(Thread.currentThread().getName()+"结束....");

            }
        },"线程二").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"开始....");
                syncClass.fun03();
                System.out.println(Thread.currentThread().getName()+"结束....");

            }
        },"线程三").start();
    }
}

SyncClass类:该类中编写了多个同步方法

public class SyncClass {
    public synchronized void fun01(){
        System.out.println(Thread.currentThread().getName()+"fun01.......");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void fun02(){
        System.out.println(Thread.currentThread().getName()+"fun02.......");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void fun03(){
        System.out.println(Thread.currentThread().getName()+"fun03.......");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试结果

在这里插入图片描述
看图分析结果

程序运行线程开始抢夺CPU,由图不难看出来,首先是线程一抢到了CPU并执行了它run方法中对象调用的同步方法,此时同步方法运行到了方法内部的线程休眠代码,线程一休眠了5秒这时候线程二和线程三开始强夺CUP,但是线程一没有完成它要完成的任务同步方法锁定对象,其他线程进入等待状态,OK,过了一会线程一休眠醒来,继续执行,执行完,释放CPU,接下来线程二和线程三开始抢夺CPU

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值