java进阶-第10章-多线程(一)

简介: 运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

一、并发、并行、进程、线程概念

并发与并行

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。

在操作系统中,安装了多个程序,并行指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。


注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。


线程与进程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

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

简而言之:**一个程序运行后至少有一个进程,一个进程中可以包含多个线程 **

我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:

进程

32a663670aee9684c708d03ca0d414ec_12d3a873e2cef1e368cbe9b98c1ddc15.png

线程

dd337adb0ab3ab1fccd4a7ad0c4df732_4f31145efabc6efe2feb657d351fb64c.png

线程调度:


分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

大部分操作系统都支持多进程并行运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。

其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。


二、创建线程

继承Thread类

定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。

创建Thread子类的实例,即创建了线程对象

调用线程对象的start()方法来启动该线程

示例:

public class MyThread extends Thread {
  //定义指定线程名称的构造方法
  public MyThread(String name) {
  //调用父类的String参数的构造方法,指定线程的名称
  super(name);
  }
  /**
  * 重写run方法,完成该线程执行的逻辑
  */
  @Override
  public void run() {
  for (int i = 0; i < 200; i++) {
    System.out.println(getName()+":"+i);
  }
  }
}

测试:


public class Demo1 {
  public static void main(String[] args) {
  //创建自定义线程对象
  MyThread mt = new MyThread("新建的线程");
  //开启新线程
  mt.start();
  //在主方法中执行for循环
  for (int i = 0; i < 10; i++) {
    System.out.println("主线程:"+i);
  }
  }
}

实现Runnable接口

定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

调用线程对象的start()方法来启动线程。

示例:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);         
        }
    }
}

测试:

public class Demo2 {
    public static void main(String[] args) {         //创建自定义类对象  线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "新建的线程");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程" + i);         
        }
    }
}

继承Thread 和实现Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。总结:实现Runnable接口比继承Thread类所具有的优势:


适合多个相同的程序代码的线程去共享同一个资源。

可以避免java中的单继承的局限性。

增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

三、线程常用方法

方法名 说明

public static void sleep(long millis) 当前线程主动休眠 millis 毫秒。

public static void yield() 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。

public final void join() 允许其他线程加入到当前线程中。

public void setPriority(int) 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。

public void setDaemon(boolean) 设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程)

线程的优先级

我们可以通过传递参数给线程的 setPriority() 来设置线程的优先级别

调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。优先级 : 只能反映 线程 的 中或者是 紧急程度 , 不能决定 是否一定先执行

Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

public class TestSleep {
        public static void main(String[] args)
        {
            new SleepThread().start();
        }
    }

示例:


/**
 * 优先级
 *
 */
public class PriorityThread extends Thread{
  @Override
  public void run() {
  for(int i=0;i<50;i++) {
    System.out.println(Thread.currentThread().getName()+"============"+i);
  }
  }
}

测试:

public class TestPriority {
  public static void main(String[] args) {
  PriorityThread p1=new PriorityThread();
  p1.setName("p1");
  PriorityThread p2=new PriorityThread();
  p2.setName("p2");
  PriorityThread p3=new PriorityThread();
  p3.setName("p3");
  p1.setPriority(1);
  p3.setPriority(10);
  //启动
  p1.start();
  p2.start();
  p3.start();
  }
}

线程的休眠

使用线程的 sleep() 可以使线程休眠指定的毫秒数,在休眠结束的时候继续执行线程

示例:

class SleepThread extends Thread
    {
        @Override
        public void run() 
        {
            String[] names = new String[]{"zs","ls","ww","z6"};
            int index = (int)(Math.random()*4);
            for (int i = 3;i > 0;i--)
            {
                System.out.println(i);
                try 
                {
                    Thread.sleep(1000);
                } 
                catch (InterruptedException e) 
                {
                    e.printStackTrace();
                }
                System.out.println("倒计时:"+i);
            }
            System.out.println("抽中学员为:"+names[index]);
        }
    }

测试:

public class TestSleep {
        public static void main(String[] args)
        {
            new SleepThread().start();
        }
    }

线程的让步

Thread.yield() 方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。

yield() 做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 **yield()** 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

案例:创建两个线程A,B,分别各打印1000次,从1开始每次增加1,其中B一个线程,每打印一次,就yield一次,观察实验结果.

示例:


class Task1 implements Runnable{
        @Override
        public void run() {
            for (int i = 0;i < 200;i++){
                System.out.println("A:"+i);
            }
        }
    }
class Task2 implements Runnable{
        @Override
        public void run() {
            for (int i = 0;i < 10;i++){
                System.out.println("B:"+i);
                Thread.yield();
            }
        }
    }
public class Demo {
        public static void main(String[] args) {
            new Thread(new Task2()).start();
            new Thread(new Task1()).start();
        }
    }

sleep()和yield()的区别

sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;

yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程

另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。


线程的合并

Thread 中,join()方法的作用是调用线程等待该线程完成后,才能继续往下运行。

join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。


fd6f1104aa5a5099387fcd24bcce7d66_c19de4f7c4b7d6a2d8a8c2d629e949e7.png

为什么要用join()方法

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

示例:

class JoinThread extends Thread{
    public JoinThread(String name){
        super(name);
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-->子线程:"+i);
        }
        System.out.println(Thread.currentThread().getName()+"线程结束运行");
    }
}
public class JoinDemo {
    public static void main(String[] args) {
        System.out.println("主线程开始运行。。。");
        JoinThread t1 = new JoinThread("新加入的线程");
        t1.start();
//        try {
//            t1.join();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        System.out.println("主线程开始结束。。。");
    }
}

四、守护线程

守护线程.setDaemon(true):设置守护线程

线程有两类:用户线程(前台线程)、守护线程(后台线程)

如果程序中所有前台线程都执行完毕了,后台线程会自动结束

垃圾回收器线程属于守护线程

public class DeamonThread extends Thread {
  @Override
  public void run() {
  for(int i=0;i<50;i++) {
    System.out.println(Thread.currentThread().getName()+"----------"+i);
    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
  }
  }
}

测试:

public class TestDeamon {
  public static void main(String[] args) {
  //创建线程(默认前台线程)
  DeamonThread d1=new DeamonThread();
  //设置线程为守护线程
  d1.setDaemon(true);//主线程结束便结束了
  d1.start();
  for(int i=0;i<10;i++) {
    System.out.println("主线程:----------"+i);
    try {
    Thread.sleep(200);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
  }
  }
}

五、线程生命周期

6e3e6702a185e62546c6e5c3297b985e_3b5e8a8c6d745af8180ffa29cce6c062.jpeg


五种基本状态

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。

新建状态(New)

当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable)

当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running)

当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked)

处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead)

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。


多线程状态之间的转换

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

相关文章
|
14天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
5天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
4天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
|
4天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
28 1
|
12天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
13天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
12天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
18天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
45 9
|
15天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
21天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####