栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java基础 多线程知识梳理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java基础 多线程知识梳理

目录

一、什么是多线程?

线程的基本概念

二、线程的创建方式

1.继承Theard类

2.实现runnable接口

两种实现多线程方法的对比分析

三:线程的生命周期及状态转化

四.线程的调度

 1.线程的优先级

1.优先级范围:

 2.获取线程池的优先级:

3.设置优先级:

2.线程休眠

 结果

3.线程让步

4.线程插队

5.多线程同步

1.线程安全问题

1.如何改进?

2.同步方法

6.死锁问题

如何预防?

7.总结

一、什么是多线程?

线程的基本概念

我们先学习两组概念。首先是并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生
  • 并行:指两个或多个事件在同一时刻发生(同时发生)

进程:一个内存中运行的一个应用程序就是一个进程。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程。进程也是程序的一次执行过程,是系统运行程序的基本单位。系统运行一个程序,就是一个进程从创建,到运行,消亡的过程,所有的应用程序都需要进到内存中执行(临时存储RAM)。进入到内存的程序叫进程。

线程:是进程中的一个执行单元,负责当前进程中程序的执行。一个进程至少有一个线程,一个进程中可以有多个线程,这样的应用程序称为多线程程序,多线程之间互不影响

 我们可以看到,任务管理器中的显示的应用都已经进入到内存当中,因此都是一个进程,每个进程都存在至少一个线程

二、线程的创建方式

1.继承Theard类

步骤:1.创建类继承Thead类重写Thead类中的run方法

           2.创建对象,并开启线程

public static void main(String[] args) {
        Mytheard3 mt=new Mytheard3();

        Thread thread=new Thread(mt);
        thread.start();
        for (int i = 0; i <100; i++) {
            System.out.println("main方法:"+" "+i);
        }
    }

}
class Mytheard3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100; i++) {
            System.out.println("runnable接口实现:"+" "+i);
        }
    }

2.实现runnable接口

步骤:1.创建类实现runnable接口重写run方法

           2.创建对象,通过Thead构造方法将Mythead的实例对象参数传入,并开启线程            

public static void main(String[] args) {
        Mytheard3 mt=new Mytheard3();
        Thread thread=new Thread(mt);
        thread.start();
        for (int i = 0; i <100; i++) {
            System.out.println("main方法:"+" "+i);
        }
    }

}
class Mytheard3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <100; i++) {
            System.out.println("runnable接口实现:"+" "+i);
        }
    }

两种实现多线程方法的对比分析

1.适合多个相同程序的代码的线程去处理同一资源的情况

2.可以避免由于java的单继承带来的局限性

三:线程的生命周期及状态转化

1. 新建状态(New)

创建一个线程对象后,该线程对象就处于新建状态,此时它不能运行,与其他Java对象一样,仅仅由Java虚拟机为其分配了内存,没有表现出任何线程的动态特征。

2. 就绪状态(Runnable)

当线程对象调用了start()方法后,该线程就进入就绪状态。处于就绪状态的线程位于线程队列中,此时它只是具备了运行的条件,能否获得CPU的使用权并开始运行,还需要等待系统的调度。

3. 运行状态(Running)

如果处于就绪状态的线程获得了CPU的使用权,并开始执行run()方法中的线程执行体,则该线程处于运行状态。一个线程启动后,它可能不会一直处于运行状态,当运行状态的线程使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。需要注意的是,只有处于就绪状态的线程才可能转换到运行状态。

4.阻塞状态(Blocked)

一个正在执行的线程在某些特殊情况下,如被人为挂起或执行耗时的输入/输出操作时,会让出CPU的使用权并暂时中止自己的执行,进人阻塞状态。线程进人阻塞状态后,就不能进入排队队列。只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。

·当线程试图获取某个对象的同步锁时,如里该销被其他线程所持有,则当前线程会进入阻塞状态,如果想从阻塞状态进入就绪状态就必须获取到其他线程所持有的锁。

·当线程调用了一个阻塞式的I/O方法时,该线程就会进入阻寒状态,如果想进入就绪状态就必须要等到这个阻塞的I/O方法返回。

·当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,如果想进入就绪状态就需要使用notify()方法唤醒该线程。

·当线程调用了Thread的sleep(long millis)方法时,也会使线程进入阻塞状态,在这种情况下,只需等到线程睡眠的时间到了后,线程就会自动进入就绪状态。

·当在一个线程中调用了另一个线程的join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,进入就绪状态。

需要注意的是,线程从阻塞状态只能进入就绪状态,而不能直接进人运行状态,也就是说,结束阻塞的线程需要重新进入可运行池中,等待系统的调度。

5.死亡状态(Terminated)

如果线程调用stop()方法或nun()方法正常执行完毕,或者线程抛出一个未捕获的异常(Exception)错误(Error),线程就进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能再转换到其他状态。

四.线程的调度

 1.线程的优先级

1.优先级范围:

 2.获取线程池的优先级:

采用getPriority()方法

3.设置优先级:

采用setPriority()方法

public class Demo21 {
    public static void main(String[] args) {
        //创建线程对象
        Thread min=new Thread(new Min(),"优先级较低的线程");
        Thread max=new Thread(new Max(),"优先级较高的线程");
        min.setPriority(Thread.MIN_PRIORITY);
        System.out.println("min优先级是:"+min.getPriority());
        max.setPriority(Thread.MAX_PRIORITY);
        System.out.println("max优先级是:"+max.getPriority());
        max.start();
        min.start();
    }
}
class Min implements Runnable{
    @Override
    public void run() {
        for (int i =1; i <=100; i++) {
            System.out.println(Thread.currentThread().getName()+"正在输出"+i);
        }
    }
}
class Max implements  Runnable{
    @Override
    public void run() {
        for (int i =1; i <=100; i++) {
            System.out.println(Thread.currentThread().getName()+"正在输出"+i);
        }
    }
}

结果:


2.线程休眠
public class Demo22 {
    public static void main(String[] args)throws Exception{
        new Thread(new sleepTheard()).start();
        for (int i = 0; i <=10 ; i++) {
            if (i==5){
                Thread.sleep(2000);
            }
            System.out.println("主线程正在输出:"+i);
            Thread.sleep(500);
        }
    }
}
class sleepTheard implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            if (i==3){
                try {
                    Thread.sleep(2000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("sleepTheard线程正在输出:"+i);
            try {
                Thread.sleep(500);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

 结果

 注意:sleep()方法是静态方法,只能控制当前运行的线程线程休眠,而不能控制其他线程休眠,休眠结束后,线程会返回就绪状态,而不是立即运行状态

3.线程让步

线程让步可以通过 yield ()方法来实现,该方法和 sleep ()方法有点相似,让当前正在运行的线程暂停,区别在于 yield() 方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。当某个线程调用 yield ()方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。

4.线程插队

在 Thread 类中提供了一个 join ()方法来实现“插队功能”。当在某个线程中调用其他线程的 join ()方法时,调用的线程将被阻塞,直到被 join ()方法加入的线程执行完成后它才会继续运行。

public class Demo24 {
    public static void main(String[] args) {
       //创建线程
       Thread t=new Thread(new EmergencyTheard(),"线程一");
       //开启线程
        t.start();
        for (int i = 0; i <6; i++) {
            System.out.println(Thread.currentThread().getName()+"输入:"+i);
            if (i==2){
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
class EmergencyTheard  implements Runnable{
    @Override
    public void run() {
        for (int i =1; i <6; i++) {
            System.out.println(Thread.currentThread().getName()+"输入:"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

从结果中我们能够看到,当main线程中的循环变量为2时,线程调用join()方法,此时t线程会插队执行,线程一开始执行,直到线程一执行完毕,main线程才继续执行

5.多线程同步

1.线程安全问题
public class Demo26 {
    public static void main(String[] args) {
        //创建对象
        SaleThread saleThread=new SaleThread();
        //创建线程
        new Thread(saleThread,"线程一").start();
        new Thread(saleThread,"线程二").start();
        new Thread(saleThread,"线程三").start();
    }
}

class SaleThread implements Runnable{
  //需求:卖出10张票
  int ticket=10;
    @Override
    public void run() {
        while (ticket>0){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"当前正在售卖:"+(ticket--));
        }
    }
}

 从结果可以看到卖出的票数出现0和负数,这显然是不合理的,那么原因是什么呢?是因为在进入到while循环中添加了sleep方法,由于线程有延迟,当票号减为1时,假设线程1此时售出1号票,对票号进行判断后,进入到while循环,在售票之前通过sleep方法让线程休眠,此时,线程2就会进行售票,因为此时的票号仍为1,因此线程2进入到循环,同理,4个线程都进入到while循环当中,休眠结束后,4个线程都进行售票,这样的话,就相当于将票号减了4次

1.如何改进?

1.同步代码块

public class Demo27 {
    public static void main(String[] args) {
        Ticket1 ticket1=new Ticket1();
        new Thread(ticket1,"线程一").start();
        new Thread(ticket1,"线程二").start();
        new Thread(ticket1,"线程三").start();

    }
}
class Ticket1 implements Runnable{
     int ticket=10;
     //定义任意一个对象作为锁对象
     Object lock=new Object();
    @Override
    public void run() {
        while (true){
            synchronized (lock){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"正在售出:"+(ticket--)+"张");
                }
                else {
                    break;
                }
            }
        }
    }
}

 加上一个锁之后,其他的线程就无法执行代码块,发生阻塞,等当前线程执行完同步代码块后,所有的线程开始抢占线程的执行权,抢到执行权的线程进入到同步代码块,执行代码,线程安全问题其实就是由多个线程同时处理共享资源所导致的。要想解决线程安全问题,必须得保证用于处理共享资源的代码在任何时刻只能有一个线程访问。

注意事项:这里的lock锁,上面的代码中, lock是一个锁对象﹐它是同步代码块的关键。
当线程执行同步代码块时,首先会检查锁对象的标志位。默认情况下标志位为1,此时线程会执行同步代码块,同时将锁对象的标志位置为0。当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后,锁对象的标志位被置为1,新线程才能进入同步代码块执行其中的代码,循环往复,直到共享资源被处理完为止。我们的锁必须是同一把锁,如果创建了多个线程,各自都有各自的锁,那么就起不到作用了。为了达到这个目的,我们可以采用字节码锁

格式:

class BuyTicketThreadExtend extends Thread {
   ...
            synchronized (BuyTicketThreadExtend.class) {
				...
			}
}

2.同步方法
public class Demo28 {
    public static void main(String[] args) {
        Ticket2 ticket1=new Ticket2();
        new Thread(ticket1,"线程一").start();
        new Thread(ticket1,"线程二").start();
        new Thread(ticket1,"线程三").start();
    }
}
class Ticket2 implements Runnable{
     int ticket=10;
    @Override
    public void run() {
     if (ticket>0){
         SaleTicket();
     }
    }
    public synchronized void SaleTicket(){
         while (true){
             if (ticket<=0){
                 break;
             }
             else {
                 System.out.println(Thread.currentThread().getName()+"正在售出"+(ticket--)+"张票");
             }
         }
     }
}

同步方法也有自己的锁,它的锁就是当前调用该方法的对象,也就是this指向的对象。这样做的好处是,同步方法被所有线程共享,方法所在的对象相对于所有线程来说也是唯一的,从而保证了锁的唯一性。当一个线程执行该方法时,其他的线程就不能进入该方法中,直到这个线程执行完该方法为止,从而达到了线程同步的效果。但是,有时候我们的方法需要是静态方法,静态方法不需要创建对象就可以直接使用“类名.方法名”的方式调用。这个时候,我们都没有对象,那么同步方法的锁就不会是this,那是什么呢?Java中静态方法的锁是该类所在类的class对象,该对象可以直接使用类名.class的方式获取。

6.死锁问题

线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续。

如何预防?

1.减少同步共享变量
2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
3.减少锁的嵌套。

7.总结

多线程是提升程序性能非常重要的一种方式,多线程可以充分利用cpu的资源,进而提高其使用效率,为解决高并发带来负载均衡问题

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/872152.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号