IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Java多线程 -> 正文阅读

[开发测试]Java多线程

多线程概述

多线程是Java的特点之一,掌握多线程编程技术,可以充分利用CPU的资源,更容易解决实际问题,并且多线程技术广泛应用于网络相关的程序设计中。

进程

进程是程序的一次动态执行过程,它对应了从代码加载、执行至完毕的一个完整过程,这个过程也是进程本身从产、发展至消亡的过程。

线程

线程是比进程更小的执行单位,一个进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程,也有它自身产生、存在和消亡的过程。与进程可以共享操作系统的资源类似,线程间也可以共享进程中的某些内存单元(包括代码与数据),并利用这些共享单元来实现数据交换、实时通信与必要的同步操作。与进程不同的是,线程的中断与恢复可以更加节省系统的开销。通俗地讲,线程是运行在进程中的“小进程”。

线程与进程的区别

(1)线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位。
(2)一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。
(3)进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其他进程不可见。
(4)线程上下文切换比进程上下文切换要快得多。

线程的执行

虽然执行线程给人一种几个事件同时发生的感觉,但这是一种错觉,因为计算机在任何给定的时刻只能执行那些线程中的一个。为了建立这些线程正在同步执行的感觉,Java 虚拟机快速地把控制从一个线程切换到另一个线程。这些线程将被轮流执行,使得每个线程都有机会使用CPU资源。

Java程序执行

Java应用程序总是从主类的 main方法开始执行。当JVM加载代码,发现 main方法之后,就会启动一个线程,这个线程称为“主线程”( main 线程),该线程负责执行main方法。那么,在main方法的执行中再创建的线程,就成为程序中的其他线程。如果main方法中没有创建其他线程,那么当main方法执行完最后一个语句,即main方法返回时,JVM就会结束Java应用程序。如果main方法中创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法即使执行完最后的语句(主线程结束),JVM 也一直要等到Java应用程序中的所有线程都结束之后,才结束Java应用程序。

线程的生命周期

(1)新建态。
当一个Thread类或其子类的对象被声明并创建,但还没有调用其 start()方法时,线程处于新建状态。此时它已经有了相应的内存空间和其他资源。
(2)就绪态。
当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法被调用时,线程首先进入就绪态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。此状态的线程已经具备了运行的条件但还没开始运行。
(3))运行态。
线程调度程序从可运行池中选择一个线程,将CPU使用权切换给线程,将其作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。此时线程对象的run()方法执行,run()方法定义了线程的具体任务,所以程序必须在子类中重写父类的run()方法。在线程没有结束run()方法之前,不要让线程再次调用start()方法,否则将发生llegalThread-StateException异常。
(4)中断态。
处于运行态的线程可能由于以下原因而中断执行,进入中断态:

  • 执行了sleep( int millsecond)方法。sleep是Thread类的一个类方法,线程一旦执行该方法,就立刻让出 CPU的使用权,使当前线程处于中断状态。经过指定的毫秒数( millsec-ond)后,线程重新进入就绪态。
  • 执行了wait()方法。wait是 object类的方法,线程对象一旦调用wait(),必须由其他线程调用notify()或notifyAll()方法通知它,才能使得它重新进入就绪态。
  • 执行了某个中断线程执行的操作,比如执行读/写操作引起线程阻塞。只有当引起阻塞的原因消除时,线程才能重新进入就绪态等待CPU资源,以便从原来中断处开始继续运行。
    (5)死亡态。
    处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有两个,一个是正常运行的线程完成了它的全部工作,即执行完run()方法中的全部语句,结束了run()方法;另一个原因是线程被提前强制性终止。线程一旦死亡,就不能复生。

线程的优先级

??处于就绪状态的线程首先进入就绪队列排队等候CPU资源,同一时刻在就绪队列中的线程可能有多个。Java 虚拟机中的线程调度器负责管理线程,调度器把线程的优先级分为10个级别,分别用Thread类中的常量表示。每个Java线程的优先级都在常数1和10之间,即Thread.MIN_PRIORITY和Thread.MAX_PRIORITY 之间。如果没有明确地设置线程的优先级别,每个线程的优先级都为常数5,即 Thread.NORM_PRIORITY。
??处于就绪状态的线程首先进入就绪队列排队等候CPU资源,同一时刻在就绪队列中的线程可能有多个。Java 虚拟机中的线程调度器负责管理线程,调度器把线程的优先级分为10个级别,分别用Thread类中的常量表示。每个Java线程的优先级都在常数1和10之间,即Thread.MIN_PRIORITY和Thread.MAX_PRIORITY 之间。如果没有明确地设置线程的优先级别,每个线程的优先级都为常数5,即 Thread.NORM_PRIORITY。
??线程的优先级可以通过setPriority(int grade)方法调整,如果参数不在1~10范围内,那么setPriority便产生一个llegalArgumenException异常。getPriority方法返回线程的优先级。需要注意是,有些操作系统只识别3个级别:1、5和 10。
??在采用时间片的系统中,每个线程都有机会获得CPU的使用权,以便使用CPU资源执行线程中的操作。当线程使用CPU资源的时间到时后,即使线程没有完成自己的全部操作,JVM也会中断当前线程的执行,把 CPU 的使用权切换给下一个排队等待的线程,当前线程将等待CPU资源的下一次轮回,然后从中断处继续执行。
??JVM的线程调度器的任务是使高优先级的线程能始终运行,一旦时间片有空闲,则使具有同等优先级的线程以轮流的方式顺序使用时间片。也就是说,如果有A、B、C、D四个线程,A和B的级别高于C和D,那么,Java调度器首先以轮流的方式执行A和B,一直等到A、B都执行完毕进入死亡状态,才会在C、D之间轮流切换。
在实际编程时,不提倡使用线程的优先级来保证算法的正确执行。

	public class Test {
	    @org.junit.Test
	    public void test() {
	        A a = new A("线程A");
	        B b = new B("线程B");
	
	        a.start();
	        b.start();
	
	        int aPriority = a.getPriority();
	        int bPriority = b.getPriority();
	
	        System.out.println("A线程的优先级:"+aPriority);
	        System.out.println("B线程的优先级"+bPriority);
	    }
	
	    public static void main(String[] args) {
	
	        A a = new A("线程A");
	        B b = new B("线程B");
	
	        a.start();
	        b.start();
	
	        System.out.println("main线程---"+Thread.currentThread().getName());
	    }
	}


	class A extends Thread{

	    public A(String name) {
	        super(name);
	    }
	
	    @Override
	    public void run() {
	        Thread.currentThread().setPriority(9);
	        System.out.println("线程A被调用");
	        System.out.println(Thread.currentThread().getName()+"优先级:"+Thread.currentThread().getPriority());
	    }
	}
	class B extends Thread{

	    public B(String name) {
	        super(name);
	    }
	
	    @Override
	    public void run() {
	        Thread.currentThread().setPriority(1);
	        System.out.println("线程B被调用");
	        System.out.println(Thread.currentThread().getName()+"优先级:"+Thread.currentThread().getPriority());
	    }
	}

结果显示
结果输出

线程的创建

  1. 方式一:继承于Thread类
    ①创建一个继承于Thread类的子类
    ②重写Thread类的run()–>将此线程要执行的操作声明在run()中
    ③创建Thread类的子类对象
    ④通过此对象调用start()
  2. 方式二:实现runnable接口,重写run方法,创建Thread类对象,参数为runnable的实现类
  3. 方式三:JDK5.0新增线程创建方式,实现Callable接口
    与Runnable接口相比Callable功能更强大一些

    ?●相比run方法,可以有返回值

    ?●方法可抛出异常

    ?●支持泛型的返回值

    ?●需要借助FutureTask类,比如获取返回结果
  4. Future接口:
    ①可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
    ②FutureTask是Future接口唯一的实现类
    ③FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

继承Thread类创建线程

public class ThreadTest {

    /**
     * 假设一个影院有3个售票口,分别用于向儿童、成人和老人售票。
     * 影院在每个窗口放50张电影票,分别是儿童票、成人票和老人票。3个窗口需要同时卖票,而现在只有一个售票员,这
     * 个售票员就相当于一个CPU,3个窗口就相当于3个线程。通过程序来看一看是如何创建这3个线程的。
     */
    public static void main(String[] args) {
        Child child = new Child("Child");
        Adult adult = new Adult("Adult");
        Old old = new Old("Old");

        child.start();
        adult.start();
        old.start();
    }
}
class Child extends Thread{
    
    public Child(String name) {
        super(name);
    }
    int ticket = 50;
    @Override
    public void run(){
        for (int i = 0; i < ticket; i++) {
        	//Thread.currentThread.getName()返回当前正在使用的线程名
            System.out.println(Thread.currentThread().getName()+"卖出票:"+i);
        }
    }

}

class Adult extends Thread{

    public Adult(String name) {
        super(name);
    }
    int ticket = 50;
    @Override
    public void run(){
        for (int i = 0; i < ticket; i++) {
            System.out.println(Thread.currentThread().getName()+"卖出票:"+i);
        }
    }
    
}
class Old extends Thread{

    public Old(String name) {
        super(name);
    }
    int ticket = 50;
    @Override
    public void run(){
        for (int i = 0; i < ticket; i++) {
            System.out.println(Thread.currentThread().getName()+"卖出票:"+i);
        }
    }
}

输出结果
在这里插入图片描述
Runnable实现类

public class MyRunnable implements Runnable{
    int ticket = 50;
    String name;
    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println(name+"卖出票:" +i);
        }
    }
}

main方法

    public static void main(String[] args) {

        MyRunnable child = new MyRunnable("Child");
        MyRunnable adult = new MyRunnable("Adult");
        MyRunnable old = new MyRunnable("Old");

        Thread childThread = new Thread(child);
        Thread adultThread = new Thread(adult);
        Thread oldThread = new Thread(old);

        childThread.start();
        adultThread.start();
        oldThread.start();
    }

结果显示
在这里插入图片描述

线程的休眠与中断

??Thread的 sleep()方法能使当前线程暂停运行一段时间(单位:毫秒)。需要注意的是,sleep()方法的参数不能为负,否则会抛出IllegalArgumentException 异常。而Thread的 interrupt()方法经常用来“吵醒”休眠的线程。当一些线程调用sleep 方法处于休眠状态时,一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法“吵醒”自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。
??Thread.sleep()与线程调度器交互,它将当前线程设置为等待一段时间的状态。一旦等待时间结束,线程状态就会被改为可运行(runnable),并开始等待CPU来执行后续的任务。

代码示例

主方法

public class InterruptTest {
    /**
     * 通过例10-6演示线程休眠。比如模拟一个火车站的售票窗口,有两个线程售票员ticket-Seller
     * 和乘客passenger,因为没人买票,售票员决定休息30分钟,这时有个乘客过来买票,吵醒休眠的售票员。
     */
    public static void main(String[] args) {

        InterruptRunnable interruptRunnable = new InterruptRunnable();

        interruptRunnable.seller.start();
        interruptRunnable.passenger.start();
    }
}

Runnable实现类

public class InterruptRunnable implements Runnable{
    
    private int ticket = 50;//一共50张票
    public Thread seller;//售票员
    public Thread passenger;//顾客

    public InterruptRunnable() {
        seller = new Thread(this);
        passenger = new Thread(this);
    }
    @Override
    public void run() {
        if (Thread.currentThread() == seller) {
            try {
                System.out.println("暂时没人买票休息30分钟");
                seller.sleep(1000*60*30);
            } catch (InterruptedException e) {
                System.out.println("售票员被吵醒了,开始卖票");
            }
        } else if (Thread.currentThread() == passenger){
            System.out.println("顾客来了,售票员要开始卖票");
            seller.interrupt();//让售票员调用interrupt吵醒自己
        }
    }
}

结果显示结果显示

线程让步与插队

1. 线程让步

线程让步的方法就是让正在执行的任务暂定,使其他任务继续执行。线程让步的方法如下:

public static void yield();//暂停当前正在执行的线程对象,并执行其他线程

主方法

public class YieldTest {

    /**
     * 通过一个实例演示线程让步。比如在校园中,经常会看到两队同学互相抢篮球,当某个同学抢到篮球后就可以拍一会,之后他会把篮球让出来,大家重新开始抢篮球,
     * 本实例模拟红蓝两队,每队分别抢到5次球的情况。这个过程就相当于Java程序中的线程,在多让步线程程序中,可以通过线程的yield()方法将线程转换成就绪状
     * 态,让系统的调度器重新调度一次,达到线程让步的目的。案例将在一个多线程程序中,通过 yield()方法对其中一个线程设置线程让步来演示。
     */
    public static void main(String[] args) {
        YieldRunnable runnable = new YieldRunnable();

        Thread redThread = runnable.red;
        Thread blueThread = runnable.blue;

        redThread.start();
        blueThread.start();

    }
}

Runnable实现类

public class YieldRunnable implements Runnable{

    int count = 5;//两个线程共拍5次球

    Thread red;
    Thread blue;

    public YieldRunnable() {
        red = new Thread(this);
        blue = new Thread(this);

        red.setName("红队");
        blue.setName("蓝队");
    }

    @Override
    public void run() {
        while (count-- > 0) {
            System.out.println(Thread.currentThread().getName()+"拍第"+count+"次球");
            //执行一次,就调用yield方法使线程重新进入就绪态
            Thread.yield();
        }

    }
}

输出结果
执行结果
执行结果

注意:
??两次执行的结果不同,第一次是红蓝交替执行,第二次蓝队连续执行两次,红队也连续执行两次。说明调用yield方法仅仅只是让线程重新回到就绪状态,仍有可能被CPU调用。

2.线程插队

??线程插队是通过join()方法阻塞当前线程,先完成被join()方法加入的线程,之后再完成其他线程。使用线程插队join()方法时,需要抛出InterruptedException异常。
??如果在线程A占有CPU资源期间,B线程插人,那么A线程将立刻中断执行,一直等到线程B执行完毕,A线程再重新排队等待CPU资源,以便恢复执行。

主方法

public class JoinTest {
    /**
     * 通过一个实例演示线程插队。比如在火车站买票的时候,有的乘客着急赶火车,会插到队伍前面先买车票
     * ,其他乘客再买票。那么在多线程程序中,也可以通过线程插队,让插队的线程先执行完,然后本线程才
     * 开始执行。
     */
    public static void main(String[] args) {

        JoinRunnable runnable = new JoinRunnable();

        runnable.thread1.start();
    }
}

Runnable实现类

public class JoinRunnable implements Runnable{

    Thread thread1;
    Thread thread2;

    public JoinRunnable() {
        thread1 = new Thread(this);
        thread2 = new Thread(this);

        thread1.setName("线程A");
        thread2.setName("线程B");
    }

    @Override
    public void run() {
        if (Thread.currentThread() == thread1) {

            System.out.println(Thread.currentThread().getName()+"准备买票...");
            thread2.start();//在执行A线程的时候启动B线程,以便线程B插队
            try {
                thread2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"开始买票...");
            try {
                Thread.sleep(1000*3);//花三秒时间买票
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"结束买票。");
        } else {
            System.out.println("线程B现在插入...");
            System.out.println("线程B开始买票...");
            try {
                Thread.sleep(1000*3);//花三秒时间买票
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程B结束买票.");
        }

    }
}

输出结果
输出结果

线程同步与死锁

1.线程同步

??在处理多线程问题时,必须要注意这样一个问题:当两个或多个线程同时访问同一个变量,并且一些线程需要修改这个变量的情况。程序应对这样的问题做出正确的处理,否则可能发生混乱。
??对于多个线程修改同一个资源的情况,采用线程同步的方式。线程同步的基本实现思路是给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,哪个线程才有权利访问该共享资源。基于上述思想,编程语言的设计思路是把同步锁加在代码段上,即把同步锁加在“访问共享资源的代码段”上。Java语言的线程同步就是若干个线程都需要使用synchronized关键字给代码段加锁。
??处于运行状态的线程若遇到sleep()方法,则线程进入睡眠状态,不会让出资源锁,sleep()方法结束,线程转为就绪状态,等待系统重新调度;处于运行状态的线程可能在等待IO,也可能进入挂起状态。当IO完成,转为就绪状态;处于运行状态的线程遇到yield()方法,线程转为就绪状态( yield 只让给权限比自己高的);处于运行状态的线程遇到wait()方法,线程处于等待状态,需要notify(YnotifyALL()来唤醒线程,唤醒后的线程处于锁定状态,获取了“同步锁”之后,线程才转为就绪状态;处于运行状态的线程,加上 synchronized后变成同步操作,处于锁定状态。获取了“同步锁”之后,线程才转为就绪状态。
??当一个线程使用的同步方法中用到某个变量,而此变量要在其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法。wait()方法可以中断线程的执行,使本线程等待,暂时让出CPU的使用权,并允许其他线程使用这个同步方法。其他线程如果在使用这个同步方法时不需要等待,那么它在使用同步方法的同时,应当用notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待,曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵循“先中断先继续”的原则。如果使用notify()方法,那么只是通知处于等待中的线程的某一个结束等待。wait()、notify()和 notifyAll()都是Object类中的final方法,被所有的类继承且不允许中断的方法。特别需要注意的是,不可以在非同步方法中使用wait()、notify()和 notifyAll()。

线程同步有两种方式:

1.同步代码块
synchronized(同步监视器){
??//需要被同步的代码,不能多了,也不能少了
}
说明:
1.操作共享数据的代码即为需要被同步的代码
2.共享数据:需要多个线程共同操作的数据
3.同步监视器:俗称,“锁”。任何一个类的对象都可以充当这个锁
要求
多个线程必须共用一把锁
补充
1.在实现Runnable接口创建多线程的方式中,我们可以考虑使用this来充当同步监视器
2.在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
2.同步方法
(1)如果共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的。
(2)同步方法中没有显示的出现锁,直接将方法声明为synchronized后,隐式调用this为锁

主方法

public class SynchroTest1 {
    /**
     * 假设去买火车票,一趟列车的车票数量是固定的,不管有多少个地方可以买火车票,
     * 买的一定是这些固定数量的火车票。如果把各个售票点理解为线程的话,则所有线
     * 程应该共同拥有同一份票数。
     */
    public static void main(String[] args) {

        SynchroRunnable runnable = new SynchroRunnable();

        Thread thread1 = new Thread(runnable);
        thread1.setName("平顶山");

        Thread thread2 = new Thread(runnable);
        thread2.setName("杭州");

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

线程同步前

public class SynchroRunnable implements Runnable{

    private int ticket = 50;//总共50张票

    @Override
    public void run() {
        while (ticket > 0) {//ticket = 0 不满足条件
            System.out.println(Thread.currentThread().getName()+": 出票号 :"+ticket);
            ticket--;//卖出一张票,票数减一
        }
    }
}

结果输出
在这里插入图片描述
问题
出现了两次50,出现的原因是当平顶山拿到50票号的时候还没让票号减一,然后杭州就也拿到了50票号,至于为什么接下来不是从48开始,我没想明白,希望有大佬能够解答一下。

解决方式1:同步代码块

public class SynchroRunnable implements Runnable{

    private int ticket = 10;//总共10张票

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket <= 0) break;

                System.out.println(Thread.currentThread().getName()+"出票号:"+ticket);

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
            }
        }
    }
}

输出结果
在这里插入图片描述
解决方式二:同步方法

public class SynchroRunnable implements Runnable{

    private int ticket = 10;//总共10张票

    @Override
    public void run() {
        show();
    }
    public synchronized void show() {
//        notifyAll();
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName()+"出票号:"+ticket);

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket--;
            try {
                notifyAll();
                wait();
                notifyAll();//这里不加notifyAll()的话最后一个线程会处于wait状态导致程序无法结束
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果输出
在这里插入图片描述

注意
wait、notify、notifyAll只能使用在同步代码块中
notifyAll,唤醒所有被wait的线程
sleep和wait的异同
1.相同点:一旦执行方法,都可以使得当前线程进入阻塞状态
2.不同点:
??①两个方法声明的位置不同,Thread类中声明sleep,Object类(作为锁的那个类)中声明wait。
??②调用的要求不同:sleep()可以在任何需要的场景下调用,wait必须使用在同步代码块中。
??③关于是否会释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁。

2.线程死锁

??资源共享时需要进行同步操作,程序中过多的同步会产生死锁。比如,张三想要李四的画,李四想要张三的书,张三对李四说:“先把你的画给我,我就给你书。”李四也对张三说:“先把你的书给我,我就给你画。”此时,张三等李四答复,而李四也等张三答复,那么这样下去最终结果就是张三得不到李四的画,李四也得不到张三的书,这实际上就是死锁。
main方法

public class DeadTest {
    /**
     * 张三想要李四的画,李四想要张三的书,张三对李四说:“先把
     * 你的画给我,我就给你书。”李四也对张三说:“先把你的书给我,我就给你画。
     */
    public static void main(String[] args) {

        DeadRunnable runnable = new DeadRunnable();

        runnable.thread1.start();
        runnable.thread2.start();

    }
}

Runnable实现类

public class DeadRunnable implements Runnable{
    private static ZhangSan zhangSan = new ZhangSan();
    private static LiSi liSi = new LiSi();

    Thread thread1;
    Thread thread2;

    public DeadRunnable() {
        thread1 = new Thread(this);
        thread2 = new Thread(this);
    }

    @Override
    public void run() {
        if (Thread.currentThread() == thread1) {
            synchronized (zhangSan) {
                zhangSan.say();
                synchronized (liSi) {
                    zhangSan.want();
                }
            }
        } else {
            synchronized (liSi) {
                liSi.say();
                synchronized (zhangSan) {
                    liSi.want();
                }
            }
        }

    }
}
class ZhangSan {
    public void say() {
        System.out.println("我是张三,我有书");
    }
    public void want() {
        System.out.println("我是张三,我想要李四的书");
    }
}
class LiSi {
    public void say() {
        System.out.println("我是李四,我有画");
    }
    public void want() {
        System.out.println("我是李四,我想要张三的书");
    }
}

输出结果
在这里插入图片描述
可以看出张三和李四都在等待对方去释放锁,然后输出想要对方的书和画,于是程序一直处于等待状态。

实现Callable接口

main方法
public class CallTest {
    /**
     * 100以内偶数和
     */
    public static void main(String[] args) {

        CallableTest callable = new CallableTest();
        //FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
        FutureTask futureTask = new FutureTask(callable);

        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            Object sum = futureTask.get();
            System.out.println("总和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
Callable实现类
class CallableTest implements Callable {
    int sum;
    @Override
    public Object call() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

输出结果
在这里插入图片描述
线程池
●JDK5.0起提供了线程池相关的API:ExecutorService和Executors
●ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
???void execute(Runnable command),执行任务/命令,没有返回值,一般用来执行Runnable
???Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
●Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
???Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
???Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
???Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
???Executors.newScheduledThreadPool(n) :创建一个线程池,它可安排在给定延迟后运行命令或定期的执行。
代码示例

public static void main(String[] args) {
        //1.通过ExecutorService工具类Executors创建一个指定线程数的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //2.调用execute去启动线程
        service.execute(new Number1Thread());

        //调用submit去启动线程
        FutureTask futureTask = new FutureTask(new Number2Thread());//利用FutureTask去获取返回值
        service.submit(futureTask);//启动线程
        try {
            Object sum = futureTask.get();
            System.out.println("10以内偶数和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //关闭线程池
        service.shutdown();
    }

class Number1Thread implements Runnable {

    /**
     * 10以内的奇数
     */
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            if (i%2 != 0) {
                System.out.println(Thread.currentThread().getName()+"输出:"+i);
            }
        }
    }
}
class Number2Thread implements Callable {

    /**
     * 10以内的偶数
     */
    @Override
    public Object call() {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            if (i%2 == 0) {
                sum += i;
                System.out.println(Thread.currentThread().getName()+"输出:"+i);
            }
        }
        return sum;
    }
}

输出结果
在这里插入图片描述

Lock
??1.从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步,同步锁对象使用Lock对象来充当
??2.java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
??3.ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。
??4.synchronized 与 lock 的异同:
??相同点:二者都可以解决线程安全问题
??不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器; lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())。
代码示例

public class LockTest {

    public static void main(String[] args) {
        /**
         * 问题描述:
         *      有两个储户分别向同一个账户存3000元,每次存1000,存三次。每次存完打印账户余额
         */
        Depositor depositor = new Depositor();

        depositor.thread1.start();
        depositor.thread2.start();
    }
}
class Depositor implements Runnable {

    /**
     * ReentrantLock是Lock接口实现类,
     * 当不传入参数时,默认传入false,表示该线程释放锁资源后接下来仍有可能抢到锁
     * 参数为true时,表示下个该线程释放锁资源后,由其他线程去抢锁
     */
    private ReentrantLock reentrantLock = new ReentrantLock();
    private double account = 0;
    Thread thread1;
    Thread thread2;

    int a1 = 3;
    int a2 = 3;

    public Depositor() {
        thread1 = new Thread(this);
        thread2 = new Thread(this);

        thread1.setName("储户A");
        thread2.setName("储户B");

    }
    @Override
    public void run() {
        if (Thread.currentThread() == thread1) {
            for (int i = 1; i <= a1; i++) {
                System.out.println(Thread.currentThread().getName()+"进行第"+i+"次操作");
                save();
                show();
                System.out.println(Thread.currentThread().getName()+"结束第"+i+"次操作");
            }
        } else {
            for (int i = 1; i <= a2; i++) {
                System.out.println(Thread.currentThread().getName()+"进行第"+i+"次操作");
                save();
                show();
                System.out.println(Thread.currentThread().getName()+"结束第"+i+"次操作");
            }
        }
    }

    private void save() {
        try {
            reentrantLock.lock();
            account += 1000;
            System.out.println(Thread.currentThread().getName()+"存入1000元");
        } finally {
            reentrantLock.unlock();
        }
    }

    private void show() {
        System.out.println(Thread.currentThread().getName()+"显示账户余额为:"+account);
    }
}

输出结果
在这里插入图片描述

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-09-07 11:07:04  更:2021-09-07 11:08:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 2:34:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码