java多线程

多线程

程序,进程,线程

​ 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
​ 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
​ 注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

核心

线程就是独立的执行路径;
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程; main()称之为主线程,为系统的入口,用于执行整个程序;
◆在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;线程会带来额外的开销,如cpu调度时间,并发控制开销。
◆每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

创建线程的方式一:继承Thread类,重写run()方法,调用start()方法启动线程

总结:注意,线程开启不一定立即执行,主要由CPU调度。

创建一个对象,然后在main方法中调用start()方法执行。结果是两个线程相互执行,具体哪个执行,要看CPU的调度。

public class TestThread01 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println("我在看代码--"+i);
        }
    }
    public static void main(String[] args) {
        //main线程: 主线程
        //创建一个新线程
        TestThread01 a = new TestThread01();
        //调用start方法,启动线程
        a.start();

        for (int i = 0; i <1000 ; i++) {
            System.out.println("我在学习多线程!!"+i);
        }
    }
}

执行结果:里面有run方法执行的语句 ,也有main方法里面的线程

创建线程的方法二:实现Runnable接口,重写run方法,执行线程要丢入Runnable接口实现类,调用start方法(推荐使用接口)
public class TestThread03  implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println("我在看代码--"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        TestThread03 testThread03 = new TestThread03();
        //将执行线程丢入Runnable的实现类接口,调用run方法
        //Thread thread = new Thread(testThread03);
        //thread.start();
        //创建线程对象,通过线程对象开启我们的线程,代理
        new Thread(testThread03).start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println("我在学习多线程!!"+i);
        }
    }
}

小结

继承Thread类

子类继承Thread类具备多线程能力

启动线程:子类对象. start()

不建议使用:避免OOP单继承局限性

实现Runnable接口

实现接口Runnable具有多线程能力

启动线程:传入目标对象+Thread对象.start()

推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

方法三:实现Callable接口(了解即可)
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);

5.提交执行:Future result1 = ser.submit(t1);

6.获取结果: boolean r1 = result1.get()
7.关闭服务: ser.shutdownNow();

Lamda表达式

Lamda表达式

为什么要使用lambda表达式
避免匿名内部类定义过多

可以让你的代码看起来很简洁

去掉了一堆没有意义的代码,只留下核心的逻辑。

也许你会说,我看了Lambda表达式,不但不觉得简洁,反而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多了,看习惯了,就好了。

理解Functional lnterface (函数式接口)是学习Java8 lambda表达式的关键所在。

函数式接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

public interface Runnable {
public abstract void run();
}

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

package kang.com;
/**
 * 推导Lambda
 * */
public class TestLamda01 {

    //3.静态内部类  类里面
    static class Like2 implements Ilike{
        @Override
        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{

            @Override
            public void Lambda() {
                System.out.println("i like lambda3");
            }
        }
        like = new Like3();
        like.Lambda();
        
        //5.匿名内部类,没有类的名称,必须借助接口或父类 注意这是一个语句,要加分号
        like = new Ilike() {
            @Override
            public void Lambda() {
                System.out.println("i like lambda4");
            }
        };
        like.Lambda();

        //6.用Lambda方法简化
        like = ()-> {
            System.out.println("i like lambda5");
        };
        like.Lambda();
    }//main里面
}
//1.定义一个函数式接口接口
interface Ilike{
    void Lambda();
}
//2.实现类
 class Like implements Ilike {
    @Override
    public void Lambda() {
        System.out.println("i like lambda1");
    }
}
package kang.com;
//自己练习Lambda方法
public class TestLambda02 {
    public static void main(String[] args) {
        // 匿名内部类
      /* Ilove ilove =new Ilove() {
            @Override
            public void love(int a) {
                System.out.println("1-->a");
            }
        };
       ilove.love(1);
    }*/

        //lambda 方法
       /* Ilove ilove = (int a) -> {
            System.out.println("1-->a");
        };
        ilove.love(1);*/
        //lambda方法简化1
         Ilove ilove;
         ilove = ( a) -> {
            System.out.println("1-->"+a);
        };
        ilove.love(1);
        
        //lambda方法简化2  方法中只有一条语句的话,可以省略大括号
        Ilove ilove1;
        ilove1 = (a) ->
            System.out.println("1-->"+a);
        ilove1.love(2);
    }

}
interface Ilove{
    void love(int a);
}

总结:
lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。

前提是接口为函数式接口(接口中有且只有一个方法)

多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。

//1.建议线程正常停止 ---》利用次数,不建议死循环
//2.建议使用标志位---》设置一个标志位
//3.不要使用stop或者destroy等过时或者jdk不建议使用的方法

sleep方法

sleep(时间)指定当前线程阻塞的毫秒数;1000毫秒==1秒

sleep存在异常InterruptedException;sleep时间达到后线程进入就绪状态;

sleep可以模拟网络延时,倒计时等。

每一个对象都有一个锁,sleep不会释放锁

静态代理对象

静态代理模式总结
真实对象和代理对象都要实现同一个接口
代理对象要代理真实对象

new Thread(()-> System.out.println("123")).start();

线程的状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gH1GvK2l-1637494028655)(E:\笔记\图片\线程状态.png)]

线程的优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

线程的优先级用数字表示,范围从1~10.由低到高
Thread.MIN_PRIORITY= 1;
Thread.MAX_PRIORITY= 10;

Thread.NORM_PRIORITY = 5;(默认优先级)

优先级低只是意味着获得调度的概率低.并不是优先级低就不会

被调用了.这都是看CPU的调度

使用以下方式改变或获取优先级
getPriority() . setPriority(int xxx)

优先级的设定建议在start()调度前

守护(daemon)线程

线程分为用户线程守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

如,后台记录操作日志,监控内存,垃圾回收等待…

线程同步

形成条件:线程+锁

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步﹒线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当一个线程获得对象的排它锁﹐独占资源﹐其他线程必须等待,使用后释放锁即可.存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下﹐加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.

同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法︰synchronized方法和synchronized块.
同步方法: public synchronized void method(int args)’
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个
synchronized方法都必须获得调用该方法的对象的锁才能执行﹐否则线程会阻塞,方法一旦执行﹐就独占该锁,直到该方法返回才释放锁﹐后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized 将会影响效率

同步块
同步块: synchronized (Obj ) { }Obj称之为同步监视器
Obj可以是任何对象﹐但是推荐使用共享资源(一般指共同要使用的对象)作为同步监视器
同步方法中无需指定同步监视器﹐因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class [反射中讲解]
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器﹐执行其中代码﹒

2.第二个线程访问﹐发现同步监视器被锁定﹐无法访问.

3.第一个线程访问完毕,解锁同步监视器.

4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问

死锁

◆多个线程各自占有一些共享资源﹐并且互相等待其他线程占有的资源才能运行﹐而导致两个或者多个线程都在等待对方释放资源﹐都停止执行的情形﹒某一个同步块同时拥有“两个以上对象的锁”时﹐就可能会发生“死锁”的问题.

产生死锁的原因:

1.系统资源不足

2.进程或者线程推进顺序不当

3.资源分配不当

死锁避免方法
产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用。"
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

处理死锁的方法:

超时等待 设置一个线程等待时间,如果超过了这个时间就释放自己的锁。

线程顺序执行

1.预防死锁:破坏死锁的条件之一,互斥条件不能改变

2.避免死锁:合理分配资源

3.检查死锁:利用专门的死锁机构检查死锁的发生,然后采取相应的措施

4.解除死锁:发生死锁的时候采取合理的方法解决死锁,一般是强行剥夺资源

如何打破四个产生条件:

  1. 打破互斥条件:改造独占性资源为虚拟大资源,但是大部分资源无法改造,因此不建议使用这个方法。
  2. 打破请求与保持条件:在进程(线程)运行之前,就把需要申请的资源一次性申请到位,满足则运行,不满足就等待,这样就不会造成在占有资源的情况下,还要申请新资源。
  3. 打破不可剥夺条件:在占有资源并且还想要申请新资源的时候,归还已经占有的资源。
  4. 打破循环等待条件:实现资源的有序分配,即对所有的设备进行分类编号,只能以升序的方式来申请资源。

Lock(锁)
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

synchronized 与Lock 的对比一
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)在finally里面释放锁

synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

线程通信

-分析一
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件.
对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后﹐又需要马上通知消费者消费
对于消费者﹐在消费之后,要通知生产者已经结束消费﹐需要生产新的产品以供消费.
在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一个共享资源,实现了同步synchronized不能用来实现不同线程之间的消息传递(通信)

解决方式1
并发协作模型“生产者/消费者模式”—>管程法
生产者:负责生产数据的模块(可能是方法,对象﹐线程,进程);消费者:负责处理数据的模块(可能是方法﹐对象﹐线程﹐进程);缓冲区∶消费者不能直接使用生产者的数据﹐他们之间有个“缓冲区

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

​ 数据缓存区
Producer(生产者) consumer消费右)

线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理(…)

线程池的核心属性:

ThreadFactory(线程工厂):用于创建工厂线程

corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。

workQueue(队列):用于保留任务并移交给工作线程的阻塞队列

maximumPoolSize(最大线程数):线程池允许开启的最大线程数
keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。

使用线程池
JDK 5.0起提供了线程池相关API:ExecutorService和ExecutorsExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

线程池的运作流程

任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。

线程池的拒绝策略

1.抛出异常

2.自己写一个类继承RejectedExecutionHandler,自己定义里面的东西

RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。

wait()和sleep()的区别

来源不同:sleep() 来自 Thread 类,wait() 来自 Object 类。

对于同步锁的影响不同:如果当前线程持有同步锁,那么 sleep 是不会让线程释放同步锁的。wait() 会释放同步锁,让其他线程进入 synchronized 代码块执行。

使用范围不同:sleep() 可以在任何地方使用。wait() 只能在同步控制方法或者同步控制块里面使用,否则会抛 IllegalMonitorStateException。

恢复方式不同:两者会暂停当前线程,但是在恢复上不太一样。sleep() 在时间到了之后会重新恢复;wait() 则需要其他线程调用同一对象的 notify()/nofityAll() 才能重新恢复。

作用的对象不一样,sleep是对本线程使用的,wait是对线程通信

线程的sleep()和yield()区别

线程执行 sleep() 方法后进入超时等待(TIMED_WAITING)状态,而执行 yield() 方法后进入就绪(READY)状态。

sleep() 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程运行的机会;yield() 方法只会给相同优先级或更高优先级的线程以运行的机会。

线程的join()方法的用法

用于等待当前线程终止。如果一个线程A执行了 threadB.join() 语句,即A.join(B),其含义是:当前线程A等待 B 线程终止之后才从 B.join() 返回继续往下执行自己的代码。

synchronized和Lock的区别

1)Lock 是一个接口;synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;

2)Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;synchronized 不需要手动获取锁和释放锁,在发生异常时,会自动释放锁,因此不会导致死锁现象发生;

3)Lock 的使用更加灵活,可以有响应中断、有超时时间等;而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,直到获取到锁;

4)在性能上,随着近些年 synchronized 的不断优化,Lock 和 synchronized 在性能上已经没有很明显的差距了,所以性能不应该成为我们选择两者的主要原因。官方推荐尽量使用 synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。

synchronized使用场景

1.作用于非静态方法,锁住的是对象实例(this),每一个对象实例有一个锁。

public synchronized void method() {}

2.作用于静态方法,锁住的是类的Class对象,因为Class的相关数据存储在永久代元空间,元空间是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。

public static synchronized void method() {}
3.作用于 Lock.class,锁住的是 Lock 的Class对象,也是全局只有一个。

synchronized (Lock.class) {}
4.作用于 this,锁住的是对象实例,每一个对象实例有一个锁。

synchronized (this) {}
5.作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个。

public static Object monitor = new Object(); synchronized (monitor) {}

synchronized,除非 synchronized 无法满足需求时,则可以使用 Lock。

synchronized使用场景

1.作用于非静态方法,锁住的是对象实例(this),每一个对象实例有一个锁。

public synchronized void method() {}

2.作用于静态方法,锁住的是类的Class对象,因为Class的相关数据存储在永久代元空间,元空间是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。

public static synchronized void method() {}
3.作用于 Lock.class,锁住的是 Lock 的Class对象,也是全局只有一个。

synchronized (Lock.class) {}
4.作用于 this,锁住的是对象实例,每一个对象实例有一个锁。

synchronized (this) {}
5.作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个。

public static Object monitor = new Object(); synchronized (monitor) {}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值