Java多线程

目录

 

创建线程的方式

继承Thread类

实现runnable接口

实现Callable接口

线程的声明周期

 * 线程的几种状态:

​编辑

 线程之间的调度

调整线程优先级 :

 线程睡眠 Thread.sleep()

线程让步 yield()

3、线程加入——join(插队)

 线程同步synchronized

同步方法

同步代码块

wait()、notify()和notifyAll()


创建线程的方式

继承Thread类

(1)创建一个继承于Thread类的子类
(2)重写Thread类中的run方法,将此线程要执行的语句放在run方法里。
(3)创建Thread类的子类对象
(4)启动线程。调用start方法。会执行run方法中的语句。

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(getName() + i);
            }
        }
    }
}
class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println("奇数" + i);
            }
        }
    }
}

public class Ch01 {
    public static void main(String[] args) {
MyThread myThread = new MyThread();
        myThread.setName("线程一");
        myThread.start();
        MyThread1 myThread1 = new MyThread1();
        myThread1.setName("线程二");
        myThread1.start();
    }
}

        我们将以输出0-100的奇数与偶数为例 那为什么我们不直接利用前面面向对象所学直接 对象.run()来调用这个方法呢 其实看似调用的两个方法功能相同 实则二者原理完全不相干 start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码,start方法是来自于我们继承的Thread类的 其只能被调用一次。相当于这个线程生命周期走到尽头将无法重复调用 而run():只是封装了要被线程执行的代码,可以当成普通的成员方法被对象调用多次。

实现runnable接口

 实现Runnable(函数式)接口
(1)创建一个实现了Runnable接口的实现类
(2)实现类去实现Runnable接口中的抽象方法:run
(3)创建实现类的对象
(4)把当前Runnable接口的实现类对象包装成Thread类对象 !!!!!!

继承Thread类中我们说到start方法是来自于我们继承的Thread类的 但这次我们去查看接口Runnable里面只有抽象方法run 那我们就要比继承Thread类创建线程多一个步骤 那就是现类去实现Runnable接口中的抽象方法后将其包装成Thread类对象 查看Thread源码即可发现Thread已经帮我们写好了传入Runnable类对象的构造器 便于我们将其包装成Thread类对象


(5)通过Thread类的对象调用start方法,启动线程。会执行run方法中的语句。

public class Ch03 {
    public static void main(String[] args) {
        MyRunnable myRunnable1 = new MyRunnable();
        Thread thread1 = new Thread(myRunnable1);
        thread1.start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}

 * 对比这前面两种创建线程的方式:
 * 开发中:优先选择使用Runnable接口的方式。
 * 原因:
 * 1、实现的方式没有类的单继承的局限性
 * 2、实现的方式更适合来处理多个线程共享数据的情况
 *
 * 联系:Thread类本身就实现了Runnable接口。
 * 相同点:两种方式都需要重写run方法,将线程要执行的语句写在run方法中。

实现Callable接口

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

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

使用FutureTask对象作为Thread对象的target创建并启动新线程。

调用FutureTask对象的get()方法来获得子线程执行结束后的返回值其中

 * 实现Callable接口出现JDK5.0以后
 * 1、call方法是有返回值
 * 2、call方法是可以throws异常的
 * 3、call方法是支持泛型(JDK5以后)

package com.jsoft.afternoon;

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

/**
 * @author
 * @date 2023年10月16日 15:16:00
 */
class NumCall implements Callable {

    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100 ; i++) {
            sum += i;
        }
        System.out.println(sum);
        return sum;
    }
}
public class Ch05 {

    public static void main(String[] args) {
        NumCall numCall = new NumCall();
        FutureTask futureTask = new FutureTask(numCall);
        new Thread(futureTask).start();

        try {
            Object o = futureTask.get();
            System.out.println("返回值:" + o);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

线程的声明周期

 * 线程的几种状态:

新建:当一个类继承Thread或实现Runnable接口时,线程处于新建对象的状态。
就绪:进入线程队列等待CPU分配内存的状态。
运行:当前线程抢到了CPU的执行权,正常执行run方法中的语句
阻塞:多个线程抢CPU的执行权,只有一个抢到了,其余的就会进入阻塞状态
死亡:线程完成了它的全部工作内容或被强制终端执行或抛出异常导致线程结束。

 线程之间的调度

调整线程优先级 :

        Java线程的优先级为整型,取值范围是1~10我们可以在Thread类中可以看见有以下三个静态常量:见名知意 他们分别为MIN_PRIORITY线程最低优先级 NORM_PRIORITY线程默认优先级 MAX_PRIORITY线程高优先级 Thread类也为我提供了setPriority()getPriority()方法来让我们设置或获取线程的优先级

 线程睡眠 Thread.sleep()

Thread.sleep(long millis(毫秒)):让调用的线程进入休眠(阻塞状态)让给其他线程使用。

从Thread类调用sleep方法可以看出来他是个是静态方法,因此不建议用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。

线程让步 yield()

yield()方法也是Thread类提供的一个静态的方法。
他同样让调用的线程进入休眠(阻塞状态)让给其他线程使用。暂停当前正在执行的线程对象,把执行机会让給相同或者更高优先级的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是直接进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次.有可能当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执

讨论 线程睡眠和线程让步的区别

sleep方法暂停当前线程后,会进入阻塞状态,只有到了设置的睡眠时间,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。

3、线程加入——join(插队)

        将几个并行线程的线程合并为一个单线程执行,应用场景是其他线程必须等待执行jion方法的线程执行完毕才能执行时),Thread类提供了join方法来完成这个功能,注意,它不是静态方法。

public class Ch07 {
    public static void main(String[] args) throws InterruptedException {
        JoinThread  t1 = new JoinThread ();
        JoinThread  t2 = new JoinThread ();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        t1.setName("线程一");
        t2.setName("线程二");
        thread1.start();
        thread1.join();
        thread2.start();
    }
}
class JoinThread implements Runnable {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public JoinThread() {
    }

    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            System.out.println(name  + i);
        }
    }
}

 线程同步synchronized

当多个线程同时操作一个共享的变量时,将会导致数据相互之间产生冲突,因此加入以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性

比如现有这样一个案例:火车战窗口卖票。
  三个窗口卖票:总票数为100张。

窗口一 二三 三个线程卖的票必须为不同一张 如果不加入线程同步会出现 窗口一与窗口二售卖同一张票的情况 那么这里就需要加入线程同步的概念

同步方法

  • 对于普通同步方法,锁是当前实例对象。也就是this

  • 对于静态同步方法,锁是当前类的Class对象 静态方法添加synchronized这把锁属于类了,所有这个类的对象都共享这把锁,这就叫类锁

class Window1 extends Thread {
    static int ticket = 100;
    static Object obj = new Object();

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

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

    public static synchronized void sell() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket--);
            } else {
                System.out.println(Thread.currentThread().getName() + "-票已售罄。");
                break;
            }
        }
    }
}

public class Demo01 {

    public static void main(String[] args) {
        Window1 w1 = new Window1("窗口一");
        Window1 w2 = new Window1("窗口二");
        Window1 w3 = new Window1("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }

}

同步代码块

即有synchronized关键字修饰的代码块。

同步代码块的锁有以下三种
1、this,代表本类的一个唯一的对象

2、任何静态的第三方对象   例 Object类

3、类对象,类只加载一次

package com.jsoft.morning.dem01;

/**
 * @Author:hua
 * @Date:2023年10月16
 * */
public class Demo1 {
    public static void main(String[] args) {
        Window1 window1 = new Window1("窗口一");
        Window1 window2 = new Window1("窗口二");
        Window1 window3 = new Window1("窗口三");
        window1.run();
        window2.run();
        window3.run();
    }


}
class Window1 extends Thread{
    public Window1(String name){
        super(name);
    }
  static   int ticket = 100;
    public void run(){
        while (true){
            synchronized (Window1.class){if (ticket>0){
                System.out.println(getName()+"卖票 票号为"+ticket--);
            }else {
                System.out.println("票已经售光");
                break;
            }}

        }

    }
}

wait()、notify()和notifyAll()

wait()wait()方法之后,那么此时访问这个对象的线程就会一直处于等待状态

notify()法唤醒某一个等待线程,

notifyAll()如果有多个线程都在等待,使用notifyAll方法可以一次唤醒所有的等待线程

总结下 wait 和 sleep 方法的不同?
共同点
    wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

不同点

 *****方法根源不同
        sleep() 是 Thread 的静态方法
        而 wait() 是 Object 的成员方法,每个对象都有

 ****醒来时机不同
sleep 方法必须要传递一个超时时间的参数,且过了超时时间之后,线程会自动唤醒。而 wait 方法可以不传递任何参数,不传递任何参数时表示永久休眠,直到另一个线程调用了 notify 或 notifyAll 之后,休眠的线程才能被唤醒。也就是说 sleep 方法具有主动唤醒功能,而不传递任何参数的 wait 方法只能被动的被唤醒。

    锁特性不同(重点)
        wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制

        wait 方法会主动的释放锁,而 sleep 方法则不会
        wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)占着茅坑拉不出屎会把坑让出来
        而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)占着茅坑不拉屎

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张有财Java成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值