多线程学习

Posted qqkkovo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程学习相关的知识,希望对你有一定的参考价值。

一、简介

程序:指令和数据的集合

进程:程序的一次执行过程,是系统资源分配的基本单位

线程:是cpu调度和执行的单位

二、线程实现

  1. 继承Thread
  • ThreadImpl extends Thread
  • 重写run()
  • new Thread().start()
  1. 实现Runnable接口
  • ThreadImpl implements Runnable
  • 重写run()
  • new Thread(new ThreadImpl( )).start()
  1. 实现Callable接口
  • ThreadImpl implements Callable< call()的返回值 >
  • 重写call()
  • TestCallable t1 = new TestCallable()
  • 创建执行服务 ExcutorService ser = Excutors.newFixedThreadPool(3);创建池子数量
  • 提交执行 Future< bollean > result = ser.submit( t1 );
  • 获取结果:result.get()
  • 关闭服务:ser.shutdownNow();

? 推荐使用实现Runnable接口,避免了单继承的局限性,灵活方便,方便同一个对象被多个线程使用

三、Lambda表达式

  • λ是希腊字母中,排第十一的字母==>英语名称为Lamda
  • 优点:
    • 避免匿名内部类过多,代码看起更加简洁;
    • 去掉了一堆没有意义的代码,只留下核心的逻辑
  • 实质属于函数式编程
(param)->expression [ 表达式 ]

	(param)->statement [ 语句 ]

		(param)->{statements}
		
			lambda演化:外部类 --> 静态内部类 --> 局部类 --> 匿名内部类(借助接口或父类实现) --> lambda表达式【 (参数)-> { 方法体 } 】

函数式接口

定义:只包含一个抽象方法。

对于函数式接口,可以通过Lamda表达式来创建该接口的对象

四、线程状态

  1. 新建:线程被创建,处于新建状态

  2. 就绪:线程对象调用start()方法开启线程,等待被cpu调度执行

  3. 运行:就绪状态的线程获得了cpu时间片,执行run()方法

  4. 阻塞: 线程放弃了对cpu的使用权,暂时停止运行

    ? 等待阻塞:线程调用wait()方法,进入等待阻塞

    ? 同步阻塞:线程获取synchronized同步锁失败,进入同步阻塞

    ? 其他阻塞:线程调用sleep()方法,进入阻塞状态

    wait() sleep()
    会释放锁,属于Object类 不会释放锁,属于Thread类
    只能在同步方法或者代码块里使用,不需要抛异常 可以在任何地方使用,需要抛出异常
    需要notify()或notifyAll()唤醒 不需要唤醒,休眠之后自动退出阻塞
  5. 死亡:线程执行完了run()方法,或者因为异常而退出了run()方法,线程的生命周期结束

方法 说明
setPriority( int ) 更改线程的优先级
sleep( long ) 指定的毫秒内休眠
join() 等待线程终止
yield() 让出cpu,再和其他线程一起争抢cpu
interrupt() 中断线程,别用
isAlive() 测试线程是否处于活动状态

守护线程(setDaemon(true))

  • 线程分为用户线程守护线程
  • jvm必须确保用户线程执行完毕,不用等待守护线程执行完毕
  • 比如:后台记录操作日志,监控内存,垃圾回收等待。。。

死锁

? 两个或多个线程,同时阻塞,都在等待某个资源的释放,而这个资源又被其他的线程占用,所以线程就一致阻塞,导致线程死锁。比如:现在有A、B两个线程,同时申请对方的资源,就会相互等待,线程一直处于等待状态,导致死锁。

? 一个同步代码块同时拥有 " 两个以上对象的锁 ",就可能发生 " 死锁 "。

线程死锁的四个条件

  1. 互斥:一个资源任意时刻,只能被一个线程占用
  2. 不剥夺:线程资源在使用完之前,不能被其他线程剥夺
  3. 请求和保持:线程保持了多个资源,又发出了新的资源请求,该资源被其他线程占用
  4. 循环等待:在资源等待链中,每一个线程获得的资源同时,被下一个线程请求

破坏死锁:破坏四个条件中的一种, 互斥条件没办法破坏。

  1. 破坏不剥夺:申请不到资源时,可以主动释放当前占有的资源
  2. 破坏请求和保持:一次性申请所有资源
  3. 破坏循环等待:按照顺序请求和释放资源

五、线程同步

? 一、synchronized

? 为了保证数据在方法中被正确访问的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源其他线程必须等待,存在以下问题:

  • 一个线程持有锁会导致其他需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题

CopyOnWriteArrayList是线程安全的集合

同步方法以及同步块

? private关键字保证数据只能被方法访问,所以只需要对方法提出一套机制--->synchronized关键字,它包括两种用法,同步方法和同步代码块

  • 同步方法:public synchronized void method(){},锁的是this,缺陷—>影响效率
  • 同步代码块:synchronized (Obj){},需要锁的是增删改的对象

二、Lock

? JDk5.0开始,提供了更强大的线程同步机制——通过显示显示定义同步锁。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,访问资源之前应先获得Lock对象。

? ReentrantLock(可重入锁)类实现了Lock,拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁和释放锁。

三、Lock和Synchronized的对比

Lock Synchronized
显示锁,需要手动开启和关闭锁 隐式锁,出了作用域会自动释放锁
只能锁代码块 锁代码块和方法
JVM花费更少的时间去调度线程,性能好

六、线程间通信

java提供了几个方法解决了线程间的通信问题

方法名 描述
wait() 线程一直等待,直到其他线程通知,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 随机唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()的线程,优先级别高的优先调度

生产者消费者问题

这是一个线程同步问题,生/消共享同一个资源,仅有一个synchronized是不够的

  • synchronized可以阻止并发更新同一个共享资源,实现了同步
  • 但是,不能实现线程间的通信

解决方式一:管程法

  • 生产者:负责生产数据的模块(模块可以是方法、对象、线程、进程)
  • 消费者:负责处理数据的模块(模块可以是方法、对象、线程、进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个 " 缓冲区 " ; 生产者将生产好的数据放入缓冲区,消费者从缓冲区取出数据

技术图片

package thread;


/**
 *
 * @author 柯神_
 * @date 2020-11-29 17:17:17
 * @Description 线程间通信,
 *                  生产者消费者问题
 *                         解决方式:一、管程法
 *                                  二、信号灯法
*/
public class ThreadCommunication {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);

        new Thread(producer).start();
        new Thread(consumer).start();

    }
}

/**
 * 一、管程法
 */
//生产者
class Producer implements Runnable{
    Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buffer.push(new Product(i));
            System.out.println("生产了" + i + "只鸡!");
        }
    }
}

//消费者
class Consumer implements Runnable{
    Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了==>" + buffer.getP().id + "只鸡!");
        }
    }
}

//产品
class Product{
    int id;

    public Product(int id) {
        this.id = id;
    }
}

//缓冲区
class Buffer{

    //定义一个容器大小
    Product[] products = new Product[10];

    //容器计数器
    int count = 0;

    //1.生产者生产产品
    public synchronized void push(Product product){
        /**
         * 缓冲区满了,生产等待,通知消费
         */
        if (count == products.length - 1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        /**
         * 缓冲区未满,可以生产
         */
        products[count] = product;
        count++;

        /**
         * 有产品了
         * 通知消费者消费
         */
        this.notifyAll();
    }

    //2.消费者消费产品
    public synchronized Product getP(){
        /**
         * 缓冲区空了,等待消费,通知生产
         */
        if (count == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        /**
         * 缓冲区有产品,可以消费
         */
        count--;
        Product product = products[count];

        /**
         * 吃了,缓冲区没满了
         * 通知生产者生产
         */
        this.notifyAll();
        return product;
    }
}

解决方式二:信号灯法

信号灯法是通过标志位来实现

/**
 * 二、信号灯法:通过标志位来实现
 */
package thread;


import lombok.SneakyThrows;

/**
 * 二、信号灯法:通过标志位来实现
 */
public class ThreadCommunication2 {
    public static void main(String[] args) {

        TV tv = new TV();
        Player player = new Player(tv);
        Watcher watcher = new Watcher(tv);

        new Thread(player).start();
        new Thread(watcher).start();

    }
}

//生产者 ==>演员录制
class Player implements Runnable{
    TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                this.tv.play("录制快本中。。。");
            } else {
                this.tv.play("新闻联播录制...");
            }
        }
    }
}

//消费者 ==>观众
class Watcher implements Runnable{
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            tv.watch();
        }
    }
}

//产品 ==>小视频节目
class TV{
    private String name;
    private boolean flag = true;   //标志位

    /**
     *  表演录制:观众等待
     */
    @SneakyThrows
    public synchronized void play(String name){
        if (!flag){
            wait();
        }

        System.out.println("演员表演了" + name);
        this.notify();
        this.name = name;
        this.flag = !this.flag;
    }

    /**
     * 观众观看:演员等待
     */
    @SneakyThrows
    public synchronized void watch(){
        if (flag){
            this.wait();
        }

        System.out.println("观众观看=======>" + name);
        this.notifyAll();
        this.flag = !this.flag;
    }
}

七、线程池

背景:线程频繁地创建和销毁,对系统的性能影响很大

线程池:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,实现资源重复利用。

优点:

  • 提高了响应速度(减少了创建线程的时间)
  • 降低了资源的消耗(线程使用完放回池中,实现资源重复利用)
  • 便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时,多长时间后终止

JDK5.0开始,提供了线程池相关的API:ExcutorService 和 Excutors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExcutor

  • shutdown()关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

package thread;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *
 * @author 柯神_
 * @date 2020-11-29 20:21:10
 * @Description
 *                  测试线程池
*/
public class PoolTest {
    public static void main(String[] args) {
        //创建线程服务,线程池,参数为线程池的大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new MyPool());
        service.execute(new MyPool());
        service.execute(new MyPool());
        service.execute(new MyPool());

        service.shutdown();
    }
}

class MyPool implements Runnable {

    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}

以上是关于多线程学习的主要内容,如果未能解决你的问题,请参考以下文章

多线程 Thread 线程同步 synchronized

多个用户访问同一段代码

多个请求是多线程吗

学习java第19天个人总结

多线程编程

多线程编程