Java-多线程

多线程介绍

进程和线程

进程和线程都是实现并发的基本单位。

进程 :进程是指程序运行的一生,它包括了代码的加载、代码的执行、代码执行完毕,进程结束。对于电脑来说,几核的电脑就相当于有几个脑子,每一个进程都能获取CPU的时间片。每一个进程都有自己的独立的储存空间

线程 :线程比进程的执行单位更小,多线程是指在一个进程下面进行着多个线程,这些线程共享一个进程空间并且同时运行。所有线程可以溜溜使用CPU的使用权,但也可以设置调度机制,把一些线程设置优先级更高,这样可以使这个线程更加容易的抢到CPU的使用权。

同步和异步

同步: 程序排队执行,特点: 效率低但是安全

异步: 程序同时执行,特点: 效率高但是不安全,极容易发生数据的错乱

并发和并行

并发: 指两个或者多个事件在同一个时间段内发生

并行: 指两个或者多个事件同时发生

多线程的三种实现方式

Thread类:

Thread类的特点就在于线程对象是交错运行的,谁抢到了就让谁运行,所以运行的过程是无序的,每次的结果可能都不一样。

第一步: 继承Thread类;第二步: 重写run方法;第三步: 调用start方法运行程序

public static void main(String[] args) {
    MyThread m = new MyThread();
    m.start();
    for (int i = 0; i < 5; i++) {
        System.out.println("冬瓜" + i);
    }
}
    static class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("馒头" + i);
            }
        }
    }

运行结果:

在这里插入图片描述

Runnable接口:

第一步: 创建一个类来实现Runnable接口;第二步:创建Runnable类的实例作为Thread构造参数,构造Thread类实例;第三步: 调用Thrad的start方法;第四步: 多线程代码最终都是通过调用Thread的start方法来运行

public static void main(String[] args) {
    MyRunnable mr = new MyRunnable();
    Thread t = new Thread(mr);
    t.start();
    for(int i=0;i<3;i++){
        System.out.println("慢头"+i);
    }
}

static class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i=0;i<3;i++) {
            System.out.println("茄子"+i);
        }
    }
}

运行结果:

在这里插入图片描述

Callable接口

Callable是一个带返回值的多线程接口

第一步: 创建Callable类实现接口; 第二步: 以该类实例作为FutureTask构造参数,构造FutureTask类实例;第三步: 以该FutureTask类实例作为Thread构造参数,构造Thread类实例;第四部: 调用Thrad的start方法

public static void main(String[] args) throws ExecutionException, InterruptedException {
    Callable<Integer> c = new MyCallable();
    FutureTask<Integer> task = new FutureTask<>(c);
    new Thread(task).start();
    Integer j = task.get();
    System.out.println(j);
    for (int i=0;i<3;i++){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }
}
    static class MyCallable implements Callable{
        @Override
        public Object call() throws Exception {
            Thread.sleep(1000);
            return 123;
        }
    }

运行结果:

在这里插入图片描述

联系:

通过上面的代码,我们可以看出Thread接口和Runnable接口都可以实现多线程,Thread虽然是Runnable接口下的子类,多线程靠的就是Runnable接口里的run()方法来实现的。

区别:

Thread类与Runnable接口

如果一个类继承 Thread类,会有线程不安全的问题,不适合于多个线程数据共享,而 Runnable 接口,就可以方便的实现数据共享。

Callable接口与Runnable接口

Callable接口是有返回值的,而Runnabale接口没有返回值。Callable接口里的call()方法可以抛出异常,而Runnable不能抛出异常,但可以自己定义异常。

多线程的操作方法

设置和获取线程的名字:

public static void main(String[] args) {
    MyThread t = new MyThread();
    new Thread(t).start();
    new Thread(t).start();

}
    static class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName()+i);
            }
    }
}

运行结果
在这里插入图片描述

线程休眠:

在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep()即可实现休眠。

public static void main(String[] args) {
    MyThread t = new MyThread();
    new Thread(t).start();
}
    static class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i=0;i<3;i++){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+i);
            }
    }
}

运行结果:

在这里插入图片描述

(这里是每个线程都会休眠3秒钟)

线程的强制运行:

在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行的时候,其他线程必须等待此线程完成之后才可以继续执行。

public static void main(String[] args) throws InterruptedException {
    new MyThread("新线程").start();
    for (int i = 0; i < 10; i++) {
        if (i == 5) {
            MyThread t = new MyThread("关系户");
            t.start();
            //main线程调用了t线程的join()方法,所以他必须等待t执行结束后才会向下执行.
            t.join();
        }
    }
}
public static class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

线程的礼让:

Thread类中提供了一种礼让方法,调用yield() 方法来实现给当前正处于运行状态下的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅仅只是一种简易,没有任何强制力来保证当前线程会将资源礼让。

yield()方法使具有同样优先级的线程有进入运行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。

(对于支持多任务的操作系统来说,不需要调用yeild() 方法,因为操作系统会为线程自动分配CPU时间片来执行。)

线程中断:

当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。

守护线程:

用户线程:当一个进程不包含任何用户线程时,进程结束。

守护线程:守护线程是为了守护用户线程,当最后一个用户线程结束后所有守护线程自动死亡。

public static void main(String[] args) {
    Thread t1 = new Thread(new MyRunnable());
    //在启动之前设置守护线程
    t1.setDaemon(true);
    t1.start();
    for (int i = 0; i < 5; i++) {
        System.out.println(Thread.currentThread().getName() + " " + i);
    }
}
static class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

运行结果:

在这里插入图片描述

线程安全问题:

在多线程的执行中,经常会有多个线程勇同时对一个变量进行操作,就会出现数据不符合常理的问题,比如在if判断里面控制变量大于0,但往往输出的数字会出现-1、-2,这是在程序运行中不允许出现的问题。

原因在于第一个线程进入了判断但还没有运行完毕,第二个线程、第三个线程也进来办事了,这时候三个线程都进入了if语句,把数据进行了一顿安排,这时候就会出现线程不安全的问题。

public static void main(String[] args) {
    Ticket ticket = new Ticket();
    new Thread(ticket).start();
    new Thread(ticket).start();
    new Thread(ticket).start();
}
static class Ticket implements Runnable{
    private int t = 10;
    @Override
    public void run() {
        while (t>0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("售票中");
            t--;
            System.out.println("售票成功,还剩:" + t);
        }
    }
}

运行结果:

在这里插入图片描述

公平锁和不公平锁

公平锁就是先来先运行,Java默认方法都是是不公平锁,即谁抢到算谁的。

可以用显式锁:private lock = new ReetrantLock(); 传入true表示为公平锁,false为不公平锁

后台线程:

在 Java 程序中,只要前台还有一个线程在运行,那么整个 Java 的进程就不会消失,使用 setDaemon() 方法可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行。

线程池

在线程运行中,最费时的是创建线程和关闭线程,如果这些步骤多了,会大大的浪费时间,降低了效率。线程池的创建,会直接提供多个线程,可以反复使用。在不能扩容的线程池中,程序没有线程执行时,会进行排队。在能扩容的线程池中,会进行扩容来保证任务的正常运行,一个线程长时间不用会被删除。

线程的六种状态

创建状态

在程序中调用构造方法创建了一个线程对象后,这个新创建出的线程对象便处于新建状态,但还需要调用Thread类的方法来使线程变成可运行状态。 Thread thread = new Thread()

就绪状态

创建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,准备着 CPU 的资源来完成使命,这个时候就称线程在就绪状态。

运行状态

当就绪状态被调用并获得处理器资源完成使命的时候,线程就进入了运行状态。此时,自动调用该线程对象根据需求被重写过的run() 方法。

阻塞状态

一个正在执行的线程在某些特殊情况下,如被人为调用sleep()、suspend()、wait()使线程进入挂起状态或需要执行输入操作时,会让 CPU 暂时中止自己的执行,进入阻塞状态。发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程就会转变成就绪状态。

死亡状态

线程调用 stop() 方法强制结束线程或 run() 方法执行结束后,就会变成死亡状态。处于死亡状态的线程不具有继续运行的能力。

我们在运行程序的时候,至少会启动两条线程,一个是main() 方法的线程,另一个就是垃圾回收器GC。

Lambda表达式:

函数式编程,在t2里面直接写入方法,调用start()方法,两种方式结果都一样,但Lanbda方法代码量更少。

public static void main(String[] args) {
    Thread t1 = new Thread(new MyRunnable());
    t1.start();
    Thread t2 = new Thread(() -> System.out.println("冬瓜!"));
    t2.start();
}
static class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("馒头!");
    }
}

运行结果:

()方法,两种方式结果都一样,但Lanbda方法代码量更少。

public static void main(String[] args) {
    Thread t1 = new Thread(new MyRunnable());
    t1.start();
    Thread t2 = new Thread(() -> System.out.println("冬瓜!"));
    t2.start();
}
static class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("馒头!");
    }
}

运行结果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值