多线程详解
Posted fzly-88
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程详解相关的知识,希望对你有一定的参考价值。
Java.Thread
01线程简介
一个进程-->多个线程
进程:执行程序的一次执行过程
线程:就是独立的执行路径
现在的多线程 多是 模拟出来的
02线程实现(重点)
三种创建方式:
-
继承Thread(重点)
自定义线程类继承Thread类 重写run()方法,编写线程执行体 创建线程对象,调用start()方法启动线程
-
实现Runnable接口(重点)
定义MyRunnable类实现Runnable接口 实现run()方法,编写线程执行体 创建线程对象,调用start()方法启动线程
-
实现Callable接口(了解 )
实现Callable接口,需要返回值类型 重写call方法,需要抛出异常 创建目标对象 创建执行服务:ExecutorService ser + Executors.newFixedThreadPool(1); 提交Future<Boolean> result1 = ser.submit(t1); 获取结果: boolean r1 = result1.get() 关闭服务:ser.shutdownNow();
Lambda表达式
为什么要使用Lambda表达式 :
-
避免匿名内部类定义过多
-
可以让你的代码看起来很简洁
-
去掉了一堆没有意义的代码,只留下核心的逻辑.
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口.
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象.
推导lambda表达式:
/*
推导lambda表达式 最初代码
*/
public class TestLambda1 {
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
public void lambda() {
System.out.println("i like lambda");
}
}
/*
推导lambda表达式 2.3.4.5.6是各种实现接口的方法,逐层推进
idea若是报错:将ProjectSettings 中Language level改成 8
参考:https://blog.csdn.net/fenghuibian/article/details/52704057
*/
public class TestLambda1 {
//3.静态内部类
static class Like2 implements ILike{
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//4.局部内部类
class Like3 implements ILike{
public void lambda() {
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();
//5.匿名内部类,没有类的名称,必须借助接口或父类
like = new ILike() {
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
//6.用lambda简化
like = ()->{
System.out.println("i like lambda5");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
public void lambda() {
System.out.println("i like lambda");
}
}
总结:
- lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹.
- 前提是接口为函数式接口
- 如果去掉参数类型, 就必须所有参数都去掉(多个参数必须加括号)
线程常用方法
- 线程停止,引入标记位,让线程自己运行我完停止,不建议强行停止
- 线程休眠:sleep()
- 线程礼让:yield()
- 线程强行执行:join()
03线程状态
创建-->就绪状态-->(阻塞状态)-->运行状态-->dead
setPriority(int newPriority) : 更改线程的优先级
static void sleep(long millis) : 在指定的毫秒内让当前正在执行的线程休眠
void join() : 等待该线程终止
static void yield() : 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() : 中断线程,别用这个方式
boolean isAlive() : 测试线程是否处于活动状态
死亡后的线程不能再次启动
测试线程优先级:setPriority(int); int型:1~10
守护(daemon)线程:thread.setDaemon(true)
04线程同步(重点)
-
由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题;
-
死锁
线程A拥有线程B的资源,现在需要线程B的资源,但线程B此时也再等A的资源,两个线程都无法向下运行,造成死锁。
-
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不发。
- 不剥夺条件:进程已获得的资源,在使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
-
通过Lock(锁)类实现锁
-
synchronized与Lock的对比:
-
Lock是显示锁(手动开启,手动关闭),synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
-
05线程通信问题
Producer(生产者)--> 数据缓冲区 --> Consumer(消费者)
package gaoji;
//测试:生产者消费者模型-->利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
//成产
@Override
public void run(){
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消费
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("消费了--》"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了,就需要等待消费者
if(count == chickens.length){
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
chickens[count] = chicken;
count++;
//可以通知消费者了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断是否能消费
if(count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
线程池:
-
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
-
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁的创建和销毁,实现重复利用。类似生活中的公共交通工具。
-
好处:
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
-
便于线程管理(....)
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
以上是关于多线程详解的主要内容,如果未能解决你的问题,请参考以下文章