Java多线程技术概述


前言


本文为笔记性质文章,为方便日后回顾,以及帮助到更多需要的人。
本文主要内容:Java多线程知识


一、进程及线程

进程:指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。

线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。 一个进程最少有一个线程。

注意:线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。一个个线程共同组成了进程。

二、线程调度

首先需要明确,就像每个皇子都想得到皇位一样

线程 最梦寐以求的都是:CPU的使用权。
这是就需要知道线程的调度方式:

分时调度与抢占式调度
分时调度:所有线程排队获得CPU使用时间片,依次使用,平均分配

抢占式调度:优先级高的线程先来,按照优先级的顺序依次执行。如果两个线程优先级相同,则随机选择一个。Java使用的就是抢占式调度。
优点:提高CPU的效率(并不是提高了程序的效率)
因为,在某一时间段,CPU运行有多个线程,但是在每个时刻,CPU都只能运行一个线程,CPU在多个线程间高速的切换,直至这些线程都被执行完。
在宏观角度,使用者是感觉不到这些的,在他看来,自己需要计算机执行的任务都是连续执行的。

三、同步与异步

同步:排队执行 , 效率低但是安全。

异步:同时执行 , 效率高但是数据不安全。

四、并发与并行

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

并行:指两个或多个事件在同一时刻发生(同时发生)。

(三,四了解即可,不必深究)

五,多线程在Java的实现

1.继承Thread类

类需继承Thread,利用run()方法实现,但在使用时并不是通过调用run()方法,而是线程的对象调用start()方法,如下:

public class MyThread extends Thread{
       public void run(){
           System.out.println("这是个线程");
       }
}

public class Run {
     public static void main(String[] args){
         MyThread myThread = new MyThread();
         myThread.start();
     }
}

结果如图:
在这里插入图片描述

2.实现Runnable接口

如果欲创建的线程类已经有一个父类了,这时就不能再继承Thread了,因为Java不支持多继承。2相比于1还有很多优点,我们稍后再说,先来看具体如何实现。

public class MyRunnable implements Runnable {
    public void run(){
        System.out.println("线程在运行中了");
    }
}

public class Run {
     public static void main(String[] args){
//         MyThread myThread = new MyThread();
//         myThread.start();
         Runnable runnable = new MyRunnable();
         Thread thread = new Thread(runnable);//将runnable传给Thread
         thread.start();
     }
}

优点:
1.通过创建任务然后分配给线程的方式实现多线程,更适合多线程同时执行任务的情况。
2.避免了继承Thread带来的局限性。
3.使任务与线程分离,提高了任务的健壮性。
4.在我们后续接触的线程池技术中,不接受继承Thread的线程。

3.实现Callable接口

特点之一是,执行结束时会返回一个结果给主线程
(Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。)
以下是具体操作方式

//Callable接口
public interface Callable<V> {
    V call() throws Exception;
}

使用步骤如下:

 //1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
     public <T> call() throws Exception {
     return T;
   }
}
//2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
  FutureTask<Integer> future = new FutureTask<>(callable);
//3. 通过Thread,启动线程
  new Thread(future).start();

都是接口,那么Callable与Runnable有哪些异同呢?

相同点:(1).都是接口;(2).都可以编写多线程程序;(3).都采用Thread.start()启动线程.
不同点
(1)Runnable没有返回值;Callable可以返回执行结果。
(2)Callable接口的call()允许抛出异常;Runnable的run()不能抛出。

六,线程的状态

初始状态:创建线程对象是的状态
可运行状态:线程已就绪,准备好被CPU调用。
运行状态:线程被获得时间片,被CPU调用。
阻塞状态:线程被阻塞等待重新回到上一状态争夺时间片
无限时等待(waiting)
计时等待(TimeWaiting)
锁定(Blocked)
终止状态:重新执行执行或抛出异常。

线程原理图

七,用户线程与守护线程

线程分为用户线程(自己决定自己的存亡),和守护线程
守护线程:当所有的线程都执行结束时,不管自己执行与否,都会自动消亡。
比如Java的垃圾回收线程就是典型的守护线程。

八,线程的中断(interrupt)

interrupt()方法用于中断线程,有中断标记,true 代表中断,false则表示没有。

九,线程安全问题

这里涉及到线程对共享资源的访问。结合操作系统来看。操作系统里有一个概念叫做:临界区,指一段同时被多个线程访问进行读写操作的代码。这就会导致线程安全问题产生。我的老师给我们举了一个栗子。

有一盆饭,还有五六个壮汉,如果大家都按一定的顺序吃饭,你一碗我一碗,这样即能保证都吃到饭,又能保证不打起来。但是有一个壮汉不按规矩来,他不光插队,还把饭里的肉都舀走了,这就会导致后续大汉的心态失衡和营养不良。放到线程里就是线程安全问题。线程虽然不是人但别忘了它遵循的是抢占式原则。

解决线程安全问题的方法是 加锁,关键字:synchronized

1.同步代码块(对象锁)

第一种方法是给对象加锁,不同的对象就是不同的锁。

synchronized(对象){

}

2.同步方法

给方法添加synchronized修饰

public synchronized static a(){
}

3.lock

1和2 都是隐式锁,lock是显式的

private Lock l = new ReentrantLock();
             l.lock();

上面举的栗子好吃,我们接着举
大汉舀饭的过程就好比是一个线程,为防止线程安全问题发生,我们加锁了,就像是在舀饭的勺子上安装了一个指纹识别器,只有符合的人才能用,其他人只能老实等着。

十,线程间通信

两个线程,当一个执行完后会唤醒等待的线程。两者协同。在Java中形象的解释为 生产者与消费者 两者具有明确的先后顺序。
我的老师将之阐述为厨师与服务员。厨师做好菜后会叫醒服务员上菜,然后休息,而服务员上完菜后会叫醒厨师接着做下一道菜,自己则休息。

/**
 * Created by alone on 2021/6/4.
 */

public class Demo4  {

    /**
     * 多线程通信问题, 生产者与消费者问题
     * @param args
     */
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }

    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0){
                    f.setNameAndSaste("老干妈炒饭","香辣味");
                }else{
                    f.setNameAndSaste("糖醋里脊","酸甜味");
                }
            }
        }
    }
    //服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //true 表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndSaste(String name,String taste){
            if(flag) {
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;//更改
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag) {
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
运行结果:
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
服务员端走的菜的名称是:老干妈炒饭,味道:香辣味
服务员端走的菜的名称是:糖醋里脊,味道:酸甜味
········

如果不加锁,就会出现我们担心的线程安全问题。话说我还做出过几回酸甜味的老干妈炒饭。。。。

十一,线程池( Executors)技术

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

Java中包含四种线程池,分别是
1.缓存线程池
2.定长线程池
3.单线程线程池
4.周期性任务定长线程池

线程池技术很重要,并不是简单就能讲清楚的,以后有机会我会就它写一个独立博客,到时候会贴在下面。

总结

以上就是今天要讲的内容,本文仅简单介绍了线程的知识和使用,具体深入性知识不做讨论。

水平有限,还请大家包涵。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值