多线程

在自己还是很弱鸡的时候,经常面试被问到创建线程的几种方式。那个时候想的是不就2种吗,一个实现runable接口,一个继承thread类。发现了自己为什么不能跟面试官聊下去的原因了~~目前还是个弱鸡,但是还是发现了很多种方法。总结了一下,无非是变相的使用Thread 和线程池。还有一个lambda的(并行流)。

继承Thread类

package com.example.demo.demo1;

public class Demo1 extends Thread {


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

    @Override
    public void run() {
        while (!interrupted()){
            System.out.println(getName()+"线程执行了");
        }
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        Thread thread = new Demo1("hah");
        thread.setDaemon(true);//将线程设置为守护线程,随着main线程的存在而存在
        thread.start();
        thread.interrupt();//线程的中断 不要用stop 因为stop并不会释放锁等资源
    }
}

实现Runable接口

package com.example.demo.demo1;

public class Demo2 implements Runnable {
    @Override
    public void run() {
        while (true){
            System.out.println("线程正在运行");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new Demo2());
        thread.start();
    }
}

匿名内部类

package com.example.demo.demo1;

public class Demo3 {

    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println("线程执行");
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行2");
            }
        }).start();
    }
}

带返回值的线程

package com.example.demo.demo1;

import org.omg.PortableServer.THREAD_POLICY_ID;

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

public class Demo4 implements Callable<Integer> {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //实例化实现callable接口对象
        Demo4 demo4 = new Demo4();
        //FutureTask实现RunnableFuture接口,RunnableFuture是Runable接口的子类
        FutureTask<Integer> task = new FutureTask<>(demo4);
        //将FutureTask以Runable传入
        Thread thread = new Thread(task);
        thread.start();
        Integer a = task.get();
        System.out.println(a);
    }
    @Override
    public Integer call() throws Exception {
        Thread.sleep(3000);
        return 1;
    }
}

定时器

package com.example.demo.demo1;

import java.util.Timer;
import java.util.TimerTask;

public class Demo5 {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("正在执行");
            }
        },0,1000);
    }
}

线程池

package com.example.demo.demo1;


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

public class Demo6 {
    public static void main(String[] args) {
        //newCachedThreadPool会自动回收已经用完的线程资源
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i = 0 ;i<10;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程执行");
                }
            });
        }
        //线程池需要关闭
        executorService.shutdown();

    }
}

常用的几个线程池

  • (1)newCachedThreadPool
  • (2)newFixedThreadPool
  • (3)newSingleThreadExecutor
  • (4)newScheduleThreadPool

1.创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:

工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

2.创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

3.创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4.创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

spring实现多线程

lambda表达式

package com.example.demo.demo1;



import java.util.ArrayList;
import java.util.List;

public class Demo7 {


    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.parallelStream().forEach(System.out::println);
        Integer b =list.parallelStream().mapToInt(a ->a).sum();
        System.out.println(b);
    }
}

多线程情况下碰得到的问题

1.线程安全性问题
2.活跃性问题(死锁,饥饿,活锁)
3.性能问题(CPU需要给线程分配时间片,线程间的切换 需要记录执行的位置等操作(类似计数器))

活跃性问题

1.死锁(多个线程互相竞争同一个资源)
2.饥饿(由于线程优先级问题,导致线程优先级低的无法抢到CPU的执行时间片,导致一直无法执行)
3.活锁(跟死锁相反,2个都是互相让步CPU资源,然后切换的时候又竞争到同一片时间片)

饥饿和公平

  • 高优先级吞噬所有低优先级的CPU的时间片
  • 线程被永久堵塞在一个等待进入同步块的状态
  • 等待的线程永远不被唤醒

如何尽量避免饥饿问题

  • 合理的设置优先级
  • 使用锁来代替synchronized

线程安全性问题

  • 多线程环境下
  • 多线程共享一个资源
  • 对资源进行非原子性操作

偏向锁

 每次获取锁和释放锁会浪费资源
 很多情况下,竞争锁并不是多线程,而是由一个线程在使用
 只有单线程访问同步代码块适用

轻量级锁

  • 自旋
  • 多个线程同时访问

Synchronized jvm原理

锁的信息存放在对象头中。
对象头中的信息

  • Mark Word
  • Class Metadata Address
  • 偏向锁
  • 轻量级锁
  • 重量级锁

Synchronized解决安全问题

内置锁(Synchronized)保证线程原子性,当线程进入方法时候,自动获取锁,一旦锁被其他线程获取到,其他的线程就会进行等待。
锁的特征:只能有一个线程进行使用。
怎么去释放锁,程序执行完毕的之后,就会把锁释放。
但是他会降低程序的运行效率
锁的资源的竞争,重入锁
内置锁也是互斥锁
使用方式:同步方法,同步代码块

volatile

  1. Volatile称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的。
  2. 可见:一个线程修改了这个变量的值,在另外一个线程能够读到这个线程修改后的值。
  3. Synchronized除了线程之间的互斥意外,还有一个非常大的作用,就是保证了可见性。
  4. 只要是全局共享变量,全部都加上volatile

volatile与synchronized区别

volatile作用:可以保证可见性,但是不能保证原子性,禁止重排序
synchronized:既可以保证原子性还可以保证原子性

重排序概念
cpu对代码实现进行优化,不会对有依赖关系做重排序
代码执行的顺序可能会发生改变,但是执行的结果不会发生任何改变。

多线程
什么是数据依赖关系?
int a = 1;
int b = 2;
int c = a*b;

a b没有依赖关系,可能先执行b

as-if-serial规则
serial 不管怎么去做重排序,但是目的是提高并行度,但是不能影响到正常的结果。
重排序,在多线程情况下遇到。

JDK提供的原子类原理及使用

  1. 原子更新基本类型(AtomicInteger…)
  2. 原子更新数组(AtomicIntegerArray…)
  3. 原子更新抽象类型(AtomicReference…)
  4. 原子更新字段

原理:CAS

Lock接口的使用

Lock li = new ReentrantLock();
li.lock();
li.unlock();

优点

  1. Lock接口需要显示的获取和释放锁,可以使代码变得更加灵活
  2. 使用Lock可以方便的实现公平性
  3. 非阻塞的获取锁
  4. 能被中断的获取锁
  5. 超时获取锁

ThreadLocal

什么是Threadlocal?给每个线程提供鞠躬变量,解决线程安全问题。
ThreadLocal底层是一个Map集合。

java内存模型 jmm多线程相关

jmm决定一个线程对共享变量的写入时,能够对另一个线程是否可见。
主内存:共享存储变量
本地内存:共享变量的副本

多线程之间如何实现通讯?

wait
notify

CountDownLatch(计数器)

  • CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量 。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

CyclicBarrier(栅栏)

  • CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
  • CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
  • CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

Semaphore(信号量)

  • Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞
  • Semaphore可以用来构建一些对象池 ,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
  • 它的用法如下:
  • availablePermits函数用来获取当前可用的资源数量
  • wc.acquire(); //申请资源
  • wc.release();// 释放资源

线程池的目的

  1. 降低资源消耗(重复利用已创建的线程降低线程创建和线程的摧毁的消耗)
  2. 提高响应效率 当任务到达时,任务可以不需要等到线程创建就能立即执行,方便管理 。
  3. 方便管理

为什么线程池要用阻塞队列?

能够等待核心线程完成任务,然后复用线程

合理配置线程池

原则:CPU密集。IO密集
CPU密集:该任务需要大量运算,而没有阻塞情况,CPU全速运行。配置线程数=cpu的核数
阻塞产生的原因:请求,读数据库,循环
IO密集:该任务需要大量IO操作,产生IO阻塞。多配置线程数,2*CPU的核数(最大线程数)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值