Java——多线程基础

Java多线程基础

线程概念

  1. 线程由进程创建,是进程的一个实体

  2. 一个进程可以拥有多个线程

  3. 单线程:同一时刻,只允许执行一个线程

  4. 多线程:同一时刻,可以执行多个线程,如一个QQ进程,可以打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件

  5. 如何通过Java程序查询电脑CPU个数:

    		Runtime runtime = Runtime.getRuntime();
            int cpuNums = runtime.availableProcessors();
            System.out.println(cpuNums);
    

线程的使用

线程的创建

Java中创建线程的两种方法:

  1. 继承 Thread 类 ,重写 run 方法
  2. 实现 Runnable 类,重写 run方法

继承 Thread

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        cat.start();
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程 i="+i);
            Thread.sleep(1000);
        }
    }
}
//当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread{

    @Override
    public void run() {   //重写run方法,写上自己的业务逻辑
        int times = 0;
        while (true) {
            System.out.println("喵喵,我是小猫咪" + (++times));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (times == 80){
                break;
            }
        }
    }
}
  1. Thread.sleep() 方法用于使当前执行的线程休眠(暂停执行)一段时间。它接受一个以毫秒为单位的参数,表示线程要休眠的时间长度。在休眠期间,线程不会执行任何代码。
  2. 当一个类继承了Thread类,该类就可以当作线程使用
  3. cat.start() 启动线程,而如果cat.run()则不会开辟新线程,而是在主线程中调用run方法
  4. 当 main 线程启动了一个子线程,主线程不会阻塞,会继续执行
  5. 使用 JConsle 可以监控线程执行情况
    • 将程序运行起来,然后再 Terminal 中输入 jconsole,连接当前的类,查看线程即可
  6. 所有线程都结束,进程才会结束

实现 Runnable 接口:

  1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承 Thread 类方法创建线程显然是不可能的。

  2. Java设计者提供了另一个方式创建线程,就是通过实现 Runnable 接口来创建线程

  3. public class Thread02 {
        public static void main(String[] args) {
            Dog dog = new Dog();
            Thread thread = new Thread(dog);
            thread.start();
        }
    }
    
    class  Dog implements Runnable{
        int count = 0;
        @Override
        public void run() {
            while(true){
                System.out.println("小狗汪汪叫" + (++count));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.getMessage();
                }
            }
        }
    }
    
  4. start() 方法只能由 Thread 来调用,所以需要将实现了 Runnable 的类传入 Thread 然后再调用 start() 开启线程

继承 Thread和实现 Runnable 接口的区别

  1. 从Java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
线程终止

Java线程终止有两种方式:

  1. 当线程完成任务后,会自动退出
  2. 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
        Thread.sleep(10000);
        t.setLoop(false);
    }
}

class T extends Thread{
    int count = 0;
    private boolean loop = true;
    @Override
    public void run() {
        while(loop){
            System.out.println("这是一个线程" + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    public void setLoop(boolean loop){
        this.loop = loop;
    }
}
线程常用方法

常用方法一:

  1. setName //设置线程名称,使之与参数name相同
  2. getName //返回该线程的名称
  3. start //使线程开始执行;Java虚拟机底层调用该线程的start0方法
  4. run //调用线程对象的run方法
  5. setPriority //更改线程的优先级
  6. getPriority //获取线程的优先级
  7. sleep //在指定地点毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt //中断线程

细节说明:

  1. start 底层会创建新的线程,调用run,而run就是一个简单的方法调用,不会启动新线程
  2. 线程优先级范围 常用:MIN_PRIORITY = 1, NORM_PRIORITY = 5, MAX_PRIORITY = 10;
  3. interrupt 是中断线程,并没有结束线程,所以一般用于中断正在休眠线程
public class ThreadMethod {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("jack");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();

        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi");
        }
        System.out.println("jack线程的优先级" + t.getPriority());
            t.interrupt();
        //主线程经过五秒后,主动中断子线程的休眠,使其继续工作
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "正在工作");
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠中");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被interrupt");
            }
        }
    }
}

常用方法二:

  1. yield :线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  2. join :线程的插队。插队的线程一旦插队成功,则肯定先执行完插入到线程所有的任务

让子线程和主线程同时每隔一秒工作一次,共工作20次,当主线程工作5次后,让子线程完成后面所有工作,然后主线程再继续工作。 join()插队一定可以,yield()礼让不一定

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        TT tt = new TT();
        tt.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "正在工作" + i);
            Thread.sleep(1000);
            if (i ==5){
                tt.join();
                //Thread.yield();
            }
        }

    }
}
class TT extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "正在工作" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

守护线程:(setDaemon)

用户线程:也叫工作线程,当线程的任务执行完或以通知的方式结束

守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

当用户线程结束后,如果子线程还没有执行完,那么将会一直执行下去,这时可以将子线程设置为守护线程,tt.setDaemon(true); ,那么当用户线程结束时,子线程也将随之而结束。

常见的守护线程:垃圾回收机制

线程的生命周期

线程七大状态

image-20231129181012565

其中,Runnable 状态可以细分为 ReadyRunning,就绪态和运行态

使用 t.getState() 可以查看当前线程所处状态

线程同步机制
  1. 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
  2. 也可以理解为:线程同步,即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对内存地址进行操作

同步具体方法——Synchronized

  1. 同步代码块

    synchronized(对象){ //得到对象的锁,才能操作同步代码

    ​ // 需要被同步的代码块

    }

  2. synchronized 还可以放在方法声明中,表示整个方法为同步方法

    public synchonized void m (String name){

    ​ //需要被同步的代码

    }

public class syn_ {
    public static void main(String[] args) {
        SellTicket3 sellTicket3 = new SellTicket3();
        new Thread(sellTicket3).start();
        new Thread(sellTicket3).start();
        new Thread(sellTicket3).start();
    }
}

class SellTicket3 implements Runnable {
    private int tickets = 100;
    private boolean loop = true;

    public /*synchronized*/ void sell() {
        synchronized (this) {
            if (tickets <= 0) {
                System.out.println("售票结束....");
                loop = false;
                return;
            }
            System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数" + (--tickets));
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

互斥锁

基本介绍:

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
  3. 关键字 synchonized 来与对象的互斥锁联系。当某个对象使用 synchonized 修饰时,表明该对象在任意时刻只能由一个进程来访问
  4. 同步的局限性:导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  6. 同步方法(静态的)都锁为当前类本身

使用细节:

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的落地步骤:
    1. 需要先分析上锁的代码
    2. 选择同步代码块或同步方法
    3. 要求对个线程的锁对象为同一个即可(当使用继承Thread方法创建线程时,创建多个线程就不是同一个对象,即锁不住!)
线程的死锁
  1. 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。
public class DeadLock {
    public static void main(String[] args) {
        DeadLockDemo deadLockDemo = new DeadLockDemo(true);
        DeadLockDemo deadLockDemo2 = new DeadLockDemo(false);
        deadLockDemo.start();
        deadLockDemo2.start();
    }
}
class DeadLockDemo extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (o1){
                System.out.println(Thread.currentThread().getName() + "进入1");
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+ "进入2");
                }
            }
        }else {
            synchronized (o2){
                System.out.println(Thread.currentThread().getName() + "进入2");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName() + "进入1");
                }
            }
        }
    }
}
释放锁

下面操作会释放锁:

  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到break、return
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

下面操作不会释放锁:

  1. 当前执行同步代码块或同步方法时,程序调用*Thread.sleep()、Thread.yield()*方法,暂停当前线程的使用,不会释放锁
  2. 线程执行同步代码块时,其他线程调用了该线程的*suspend()*方法将该线程挂起,该线程不会释放锁。
  3. 注意:应尽量避免使用 suspend() 和 resume() 来控制线程
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值