关于java中的线程

一、基本介绍


什么是线程?

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

举个例子:

当我们使用迅雷的时候,迅雷就是一个进程,而当你每下载一部电影的时候,就会开启一个线程。

什么是进程?

进程就是指运行中的程序,如点击迅雷,就启动了一个进程,操作系统就会为迅雷分配内存空间。


线程相关概念:

  1. 并发:同一时刻,单核cpu(某一个cpu)在对多个任务进行交替执行

  1. 并行:同一时刻,多核cpu(多个cpu)在对多个任务进行同时执行

注意:并发是对于一个cpu,并行则是对于多个cpu,在使用中并发和并行可同时存在


案例引入

在实际中,我们为了提高效率,可以使用多线程来处理事情,即同时处理多个任务。

举个大脑多线程的使用例子:

左手画圆,有手画矩形

那么在java中又如何使用多线程呢?


二、创建线程

通过继承Thread类

class A extends Thread{

}

此时我们还需要重写一个run()方法!

class A extends Thread{
    @Override
    public void run() {
        
    }
}

这样我们就具备了创建线程的条件了。下面我们创建线程

public class test01 {
    public static void main(String[] args) {
        A a = new A();
        a.start(); //开启线程
    }
}

class A extends Thread{
    @Override
    public void run() {

    }
}

注意:一定要在main()方法中通过start()方法,才能真正创建一个线程,如果直接调用run()方法是实现不了的(具体原因下面说明)。


通过实现Runnable接口

当然我们也要重写run()方法

class A implements Runnable{
    @Override
    public void run() {

    }
}

但是创建线程的方法就不一样了,因为Runnable这个接口他就一个run()方法,没有start()方法。

聪明java设计者于是就重载了一个Thread类的构造方法。

于是乎,我们现在可以通过创建一个Thread类对象,通过多态,来调用start()方法。

public class test01 {
    public static void main(String[] args) {
        A a = new A();
        Thread thread = new Thread(a);
        thread.start(); //我又创建出来拉~
    }
}

class A implements Runnable{
    @Override
    public void run() {

    }
}

两种实现方式的对比

  1. 通过继承Thread的方式,可以直接调用Thread类中方法

  1. 通过实现Runnable接口的方式,更加灵活,因为java只有单继承,这样你就可以继承其他重要的类辣。


思考:为什么不直接调用run()方法呢?

run()方法的本质就是一个普通的方法,没有真正的启动一个线程。

start()方法,他会去调用start0()方法,start0()方法是由JVM来调用的,内部源码看不到,不过肯定调用了run()方法,start0()的底层c/c++实现,就不再深究了,切勿走火入魔~

因此,真正实现多线程的是start0()方法,而不是run()方法。


浅析线程创建

为了常看输出结果,我们每输出一次就让他睡1s

public class test01 {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        Thread thread = new Thread(a);
        thread.start();

        for(int i = 0; i < 50; i++){
            System.out.println("main线程在运行~");
            Thread.sleep(1000);
        }
    }
}

class A implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("线程A在运行~");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

创建示意图:

当我们对当前程序进行运行(run)的时候

首先开了一个进程,然后启动了一个main线程,在main线程中又开启了一个子线程-A线程。

注意:

当main线程执行完结束了,子线程并不会直接结束,进程也不会结束,而是等待其他线程都结束了,进程才会结束。当main线程启动了子线程,main线程不会不动(阻塞),而是继续执行。

所以最终结果如下(线程A是死循环打印):


线程终止

  1. 等线程完成任务,自动退出

  1. 通过修改线程某个变量,来控制run()方法,让他提前退出

案例:

public class test01 {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        Thread thread = new Thread(a);
        thread.start();
        Thread.sleep(2*1000);
        //通知线程终止
        A.setLoop();
    }
}

class A implements Runnable{
    private  static boolean loop = true;
    @Override
    public void run() {
        while(loop){
            System.out.println("线程A在运行~");

        }
    }

    public static void setLoop(){
        loop = false;
    }
}

三、线程常用方法

常用方法

1.start()

创建并执行该线程,底层是JVM调用该线程的start0()方法

2.run()

调用当前线程的run()方法

3.setName(Stringname)

设置线程的名称为name

4.getName()

获取线程的名称

5.sleep(long millis)

让线程休眠millis毫秒(但不会终止线程!)

6.interrupt()

中断线程

7.yield()

线程礼让,让出cpu,让其他线程执行,但是会根据实际情况来决定,礼让是否会成功(取决于当时cpu负载)

8.join()

线程插队。如果线程插队成功,则必须先执行完插入的线程中所有任务,才进行其他线程


守护线程

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

举个例子:

通过使用多线程,我们知道main线程结束后,创建的子线程并不会结束,如果此时我们想要让子线程结束该怎么办呢?

  1. 通过使用线程终止的方式,手动让线程终止

  1. 将线程设置为守护线程

如下:

public class test01 {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        Thread thread = new Thread(a);
        thread.setDaemon(true); //将线程设置为守护线程
        thread.start();
    }
}

class A implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("线程A在运行~");
            
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            
        }
    }
}

注意:设置守护线程的语句要先与创建的语句,当所有用户线程结束后,守护线程才会结束

thread.setDaemon(true); //一定要先于start(),不然会抛异常
thread.start();

四、线程的状态

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

NEW

尚未启动的线程处于此状态

RUNNABLE

在JAVA虚拟机中执行的线程处于此状态

BLOCKED

被阻塞等待监视器锁定的线程处于此状态

WAITING

正在等待另一个线程执行的线程处于此状态

TIMED_WAITING

正在等待另一个线程执行到指定等待时间的线程处于此状态

TERMINATED

已退出的线程处于此状态

如下代码可以看到除BOLCKED的状态:

public class test01 {
    public static void main(String[] args) throws InterruptedException {
        A a = new A();
        System.out.println("线程A的状态:" + a.getState());
        a.start();
        for(int i = 0; i <5; i++){
            System.out.println("线程A的状态:" + a.getState());
            Thread.sleep(1000);
        }

        Thread.sleep(10* 1000);
        System.out.println("线程A的状态:" + a.getState());
    }
}

class A extends Thread{
    @Override
    public void run() {
        B b = new B();
        b.start();
        try {
            b.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for(int i = 0; i < 5; i++){
            System.out.println("线程A正在执行~");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class B extends Thread{
    @Override
    public void run() {
        try {
            sleep(2*1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

五、线程同步机制

为什么要引入这个东西呢?

案例引入

现模拟一个学生选课系统,有多个学生来抢一门课。

public class test02 {
    public static void main(String[] args) {
        //模拟三个学生来抢
        GetClass getClass = new GetClass();
        new Thread(getClass).start();
        new Thread(getClass).start();
        new Thread(getClass).start();
    }
}

class GetClass implements Runnable{
    private int classnums = 20;
    @Override
    public void run() {
        while(true){
            if(classnums <= 0){
                System.out.println("课被抢完了~");
                break;
            }
            else{
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("恭喜"+ Thread.currentThread().getName() + "抢到了,"+ "现在还剩" + (--classnums) + "名额");
            }
        }

    }
}

输出结果如下:

我们会发现不仅有超名额,还有重复抢一个名额。

这是因为某一时刻,一下进去了好几个线程,同时抢,同时减~

所以我们就引入了线程同步机制(锁)。


基本介绍

在多线程编程时,有一些敏感数据不允许被多个线程同时访问,可以使用线程同步机制,保证在任何时刻,数据只能有一个线程访问,即当有一个线程在对内存操作时,其他线程都不能对这个内存操作。

基本用法

  1. 使用代码块

synchronized (对象){
      //需要被同步的代码
}
  1. 声明在方法中,表示整个方法同步

public synchronized void fun(){
    //需要被同步的代码
}

通过线程同步机制,就能很好的解决案例中出现的问题。

public class test02 {
    public static void main(String[] args) {
        //模拟三个学生来抢
        GetClass getClass = new GetClass();
        new Thread(getClass).start();
        new Thread(getClass).start();
        new Thread(getClass).start();
    }
}

class GetClass implements Runnable{
    private int classnums = 20;
    @Override
    public void run() {
        while(true){
            synchronized (this){
                if(classnums <= 0){
                    System.out.println("课被抢完了~");
                    break;
                } else{
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("恭喜"+ Thread.currentThread().getName() + "抢到了,"+ "现在还剩" + (--classnums) + "名额");
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

输出如下:

一点毛病都没有了~


Synchronized细节

1.在java中引入了对象互斥锁的概念,目的是为了保证共享数据的完整性,但会降低效率

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

3.当某个对象被synchronized修饰的时候,一个线程访问了这个对象,那么它就有了这个互斥锁,即相当于拥有了这个对象的标记,此时该对象只能在同一时刻被一个对象所访问。

4.synchronized修饰非静态方法,锁的对象可以是this或其他对象

     public synchronized void fun(){
         
    }

5.synchronized修饰静态方法,锁的对象是当前类本身

class A extends Thread{
    public static void fun(){
        synchronized (A.class){

        }
    }
}

注意:

  1. synchronized修饰普通方法,默认锁对象为this

  1. synchronized修饰静态方法,默认锁对象为当前类

  1. 多线程锁的对象一定为同一个,否则锁了没用


死锁

举个例子:

小明对小王说:你先请我,我就请你。

小王对小明说:你先请我,我就请你。

于是就陷入死循环了。

创建一个死锁:

public class test01 {
    public static void main(String[] args) throws InterruptedException {
        DeadLockDemo deadLockDemo = new DeadLockDemo(true);
        DeadLockDemo deadLockDemo1 = new DeadLockDemo(false);
        deadLockDemo.start();
        deadLockDemo1.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() {
        if(flag){
            synchronized (o1){
                System.out.println("111");
                synchronized (o2){
                    System.out.println("222");
                }
            }
        }else{
            synchronized (o2){
                System.out.println("333");
                synchronized (o1){
                    System.out.println("444");
                }
            }

        }
    }
}

分析:一个线程想要获得o1对象的,才能执行下去,另一个线程想要获得o2对象的锁才能执行下去,两个线程都想要获得对象手中的锁,因此程序会卡在那。

所以一定要避免这种情况的发生。

最后

本人水平有限,如有问题,欢迎指出~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值