JAVA线程

本文深入探讨Java多线程的基础概念,包括线程与进程的区别、多线程的优势及创建方式,详细解析线程的生命周期与控制方法,如join、后台线程、sleep与yield等。此外,文章还讲解了线程同步、安全问题、同步锁、死锁及线程通讯的解决策略。

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

线程

一.线程与进程

1.操作系统可以执行多个任务,每个任务通常就是一个程序,运行着的程序就是一个进程,进程中可能包含多个顺序执行流,每个顺序执行流就是一个线程
2.进程是系统进行资源分配和调度的一个独立单位
3.进程主要三个特性:
独立性:进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性:程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,进程拥有自己的声明周期和不同的状态
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响
注意:并发:同一时刻只有一条指令执行,多个进程指令快速轮换执行,并行:同一时刻,有多条指令在多个处理器上同时执行
4.一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源,编程时要确保线程不会妨碍同一进程的其他线程。
5.线程是独立运行的,它并不知道进程中其他线程的存在,线程的执行是抢占式的,可能被挂起,也可能被执行
6.一个线程可以创建和撤销另一个线程,同一个线程中的多个线程之间可以并发执行

二.多线程优势

1.进程间不能共享内存,但线程之间共享内存非常容易
2.创建进程,系统要重新分配系统资源,代价比线程大
3.JAVA内置多线程功能支持

三.线程创建方式

1.继承Thread

使用继承Thread类的方法创建线程类,多条线程之间无法共享线程类的实例变量

public class DemoByThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("i = " + i + "-->线程名:"+getName());
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程名:"+ Thread.currentThread().getName());
            if(i == 5){
                new DemoByThread().start();
                new DemoByThread().start();
            }
        }
    }
}

2.实现Runnable

public class DemoByRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("i = " + i + "-->线程名:"+Thread.currentThread().getName());
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程名:"+ Thread.currentThread().getName());
            if(i == 5){
                new DemoByThread().start();
                new DemoByThread().start();
            }
        }
    }
}

两种方式区别
截图

四.线程生命周期

在这里插入图片描述
图源
1.新建:使用new()关键字创建线程,虚拟机为其分配了内存并初始化变量值
2.就绪:调用start()方法,虚拟机为其创建方法调用栈和程序计数器,表示可以运行了
阻塞到就绪:
在这里插入图片描述
3.运行:执行run()方法,线程不会一直都处于运行状态,执行一段时间后,系统会收回线程所占用的资源,根据抢占式策略,让其他线程获得执行机会,收回后,系统会考虑优先级选线程
4.阻塞:执行sleep()或yield()方法主动放弃线程占用的资源,则进入此状态
运行到阻塞:
在这里插入图片描述
5.死亡:run()方法执行完,用isAlive()查看是否已死亡,死亡和创建时isAlive都是false,其他三种都是true
截图

注意:
不要对已经处于启动状态的线程再次调用start方法,否则将引起IllegalThreadStateException
也不要对处于死亡状态的线程调用start()方法
当主线程结束时候,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和 主线程相同的地位,它不会受主线程的影响

五.线程控制

join

当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join方法加入的join线程完成为止
换句话说,就是当前线程加进来一个子线程,先把这个子线程运行完后,才返回来执行当前线程的逻辑。

public class ThreadJoin extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程 "+getName()+"  计数"+i);
        }
    }

    public static void main(String[] args) throws Exception{

        for (int i = 0; i < 30; i++) {
            System.out.println("主线程 " + Thread.currentThread().getName() + "计数"+ i);
            if(i == 15){
                ThreadJoin tj = new ThreadJoin();
                tj.start();//调用join之前,必须要调用start,不然线程是不会执行的
                tj.join();
            }
        }
    }
}

后台线程(守护线程)

后台运行,为其他线程提供服务。JVM垃圾回收线程就是后台线程
特征:如果所有的前台线程都死亡,后台线程会自动死亡,不过死亡不会立即死亡
注意:前台线程死亡后,JVM会通知后台线程死亡,但从它接收指令到作出响应,需要一定时间。
setDaemon必须在start方法之前调用

public class ThreadDaemon extends Thread{

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

    public static void main(String[] args) {
        ThreadDaemon t = new ThreadDaemon();
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        //当程序执行到这里,前台线程(main)执行结束,后台线程(t)也会随之结束

    }
}

sleep

当线程使用sleep方法阻塞后,在其sleep时间段内,该线程不会获得执行的机会,即使系统中没有其他可运行的线程,处于
sleep中的线程也不会运行
注意:sleep只阻止当前线程的运行,而其他线程是不受影响的,会继续执行下去

public class ThreadSleep extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 25; i++) {
            try {
                sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(" 子线程sleep300s-->"+i+"-->时间:"+showDate());
        }
    }

    public static void main(String[] args) {
        new ThreadSleep().start();
        for (int i = 0; i < 50; i++) {
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(" 主线程sleep100s-->"+i+"-->时间:"+showDate());
        }
    }

    public static String showDate(){

        Date dNow = new Date( );
        SimpleDateFormat ft =
                new SimpleDateFormat (" YYYY/MM/dd HH:mm:ss.SSS");
        return ft.format(dNow);
    }
}

yield

让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将线程转入就绪状态。
让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用yeild方法暂停之后,线程
调度器又将其调度出来重新执行。
实际上,某个线程调用了yield方法暂停后,只有优先级与当前线程相同或更高才有机会获得执行机会
注意:实际代码验证中,线程优先级不一定高的优先级一定先执行,只表示执行机会比较多而已

public class ThreadYield extends Thread{

    public ThreadYield() {
    }

    public ThreadYield(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(getName()+"**"+i);
            Thread.yield();
        }
    }

    public static void main(String[] args) {

        ThreadYield tl = new ThreadYield("低级");
        tl.setPriority(Thread.MIN_PRIORITY);
        tl.start();

//        ThreadYield tm = new ThreadYield("中级");
////        tm.setPriority(Thread.NORM_PRIORITY);
////        tm.start();

        ThreadYield tg = new ThreadYield("高级");
        tg.setPriority(Thread.MAX_PRIORITY);
        tg.start();

    }


}

在这里插入图片描述

优先级

不同系统提供的线程优先级不一样,我们应该尽量避免直接为线程执行优先级,而应使用
JAVA那三个静态常量来设置优先级,这样才可以保证程序最好的可移植性

六.线程同步

安全问题

在这里插入图片描述

账户类

public class Account {

    private String accountNo;//账户编号
    private double balance;//金额

    public Account() {
    }

    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Objects.equals(accountNo, account.accountNo);
    }

    @Override
    public int hashCode() {

        return Objects.hash(accountNo);
    }
}

取款类

public class DrawThreadNoSafe extends Thread{

    private Account account;
    private double drawAmount;

    public DrawThreadNoSafe(String name, Account account, double drawAmount) {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    @Override
    public void run() {
        if(account.getBalance() >= drawAmount){
            System.out.println(this.getName()+"取钱成功:取出"+drawAmount+"元");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.setBalance(account.getBalance()-drawAmount);//这里会有同步问题,account里的金额可能被其他线程抢先一步修改
            System.out.println("当前余额为: " + account.getBalance());
        }else{
            System.out.println(this.getName()+"余额不足,取款失败");
        }
    }
}

测试类

public class TestThread {

    public static void main(String[] args) {
        Account account = new Account("A账户", 1000);
        new DrawThreadNoSafe("甲取款",account,600).start();
        new DrawThreadNoSafe("乙取款",account,600).start();
    }
}

结果
在这里插入图片描述

synchronized

同步块

在这里插入图片描述
注意:任何时刻只有一条线程可以或得同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定
通常推荐使用可能被并发访问的共享资源充当同步监视器
取款类

    public void run() {
        synchronized (account){//account是两个线程都要操作的资源,可以把它作为同步锁来控制线程访问
            if(account.getBalance() >= drawAmount){
                System.out.println(this.getName()+"取钱成功:取出"+drawAmount+"元");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("当前余额为: " + account.getBalance());
            }else{
                System.out.println(this.getName()+"余额不足,取款失败");
            }
        }
    }
同步方法

在这里插入图片描述

取款类

    public void run() {
        account.draw(drawAmount);
    }

账户类

    public synchronized void draw(double drawAmount){
        if(this.getBalance() >= drawAmount){
            System.out.println(Thread.currentThread().getName()+"取钱成功:取出"+drawAmount+"元");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(this.getBalance()-drawAmount);
            System.out.println("当前余额为: " + this.getBalance());
        }else{
            System.out.println(Thread.currentThread().getName()+"余额不足,取款失败");
        }
    }

在这里插入图片描述

同步监视器释放

在这里插入图片描述

volatile

可见性

关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值
在这里插入图片描述
线程打印类

public class PrintString implements Runnable{

    private static volatile boolean continuePrint = true;

    public boolean isContinuePrint() {
        return continuePrint;
    }

    public void setContinuePrint(boolean continuePrint) {
        this.continuePrint = continuePrint;
    }

    public void printStringMethod(){
        try {
            while (continuePrint == true){//这里面不要有代码,不然运行模拟不出来
            }
            System.out.println("run printStringMethod  " + Thread.currentThread().getName());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void run() {
        printStringMethod();
    }
}

测试类

    private static void test() {
        PrintString ps = new PrintString();//启动一个子线程打印
        new Thread(ps).start();
        System.out.println("主线程要关闭子线程了"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ps.setContinuePrint(false);//关闭子线程

    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

非原子性

当多线程对同一个资源进行操作时,volatile并不能保证该资源操作是原子性操作,比如都对count进行相加操作
线程计算类

public class ThreadI extends Thread{
    volatile public static int count;//公共资源
    private static void addCount(){//每个线程执行100次相加
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count "+count);
    }

    @Override
    public void run() {
        addCount();
    }
}

测试类

    private static void test() {

        ThreadI[] ti = new ThreadI[100];
        for (int i = 0; i < 100; i++) {
            ti[i] = new ThreadI();
        }
        for (int i = 0; i < 100; i++) {
            ti[i].start();
        }

    }

在这里插入图片描述
在这里插入图片描述
由结果可知,100个线程计算100次,总的结果却不是10000
这是因为按正常逻辑来说,第一个线程加到100,第二个线程从100基础上再加到200,但是,当执行count++的时候,有些线程才加到比如288的时候,马上另外一个线程就会读取count进行相加,这个时候的值为288,那么就从288开始相加,而不是从300相加,这就不能保证每个线程执行完后再执行下一个线程的原子性操作

同步锁Lock

在这里插入图片描述

账户类

    public class Account {

    private String accountNo;//账户编号
    private double balance;//金额
    private final ReentrantLock lock = new ReentrantLock();
    ...
    public void draw(double drawAmount){
        lock.lock();//增加同步锁
        try {
            if(this.getBalance() >= drawAmount){
                System.out.println(Thread.currentThread().getName()+"取钱成功:取出"+drawAmount+"元");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setBalance(this.getBalance()-drawAmount);
                System.out.println("当前余额为: " + this.getBalance());
            }else{
                System.out.println(Thread.currentThread().getName()+"余额不足,取款失败");
            }

        }finally {
            lock.unlock();
        }
    }

在这里插入图片描述

注意:不管用Lock还是synchronized,作用的对象都是需要操作公共资源的那个对象,比如上面的Account,是一个公共资源,多个线程都会访问这个Account,那么就需要对这个对象进行加锁处理来达到同步效果!若在非公共资源上加锁,那么锁只作用于当前类,每个线程都会访问新的当前类,从而不能达到同步效果,比如把锁加在DrawThreadNoSafe 类上

死锁

在这里插入图片描述

资源A类

public class A {

    public synchronized void before(B b){
        System.out.println("当前线程"+Thread.currentThread().getName()+"访问了A的Before");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程"+Thread.currentThread().getName()+"即将访问B的last方法");
        b.last();//b.before的时候b对象已经加锁,这里阻塞
    }

    public synchronized void last(){
        System.out.println(Thread.currentThread().getName()+"正在访问A的last方法");
    }

}

资源B类

public class B {

    public synchronized void before(A a){
        System.out.println("当前线程"+Thread.currentThread().getName()+"访问了B的Before");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程"+Thread.currentThread().getName()+"即将访问A的last方法");
        a.last();//a.before的时候a对象已经加锁,这里阻塞

    }

    public synchronized void last(){
        System.out.println(Thread.currentThread().getName()+"正在访问B的last方法");
    }
}

线程调用类

public class ThreadDead extends Thread{
    A a = new A();
    B b = new B();

    public void init(){
        Thread.currentThread().setName("mian主线程调用");
        a.before(b);
        System.out.println("mian主线程调用结束");
    }

    @Override
    public void run() {
        Thread.currentThread().setName("ThreadDead线程调用");
        b.before(a);
        System.out.println("ThreadDead线程调用结束");
    }

    public static void main(String[] args) {
        ThreadDead td = new ThreadDead();
        //启动两个线程进行调用
        td.start();//子线程
        td.init();//主线程

    }
}

注意:由于Thread类的suspend也很容易导致死锁,故Java不再推荐使用该方法来暂停线程的执行

七.线程通讯

存钱取钱问题(object方法控制)

在这里插入图片描述

账户类

public class Account {
    private String accountNo;
    private double balance;
    private boolean flag = false;

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public Account(double balance) {
        this.balance = balance;
    }

    public synchronized void draw(double drawAmount) {//取钱
        try {
            if (!flag) {
                wait();
            } else {
                System.out.println(Thread.currentThread().getName() + "取钱成功:取出" + drawAmount + "元。");
                this.setBalance(this.getBalance() - drawAmount);
                System.out.println("当前余额为:" + this.getBalance());
                flag = false;//false表示钱已经取了,不能再取了
                notifyAll();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void deposit(double drawAmount){//存钱
        try {
            if(flag){
                wait();
            }else{
                System.out.println(Thread.currentThread().getName()+"存钱成功。");
                this.setBalance(drawAmount);
                System.out.println("当前余额为:"+this.getBalance());
                flag = true;//通知钱已经存了
                notifyAll();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

存款类

public class DepositThread extends Thread{

    private Account account;
    private double drawAmount;

    public DepositThread(String name,Account account, double drawAmount) {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            account.deposit(drawAmount);
        }
    }
}

取款类

public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;

    public DrawThread(String name,Account account, double drawAmount) {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            account.draw(drawAmount);
        }

    }
}

模拟用户类

public class TestAccount {
    public static void main(String[] args) {
        Account account = new Account("A账户",1000);
        new DrawThread("甲取款", account, 800).start();
        new DepositThread("A存款", account, 800).start();
        new DepositThread("B存款", account, 800).start();
        new DepositThread("C存款", account, 800).start();
    }
}

使用条件变量控制协调(Lock)

在这里插入图片描述
前面部分类无需修改,这里只修改账户类即可

账户类

public class Account {
    private String accountNo;
    private double balance;
    private boolean flag = false;

    private final Lock lock = new ReentrantLock();//同步锁
    private final Condition con = lock.newCondition();//同步锁条件

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public Account(double balance) {
        this.balance = balance;
    }

    public void draw(double drawAmount) {//取钱
        lock.lock();
        try {
            if (!flag) {
                con.await();
            } else {
                System.out.println(Thread.currentThread().getName() + "取钱成功:取出" + drawAmount + "元。");
                this.setBalance(this.getBalance() - drawAmount);
                System.out.println("当前余额为:" + this.getBalance());
                flag = false;//false表示钱已经取了,不能再取了
                con.signalAll();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void deposit(double drawAmount){//存钱
        lock.lock();
        try {
            if(flag){
                con.await();
            }else{
                System.out.println(Thread.currentThread().getName()+"存钱成功。");
                this.setBalance(drawAmount);
                System.out.println("当前余额为:"+this.getBalance());
                flag = true;//通知钱已经存了
                con.signalAll();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

八.线程组与线程池

后续更新…

参考:
深入理解Java并发之synchronized实现原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值