多线程总结笔记

多线程!!

使用多线程并不难.难点是对这些概念的理解.

在实际开发过程中,我们自己写多线程的机会并不多,我们会使用框架.很多框架已经封装了多线程.

一.面试官都会问多线程的概念.

  1. 程序:就是具有特定功能的代码编译打包后的产物!例如:钉钉.程序安装到电脑上之后会有两种状态:运行状态和非运行状态.
  2. 进程:正在运行的程序.进程是资源分配的最小单位.操作系统会给进程分配资源(cpu,内存).
  3. 线程:一个进程里面包含1个或者多个线程.线程就是真正干活的"人".一般来说,一个线程可以执行一个或者多个任务.
  4. 操作系统把资源分配给进程,进程会把资源分配给线程.操作系统并不会直接和线程打交道.

二.当运行一个Java程序的时候,系统其实会创建3个线程.

  1. 主线程:执行main函数代码的线程
  2. 垃圾回收线程:垃圾回收线程会执行垃圾回收机制,会定期器回收不再使用的堆区内存.
  3. 错误处理线程:会出现报错.报错有一个线程.
//单线程.
//如果把所有的任务都放在一个线程里面执行会出现一个问题:如果前面的任务执行不完,后面的任务不开启.
//如果想要多个任务"同时"执行,称为并发.多个进程(正在运行的程序)他们交替使用cpu进行运算.从微观上,只看一个进程,程序中包含若干个线程,这些线程会抢占cpu.多线程让多个任务同时执行.

//如何创建额外的线程:
//创建线程有两种方式:
//1.定义Thread类的子类,重写run方法.1).新建一个类继承于Thread类;2).在子类中重写run方法--run方法里面写你要执行的任务;3).创建一个线程对象;4).需要调用线程对象的start方法,用于开辟线程.5).调用run方法其实就是普通的方法调用,并不会开辟新线程,只有调用start方法才会开辟新线程,在新线程里运行run方法.

//定义成匿名类:没有名字的 局部内部类,主要就是为了重写方法.
Thread t =new Thread(){
    @Override
    public void run(){
        //自己写的代码
    }
};
t.start();

//2.创建一个Runnable接口的实现类,这个实现类作为Thread的参数.1).创建一个类,实现Runnable接口;2).实现接口中声明的run方法.3).创建一个Thread对象,构造器的参数就是实现Rannable接口的对象.4).调用Thread对象的start方法.

//匿名类的使用场景:如果只是为了重写父类的方法,或者为了实现接口中的方法.
Thread t2 =new Thread(new Runnable(){
    @Override
    public void run(){
        //自己写的代码
    }
});
t2.start();

//区别:继承Thread和实现Runnable接口的区别?
//  1.在Java中只允许单继承,不能有多个父类,如果一个类已经有父类了,那么不让使用继承Thread方式实现多线程了.例如,如果有一个Student类已经继承了Person类那么只能选择Runnable接口了.
//2.如果继承Thread,调用sleep时,可以那对象(this)通用sleep,因为继承了整个方法.但是Runnable就不行,只能用Thread.sleep(long)方式.
//3.如果使用继承的方式实现线程同步,解决线程同步的问题.共享数据定义成static的,而且所需要类名.class.如果使用同步方法,方法必须是static方法.但Runnable实现类都可以不定义成静态的.static.

//lambda表达式:
public static void main(String[] args) {
    Runnable mr = () -> { 
    System.out.println("我是一个子线程"); 
    for(int i = 0; i < 500000; i++) { 
        System.out.println(i); 
    }
  };
    Thread t = new Thread(mr);
    t.start(); 
    System.out.println("hello world");
}


Thread类的常用方法:
  1. Thread是一个用于描述线程的类.他是可以描述,线程要执行什么任务(Runnable),线程的优先级,线程名称,线程状态等.
  2. currentThread() 获取当前线程对象。 类方法. Thread.currentThread()
  3. setName(String name) 设置线程的名字。
  4. getName() 获取线程的名字。
  5. setPriority(int priority) 设置线程的优先级。 优先级的取值范围[1,10],默认是5.优先级高不高只是概率问题,不是绝对的.
  6. getPriority() 获取线程的优先级。
  7. getState() 获取线程的状态.线程的状态.
    1. NEW 线程处于新建状态,当你new出来一个Thread,但是还没有调用start方法.
    2. RUNNABLE 可运行状态,就绪状态.当start之后,尚未获取cpu之前的状态.当前cpu时间片耗尽,会从运行状态到就绪状态.
    3. RUNNING运行状态,正在使用cpu.
    4. BLOCK阻塞状态.sleep
  8. join() 执行该线程,会阻塞当前线程。执行调用join方法的线程.等调用join方法的线程执行完之后,当前线程才继续执行.
  9. sleep(long millis) 休眠指定时间(单位毫秒),会阻塞当前线程。类方法
  10. start() 启动线程.
  11. yield() 暂定该线程的执行,交出CPU的使用权。
  12. 运行程序的时候,系统会给你创建一个线程(main),在这个线程中执行main方法.
线程的同步

卖票问题

什么是线程的同步?

  1. 并行指的是线程同时执行。
  2. 同步不是线程同时执行,而是线程不同时执行。同步本质指的是数据的同步。一般情况下,线程之间是相互独立,如果都去访问同一个变量,极有可能让这个数据变乱。如果不想让数据变乱,应在不让他们同时访问同一个变量。这个控制过程称为线程同步。

一.线程同步问题,就是线程安全问题,如果多个线程访问同一个数据(共享数据),会产生数据紊乱问题.如果解决?

  1. 上锁;—同步锁synchronized
  2. 同步代码块:被加同步锁的代码块,称为同步代码块.
  3. 同步方法:被synchronized修饰的方法称为同步方法。

二.要给什么代码块加锁?

  1. 凡是涉及到共享变量的代码都要加锁.

如何实现线程的同步?

1.同步代码块

synchronized(对象可以用类名.class){ //必须多个线程用同一把锁,锁是什么对象都行.锁也叫做同步监视器.
    共享资源//我们所谓的那个变量。 
    }

同步代码块synchronized (对象),多个线程要公用同一个对象,才能真正意义上加上锁。对象没有特殊要求,可以是任何继承于Object类的对象。包括this

2.同步方法

被synchronized修饰的方法称为同步方法。

3.使用锁对象实现加锁和解锁.

public class SellWindow4 implements Runnable { 
    private int tickets = 100;
    //锁对象
    Lock lock = new ReentrantLock(); 
    @Override 
    public void run() { 
        while (tickets > 0) { 
            String threadName = Thread.currentThread().getName(); 
            lock.lock(); //加锁
            tickets--; 
            if (tickets >= 0) { 
            System.out.println(threadName + "卖掉1张票,剩余" + tickets); }
            lock.unlock(); //释放锁
      
        } 
    } 
}
  1. 线程的生命周期(从诞生到死亡)
  2. 变量的生命周期:变量从定义到内存被回收
  3. 线程的生命周期:线程从创建到销毁.在整个过程中,不同时期在整个过程中,不同的时期线程有不同的状态。而且在程序运行期间会发生状态的转换。

官方定义的线程状态如下:

  1. NEW:新建状态,指的是线程已经创建,但是尚未start()。
  2. RUNNABLE:可运行状态(已经调用了start方法),已经准备就绪,一旦抢到CPU就立即执行。
  3. BLOCKED:阻塞状态,处于阻塞状态的线程正在等待进入Synchronized块(或方法)。
  4. WAITING:等待状态,等待其他线程执行任务。直到其他线程任务结束或者收到notify信号。
  5. TIMED-WAITING:等待状态,限定时间的等待状态。
  6. TERMINATED:终止状态。线程要运行的任务已经结束。

生活中程序员会把线程划分为如下状态:

  1. NEW:新建状态,指的是线程已经创建,但是尚未start()。
  2. RUNNABLE:可运行状态(已经调用start方法),已经准备就绪,一旦抢到CPU就立即执行。
  3. RUNNING:正在运行状态,已经抢到CPU,正在执行代码片段。
  4. BLOCKED:阻塞状态。
  5. DEAD:死亡状态。线程的任务已经结束

一.线程一共两种:

  1. 用户线程:一般情况下,我们创建的线程都是用户线程.
  2. 守护线程:如果把线程t.setDaemon(true);这个t就会变成守护线程.

二.程序什么时候会终止:

  1. 在单线程下,main方法一旦执行完毕,程序就终止
  2. 在多线程下,所有的用户线程全部结束以后,程序才会终止.

三.程序刚启动的时候,系统会自动创建三个进程:

  1. mian线程—用户线程.
  2. 垃圾回收线程–守护线程.
  3. 有一个运行System.err的线程—守护线程.

三、线程池

1、什么是线程池?

  1. 水池:存放水的池子。
  2. 线程池:存放线程的池子。
  3. 线程池里面包含两类线程:核心线程(长期存在的线程);非核心线程(不能长期存在的).
  4. 任务队列:任务队列里包含若干个任务.BlockingTask(任务代码块)—Runnable对象.
  5. 队列:先进先出

任务如何与线程分配?

如果有线程空闲

Java中的线程池:是一个管理线程的池子。可以在需要的时候开辟线程,可以控制最大开辟的线程个数,可以在不需要的时候关闭线程,可以让任务排队执行。这些管理过程不需要我们干预,线程池能帮我们完成。我们所要做的就是往线程池中放任务。

2、为什么要有线程池?

  1. 多线程解决了任务并发问题,但是开辟和关闭线程很消耗系统的性能,开辟和关闭一个线程要处理很多细节,频繁的开辟和关闭线程会给系统增加很多开销。
  2. 线程池使用了重用的概念,可以控制线程开辟的数量,复用这些线程执行任务。这样就不用频繁的开辟和关闭线程了。

Execute 执行 .exe

Executor 执行器

3、创建线程池各参数含义

  1. new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,handler)
  2. corePoolSize 核心线程数
  3. maximumPoolSize 最大的线程数(还能扩充那么大)
  4. keepAliveTime 保留的存活时间(多长时间不用,移除该线程)
  5. unit TimeUnit 指定时间单位
  6. workQueue new ArrayBlockingQueue<>(5); 规定排队线程个数
  7. handler new ThreadPoolExecutor.CallerRunsPolicy() 指定拒绝策略

线程池能帮我们管理线程(在需要的时候开辟线程,不需要的时候销毁线程),把任务交给线程池即可,线程池会给你分配线程去执行,自己不需要和线程打交道.

拒绝策略

提供了四个预定义的处理程序策略:

  1. 在 ThreadPoolExecutor.AbortPolicy ,不执行新任务,并抛出异常。

  2. 在 ThreadPoolExecutor.CallerRunsPolicy 中,执行新提交的任务.

  3. 在 ThreadPoolExecutor.DiscardPolicy 中 ,不执行任务,也不抛异常。

  4. 在 ThreadPoolExecutor.DiscardOldestPolicy 中 ,丢弃队首的任务.

pool可以指定核心线程的个数,最大允许的线程的个数,超过核心线程数以后,多久关闭线程,任务队列,以及任务拒绝的机制。

5、线程池工具类

Exectors 是线程池工具类,可以帮我们快速构建线程池。

三种常见的线程池:

  1. 固定线程个数的线程池

  2. 不限线程个数的线程池

  3. 单个线程的线程池(串行任务池)

public class TestExecutors { 
    public static void main(String[] args) { 
    //创建一个固定个数的线程池 
    ExecutorService es = Executors.newFixedThreadPool(3); 
    //创建一个不限容量的线程池 ,初始容量是0.闲置60s,那么这个线程会被销毁
    //ExecutorService es = Executors.newCachedThreadPool(); 
    //创建一个只有一条线程的线程池 ,任务串行执行.
	// ExecutorService es = Executors.newSingleThreadExecutor(); 
    es.submit(new Runnable() { 
        @Override 
        public void run() { 
    		System.out.println(Thread.currentThread().getName()); 
        } 
    }); 
     es.execute(new Runnable() { 
        @Override 
        public void run() { 
    		System.out.println(Thread.currentThread().getName()); 
        } 
    });
	} 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值