Java多线程学习笔记

本文详细介绍了Java多线程的概念、应用场景、线程生命周期、线程优先级、创建线程的多种方式,以及线程同步与锁的重要性,深入探讨了线程的管理、线程安全问题和死锁现象,是Java多线程学习的全面指南。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、多线程是什么?

介绍多线程之前要介绍线程,介绍线程则离不开进程。

进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元,一个进程一直运行,直到所有的非守护线程都结束运行后才能jie。

线程:就是进程中的一个独立控制单元,线程在控制这进程的执行。一个进程中至少有一个线程,线程不能独立存在,必须是进程的一部分。

多线程:一个进程中有多个线程并发执行,每条线程并行执行不同的任务。

二、为什么要用多线程?

①、为了更好地利用CPU资源,如果只有一个线程,则第二个任务必须等待第一个任务结束后才能进行;如果使用多线程,则在主线程执行任务的同时可以执行其他任务,不需要等待。

②、进程之间不能共享数据,线程可以。

③、系统创建进程需要为该进程重新分配资源,创建线程代价比较小。

④、Java语言内置了多线程功能支持,件货了Java多线程编程。

三、线程的生命周期介绍

线程生命周期:新建-->就绪-->运行-->等待、阻塞、睡眠-->终止

新建状态:从新建一个线程对象到程序Start()这个线程之间的状态都是新建状态。

就绪状态:线程对象调用了Start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度。

运行状态:就绪状态下的线程在获取CPU资源后就可以执行方法run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞、死亡三种状态。

等待、阻塞、睡眠状态:在一个线程执行了睡眠方法sleep()、挂起方法suspend()等方法后会失去所占有的资源,从而进入阻塞状态;在睡眠结束后或获得设备资源后可重新进入就绪状态。

①等待阻塞:运行状态中的线程执行wait()方法,是现成进入到等待阻塞状态

②同步阻塞:线程在获取synchronized同步锁失败(因为同步锁被其他线程占用)

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

终止状态:run()方法执行完成后或者发生其他终止条件时就会切换到终止状态。

线程生命周期图:

四、线程优先级

每一个Java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。

Java线程的优先级是一个整数,取值范围是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5)。

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程的执行顺序,而且非常依赖于平台。

五、线程的创建

Java提供三种创建线程的方法:通过继承Thread类本身、通过实现Runnable接口、通过实现Callable接口创建Future创建线程

①通过继承Thread类本身

定义一个类继承Thread、重写Thread类中的run()方法(将自定义代码存储在run()方法,让线程运行;该方法是新线程的入口)、调用线程的start()方法(该方法有两步:启动线程、调用run()方法)。

该方法实现多线程的本质上是实现了Runnable接口。

/**Thread创建线程代码实现*/
/**主类*/
package com.marshal.org.main;

import com.marshal.org.threadutils.thread.MyThread;

public class ThreadMain {
    public static void main(String[] args) {
        //创建两个线程
        MyThread myThreadOne = new MyThread("线程一");
        MyThread myThreadTwo = new MyThread("线程二");
        //执行start()方法启动线程,调用run()方法也会执行,但是会以单线程方式执行
        myThreadOne.start();
        myThreadTwo.start();
        //主线程
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程main" + ":run " + i);
        }
    }
}
/**继承Thread的子类*/
package com.marshal.org.threadutils.thread;

public class MyThread extends Thread {
    //设置线程名称
    public MyThread(String name) {
        super(name);
    }
    //重写run()方法
    @Override
    public void run() {
        for (int j = 0; j < 4; j++) {
            //获取当前线程的静态对象
            Thread thread = currentThread();
            System.out.println("当前线程的静态对象" + thread);
            //获取当前线程的ID、线程名、线程状态、以及堆栈追踪信息
            System.out.println("当前线程ID: " + j + "--" + this.getId() + ", 当前线程名字: " + this.getName() + ", 当前线程状态: " + this.getState() + ", 当前线程堆栈追踪信息: " + this.getStackTrace());
        }
    }
}

运行结果:

Thread类的对象的重要方法
public void start()使该线程开始执行;Java虚拟机调用该线程的run()方法
public void run()

如果使用该线程是使用独立的Runnable运行对象构造的,

则调用该Runnable对象的run()方法;

否则,该方法不执行任何操作并返回

public final void setName(String name)修改线程名称,使之与参数name相同
public final void setPriority(int priority)修改线程优先级
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程
public final void join(long millisec)等待该线程终止的时间最长为millis毫秒
public void interrupt()中断线程
public final boolean isAlive()判断线程是否处于活跃状态
Thread类的静态方法
public static void yield()暂停当前正在执行的线程对象,并执行其他线程
public static void sleep(long millisec)

在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),

此操作受到操作系统计时器和调度程序精度和准确性影响

public static boolean holdsLock(Object obj)当且仅当当前线程在指定的对象上保持监视器琐时,才返回true
public static Thread currentThread()返回对当前正在执行的线程对象的引用
public static void dumpStack()将当前线程的堆栈跟踪打印至标准错误流

 

 

 

 

 

 

 

②通过实现Runnable接口

接口应该由那些打算通过某一线程执行其实例的类来实现(implements)。类必须定义一个成为run()的无参方法。

定义类实现Runnable接口、实现Runnable接口中的run()方法,将线程要运行的代码放在该run()方法中、通过Thread类创建线程对象、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。自定义的run()方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run()方法就要先明确run()方法所属对象、调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法。

/**代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;

public class RunnableMain {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        //创建线程对象
        Thread threadOne = new Thread(myRunnable);
        Thread threadTwo = new Thread(myRunnable);
        Thread threadThree = new Thread(myRunnable);
        threadOne.start();
        threadTwo.start();
        threadThree.start();
    }
}


package com.marshal.org.threadutils.runnable;

//定义类实现Runnable接口
public class MyRunnable implements Runnable{
    //实现Runnable接口的抽象方法run()方法, 在内部完成逻辑执行
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果:

③通过实现Callable接口创建FutureTask创建线程

创建Callable接口的实现类,并实现call()方法,该方法将作为线程执行体,且具有返回值。

创建Callable接口实现类的实例,使用FutureTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值。

使用FutureTask对象作为Thread对象启动线程。

调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

/**代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.callable.MyCallableFuture;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableFutureMain {
    public static void main(String[] args) {
        MyCallableFuture myCallableFuture = new MyCallableFuture();
        FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(myCallableFuture);
        for (int i = 0; i < 100; i++) {
            if (i==30){
                Thread thread = new Thread(integerFutureTask, "子线程");
                thread.start();
            }
        }

        try {
            System.out.println("子线程的返回值为:" + integerFutureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}


package com.marshal.org.threadutils.callable;
import java.util.concurrent.Callable;

public class MyCallableFuture implements Callable {
    @Override
    public Object call() throws Exception {
        int i = 0;
        for (; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+ "==" + Thread.currentThread().getState());
        }
        return i;
    }
}

运行结果:

六、继承Thread类、实现Runnable接口、实现Callable接口的区别。

①继承Thread类:线程代码存放在Thread子类run()方法中。

优势:

      编写简单,可以直接使用this.getName()获取当前线程,不必使用Thread.currentThread()方法。

劣势:

      已经继承了Thread类,无法在继承其他类。

②实现Runnable接口:线程代码存放在接口的子类的run()方法中。

优势:

      避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

劣势:

      比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。

③实现Callable接口,创建Future对象

优势:

      有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

劣势:

       比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。

建议:使用实现接口的方式创建多线程。

七、线程状态管理

①线程睡眠--sleep

线程的原因:线程执行的太快,需要强制执行到下一个线程。

线程睡眠的方法:

      sleep(long millis)在指定的毫秒数内,让正在执行的线程休眠。

      sleep(long millis, int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。

/**线程睡眠代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;

public class RunnableMain {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        //创建线程对象
        Thread threadOne = new Thread(myRunnable);
        threadOne.start();
    }
}


package com.marshal.org.threadutils.runnable;

//定义类实现Runnable接口
public class MyRunnable implements Runnable{
    //实现Runnable接口的抽象方法run()方法, 在内部完成逻辑执行
    private int time =10;
    @Override
    public void run() {
        while (true){
            if (time>=0){
                System.out.println(Thread.currentThread().getName() + ":" + time--);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

扩展:

      Java线程调度是多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管怎样编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不能靳准的去控制,所以如果调用Thread.sleep(1000)是线程睡眠一秒,可能结果会大于一秒。

②线程让步--yield

该方法和sleep()方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法之后暂停,但之后调度器又将其调度出来重新进入到运行状态。

/**线程让步代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;

public class RunnableMain {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        //创建线程对象
        Thread threadOne = new Thread(myRunnable, "threadOne吃完还剩:");
        Thread threadTwo = new Thread(myRunnable, "threadTwo吃完还剩:");
        Thread threadThree = new Thread(myRunnable, "threadThree吃完还剩:");
        threadOne.start();
        threadTwo.start();
        threadThree.start();
    }
}

package com.marshal.org.threadutils.runnable;

//定义类实现Runnable接口
public class MyRunnable implements Runnable{
    //实现Runnable接口的抽象方法run()方法, 在内部完成逻辑执行
    private int time =10;
    @Override
    public void run() {
        while (true){
            if (time>=0){
                System.out.println(Thread.currentThread().getName() + ":" + time-- + "个瓜");
                if (time % 2 ==0){
                    System.out.println("线程:" + Thread.currentThread()+ "让步");
                    Thread.yield();
                }
            }
        }
    }
}

运行结果:

线程睡眠sleep和线程让步yield的区别

sleep方法声明InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。

sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后,是直接进入就绪状态。

③线程合并--join

当B线程执行到A线程的join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。

join可以用来临时加入线程执行。

/**线程未合并代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;

public class RunnableMain {
    public static void main(String[] args) {
        MyRunnable myRunnableOne = new MyRunnable("A");
        MyRunnable myRunnableTwo = new MyRunnable("B");
        MyRunnable myRunnableThree = new MyRunnable("C");
        //创建线程对象
        Thread threadOne = new Thread(myRunnableOne);
        Thread threadTwo = new Thread(myRunnableTwo);
        Thread threadThree = new Thread(myRunnableThree);
        threadOne.start();
        threadTwo.start();
        threadThree.start();
    }
}


package com.marshal.org.threadutils.runnable;

//定义类实现Runnable接口
public class MyRunnable implements Runnable{
    private String name;

    public MyRunnable(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(name + "--" + i);
        }
    }
}

运行结果:

/**线程合并代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;

public class RunnableMain {
    public static void main(String[] args) {
        MyRunnable myRunnableOne = new MyRunnable("A");
        MyRunnable myRunnableTwo = new MyRunnable("B");
        MyRunnable myRunnableThree = new MyRunnable("C");
        //创建线程对象
        Thread threadOne = new Thread(myRunnableOne);
        Thread threadTwo = new Thread(myRunnableTwo);
        Thread threadThree = new Thread(myRunnableThree);
        threadOne.start();
        try {
            threadOne .join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadTwo.start();
        threadThree.start();
    }
}


package com.marshal.org.threadutils.runnable;

//定义类实现Runnable接口
public class MyRunnable implements Runnable{
    private String name;

    public MyRunnable(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(name + "--" + i);
        }
    }
}

运行结果:

补充.:

线程threadOne.join()需要等待threadOne .start()执行之后才有效果,如果threadOne .join()放在threadTwo.start()之后的话,threadOne 和threadTwo仍然会交替执行,threadThree会在前两个线程交替执行完成之后再执行。

④停止线程

原stop方法因为有缺陷已经停用了,那么现在该如何停止线程?现在分享一种让run()方法结束。

开启多线程运行,运行的代码通常时循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。

/**线程停止代码实现*/

⑤特殊情况

当线程处于了冻结状态,就不会读取到标记,也就不会结束。当没有指定方法让冻结的线程恢复到运行状态时,我们需要对冻结的状态进行清除,也就是强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

Thread类提供该方法: interrupt()

      如果线程在调用Object类的wait()、wait(long)、wait(long,int)方法,或者该类的join()、join(long)、join(long、int)、sleep(long)或sleep(long、int)方法过程中受阻,则其中断状态将被清除,还将收到一个InterruptedException。

八、线程优先级设置

每个线程执行时都有一个优先级的属性,优先级搞得线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。

优先级常量:MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY

/**线程优先级代码实现*/

九、线程同步与锁

①为什么要进行线程同步

Java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如对其进行增删该查操作),会导致数据不准确,而且相互之间产生冲突。所以加入同步锁以避免该线程在没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性。

②线程不同步会发生什么问题

不进行线程同步,会造成多个线程去操作同一个资源变量。

/**代码实现*/

③线程同步方法

同步方法一:

使用synchronize关键字修饰方法。因为每个Java对象都有一个内置锁,当用synchronize关键字修饰方法时内置锁会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会处于阻塞状态。

 

同步方法二:

同步代码块:就是拥有synchronize关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

 

如果同步函数被静态修饰后,使用的锁是什么锁?静态方法中不能定义this!

静态内存:内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class 该对象类型是Class.

所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,类名.class。

同步的前提:

必须有两个或者两个以上的线程。

必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。

只能同步方法,不能同步变量和类。

不必同步类中所有方法,类可以拥有同步和非同步的方法。

如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象)类中的任何一个同步方法。

线程睡眠时,他所持有的任何锁都不会释放。

同步锁的优缺点:

优势:解决多线程安全问题。

劣势:多线程需要判断,消耗资源降低效率。

如何找问题?

  1、明确哪些代码是多线程运行代码。

  2、明确共享数据。

  3、明确多线程运行代码中哪些语句是操作共享数据的。

十、死锁

进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以他们就相互等待对方占有的资源释放,所以也就产生了一个循环等待死锁。

/**死锁代码实现*/

死锁形成的必要条件:

互斥条件:资源不能被共享,只能被同一个进程使用。

请求与保持条件:已经得到资源的进程可以申请新的资源。

非剥夺条件:已经分配的资源不能从相应的进程中强制剥夺

循环等待条件:系统中若干进程形成环路,该环路中每个进程都在等待相邻进程占用的资源。

十一、线程间的通讯、等待唤醒机制

 

十二、线程池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值