JAVA线程(二)

本文深入探讨了线程同步的重要性及其实现方式,包括使用synchronized关键字进行锁操作的方法,并介绍了线程的基本方法如sleep(), wait(), notify(), join()等。

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

一,线程同步问题
1.Why?
为什么要线程同步?
线程对象(Runnable)中定义了全局变量,run方法会修改该变量时,如果有多个线程同时使用该线程对象,那么就会造成全局变量的值被同时修改,造成错误。看一下下面的代码:

public class CopyOfAccount implements Runnable{
    private int sum = 8000;// 钱
    @Override
    public void run() {
        // 取钱
        withdraw();
    }

    public void withdraw() {
        System.out.println("进入了withdraw");
            if (5000 <= sum) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 取钱
                sum = sum - 5000;
                System.out.println("吐5000");
            } else {
                System.out.println("余额不足");
            }
    }

    public static void main(String[] args) {
        CopyOfAccount r = new CopyOfAccount();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("账户余额" + r.getSum());

    }
    public int getSum() {
        return sum;
    }
    public void setSum(int sum) {
        this.sum = sum;
    }

}

输出结果:
这里写图片描述
sum=8000是Runable中的全局变量,造成了全局变量同时修改的错误。

2.What?
多个线程之间共享变量造成上述的问题,解决办法加锁。
java jdk给我们提供了synchronized关键字
使用synchronized锁定方法或代码块,同一时间内只能有一个线程访问该方法或代码块.
注:synchronized是一个非常重要的关键字。它的原理和数据库中事务锁的原理类似。我们在使用过程中,应该尽量缩减synchronized覆盖的范围,原因有二:1)被它覆盖的范围是串行的,效率低;2)容易产生死锁。

3.How?
我们看一下上述代码的修改方案:
第一种解决办法synchronized修饰代码块

public class Account implements Runnable{
    private int sum = 8000;// 钱
    @Override
    public void run() {
        // 取钱
        withdraw();
    }
    // 一.同步方法
    // 原理: 锁定当前对象 拿当前对象作为一把锁 this 1 开 0关
    // JVM虚拟机先判断这把锁是开或者是关
    // 两个的锁 都是 this 这两个this是同一把锁吗
    // this 指向的是 正在调用该方法的对象
    public void withdraw() {
        System.out.println("进入了withdraw");
// 左边这个线程 一开始锁是开着的 能调用withdraw 进来马上锁上 1------->0
        System.out.println(this);
        synchronized (this) { //1 打开
            //1------->0(锁上)
            if (5000 <= sum) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
                // 取钱
                sum = sum - 5000;
                System.out.println("吐5000");
            } else {
                System.out.println("余额不足");
            }
            //0-------->1 打开...
        }
        // 方法执行完了 在方法执行完毕后 开锁 ---->1

    }
    public int getSum() {
        return sum;
    }
    public void setSum(int sum) {
        this.sum = sum;
    }

}

运行结果:
这里写图片描述

第二种解决办法:synchronized修饰方法

public synchronized void withdraw() {}

第三种办法:当然把线程共享的全局变量改成不共享的局部变量

二,线程的基本方法:
1.sleep():
  在指定时间内让当前正在执行的线程暂停执行,但不会释放”锁标志”。不推荐使用。
  sleep()使当前线程进入阻塞状态,在指定时间内不会执行。

2.wait()方法
  在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的”锁标志”,从而使别的线程有机会抢占该锁。
  当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
 
3.notify(),notifyAll()
  唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
  
4.yield方法
  暂停当前正在执行的线程对象。
  yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
  yield()只能使同优先级或更高优先级的线程有执行的机会。

5.join方法
  等待该线程终止。
  等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
这里写图片描述

public class TestJoin {
    public static void main(String[] args) {
        MyRunable mr=new MyRunable();
        Thread t1 = new Thread(mr);
        t1.start();
        try {
            System.out.println("t1将调用join方法");
            t1.join();// 线程合并 看起来的效果 相当于 方法调用
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我是main线程");
    }
    static class MyRunable implements Runnable {
        @Override
        public void run() {
            for(int i=0;i<1000;i++){
                System.out.println("i="+i);
            }
        }
    }
}

6.isAlive()
判断线程是否还”活”着(就绪、运行、阻塞),即线程是否还未终止
活着(就绪,运行,阻塞)返回true
新建(start之前),结束:返回false

7.getPriority() 获得线程的优先级数值
setPriority() 设置线程的优先级数值

sleep()和wait()区别总结一下

相同点:
都是从运行状态—-> 阻塞状态
不同点:
1.解除阻塞状态的时机不一样

(1)sleep 睡醒了 自动解除阻塞
(2)wait 当调用this.notify()方法的时候 把阻塞的线程唤醒

  1. 是否释放锁

sleep 不会释放锁 wait 会释放锁

三.线程打断
1.线程对象.interrupt() 方法
线程睡着抛出一个InterruptedException打断它.但这种不是很好,类似泼凉水的打断 不推荐

2.线程.stop()
相当于一棒子打死了 一般不要使用,除非线程实在关不掉,这种直接不抛异常了,直接关掉线程,这种方法是不安全也是不受提倡的。

3.return
run方法执行完了线程就结束了

4.主线程 控制 子线程结束
想要在主线程中让该线程停止,如果线程t是死循环这种情况(while(true))
1.修改线程t代码(while(flag))
2.在主线程 调用 t.flag=false;
3.t线程由于flag改为false则结束

最后总结一张图
这里写图片描述

下一篇重点:死锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值