1.15 java学习之多线程

多线程

1.1线程的基本概念

进程(process):操作系统中运行的程序就是进程,比如各种软件

在每个软件或者进程中,有多个线程,共同执行一个进程。

程序:是指令和数据的有序集合,是静态的概念。

进程是执行程序的一次执行过程,是动态过程,是系统资源分配的单位。

线程(Thread):是CPU调度和执行的单位。

注意:很多的多线程是模拟出来的,真正的多线程是多个CPU,即多核。比如服务器。

如果是模拟出来的,即在一个CPU的情况下,同一个时间,cpu只能执行一个代码 ,因为切换的很快,所以有同时执行的错觉。

线程是独立的执行路径。

在程序中即使没有自己创建线程,后台也会有多个线程,比如主线程,gc线程。

main():是程序的主入口,用于执行整个程序。

同一个进程如果有多个线程,线程的运行是靠CPU调度的,与操作系统相关,不能人为干预。

同一份资源存在资源抢夺的问题,需要并发控制。

线程会带来额外的开销,并发控制开销。

每个线程在自己的内存交互,内存不当会造成数据不一致。

1.2线程的创建

Thread类

  1. 自定义线程类继承Thread
  2. 重写run(),在里面执行线程方法体
  3. 创建***线程类***对象来调用start()方法启用线程

多个线程是同时执行,开启之后不一定立即开启,需要CPU调用

实现Runnable接口

  1. 实现Runnable接口

  2. 实现Run()方法,编写执行体

  3. 创建实现Runnable接口的实现类对象,在创建一个包含(Runnable接口的实现类对象)线程(Thread)类对象来调用start()方法启用线程。

    // new Thread (Threadtest).start()

方法: new Thread (实现类对象,名字).start()

推荐使用Runnable,因为java中继承有局限性。方便一个对象被多个线程使用。

多个线程操作一个资源,线程不安全 ,数据紊乱

实现Callable接口

  1. 实现Callable ,需要返回值类型
  2. 重写call 方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFieldThreadPool(1);
  5. 提交执行:futureresult1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务:ser.shutdownNow();

可以提供返回值

可以用异常

静态代理

可以使用接口声明对象,但是不能直接实例化。可以用其实现类去实现对象,(多态)

静态代理模式:

  1. 真实对象和代理对象都是实现统一接口
  2. 代理对象要代理真实对象

好处:代理对象可以做很多真实对象做不了的事情

真实对象专注自己的事情

这个和 new Thread (Threadtest).start() 是一致的

​ 代理对象 真实对象 具体方法:其中都实现了Runnable接口

Lambda表达式

为什么使用Lambda?

  1. 避免匿名内部类定义过多
  2. 代码更简洁
  3. 只留下核心逻辑

函数式接口:任何接口,如果只有唯一一个抽象方法,就是函数是接口。

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

推到过程:

  1. 首先定义一个接口:interface Ilike{

    void lambda()

    }

  2. 外部实现类:class Like implements Ilike{

    public void lambda(){

    System.ou.print(“外部类”)}

    }

    Ilike like = new Like();

    like.lambda();

  3. 静态内部类,放在类里:static class public Like2 implements Ilike{

    public void lambda(){

    System.ou.print(“静态内部类”)}

    }

​ Ilike like = new Like2();

​ like.lambda();

  1. 局部内部类,放在方法里: class public Like3 implements Ilike{

    public void lambda(){

    System.ou.print(局部内部类”)}

    }

    Ilike like = new Like3();

    like.lambda();

  2. 匿名内部类:没有类的名称,必须借助接口或者父类:

    like =new Ilike(){

    public void lambda(){

    System.ou.print(“匿名内部类”)}

    }

    like.lambda()

  3. 用lambda简化:

    like =()->{

    System.ou.print(“lambda表达式”)

    }

    like.lambda

  4. 有参数的表达式可以去掉参数类型:int a -》 a

  5. 可以去掉括号,如果只有一行代码,可以去掉花括号。

    like = ()->System.ou.print(“lambda表达式”)

    1.3线程状态1.15 java学习之多线程

线程的几种状态
新建状态(New):
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
就绪状态(Runnable):
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
运行状态(Running):
处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
阻塞状态(Blocked):
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态可分为以下3种:
位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
其他阻塞状态(Otherwise Blocked):
当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
死亡状态(Dead):
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。

停止线程:
  1. 不建议JDK提供的stop()和destroy()方法

  2. 推荐线程自己停下来,利用次数,不建议死循环。

  3. 建议使用一个标识位进行终止变量,当flag = false;,停止线程

    1. 设置一个标识位

      private boollean flag= true;

    ​ void run (){

    ​ while(flag){

    ​ system…out.print(“shuhsfhdi”)}

    ​ }

    1. 设置一个公开的方法停止线程,转化标识为。

      public void stop(){

      this.flag = false}

    2. 在主线程中,调用stop()方法,让线程停止。

    线程休眠

用法:Thread.sleep(时间)

sleep ():指当前线程阻塞的毫秒数;

sleep():有异常InterruptedExecption需要抛出

sleep到达时间后进入就绪状态

可以模拟网络延时和倒计时

每一个对象都有一把锁,但是sleep不会释放锁。

模拟网络延时:可以放大发生的概率

模拟倒计时:

int num= 10;

while(true){

​ Thread.sleep(1000);

num–;

if (num<=0){

break;

} }

获取当前系统时间

Date st = new Date(System.currentTimeMillis());//获取当前时间

while(true)

{//循环输出当前系统时间

Thread.sleep(1000)

system.out.print (new SimpleDateFormat(“HH:mm:ss”).format(st));打印出来时间

st = new Date(System.currentTimeMillis())////更新时间

}

线程礼让(yield)

用法:Thread.yield()

让当前正在执行的线程暂停,但不阻塞;

将线程从运行状态转化为就绪状态

让cpu重新调度,礼让不一定成功

线程强制执行

Join合并线程,待此线程执行完毕,在执行其他线程,其他线程进入阻塞状态。

线程的优先级

PRIORITY:优先级

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

但是不一定,只是优先级大权重大,决定权在CPU中

设置优先级:1~10;

Thread.MIN_PRIORITY = 1

Thread.MAX_PRIORITY =10

Thread.NORM_PRIORITY =5

使用getPriority,setPriority(int a)获取设置优先级

主线程main 一般优先级是5

一定要先设置优先级在start();

守护(daemon)线程

Thread.daemon:默认是false表示用户线程

守护线程会在虚拟机结束后在运行一段时间,在结束。

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

虚拟机必须保证用户线程执行完毕:main 线程是用户线程,gc是守护线程

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

1.4线程同步

含义:多个线程操作一个资源

并发:同一个对象被多个线程同时操作

线程同步就是一种等待机制,多个对象需要某一个对象的需要进入该对象等待池,形成队列,等待当前线程完毕。

为保证安全:加入锁机制 synchronized,增加安全性也会带来性能问题。

对于属性可以根据private,对于方法根据关键字:synchronized关键字:

有两种用法:synchronized 方法和synchronized(obj){}块;

每个synchronized必须获得对象锁才能对对象进行访问,否则线程会堵塞,方法一旦执行,就会独占锁,后面堵塞的线程要等待,会影响效率。

对于synchronized锁的是对象本身就是this。

对于synchronized (obj){}块锁的是里面的obj,obj可以是任何对象(需要增删改查)也称之为同步监视器。就是把要锁定的内容放入代码块里面。

死锁

某个同步代码块同时拥有两个以上的对象锁,,会发生死锁的问题。发生相互线程等待对方释放资源,都停止执行。

必要条件:

  1. 互斥:一个资源只能被一个进程使用
  2. 请求与保持条件:一个个进程引擎求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺:进程已获得资源,在未使用的情况下,不能强行剥夺
  4. 循环:若干个进程之间形成头尾相接的资源关系

破坏一个条件就是避免死锁;

lock

显示定义同步锁

,使用lock对象充当

与synchronized一样

一般用可重入锁搭配:private final ReentranLock lock =new ReentranLock ();

lock.lock();//加锁:一般放在try语句中的try中

lock.unlock();解锁:一般放在try语句中的finally中

lock只有代码块,synchronized有方法和代码块

lock有开关,synchronized隐士锁,除了作用域自动释放

lock性能好

线程通信

object类提供几个线程之间通信的方法:wait(),:表示线程一直在等待,知道其他线程通知,与sleep不同,会释放锁

notify():唤醒处于等待的线程

notifyALll():唤醒同一对象上所有调用wait()的方法,优先级别高的线程优先调度。

以上均是在同步方法或者同步代码块中使用。

解决线程通信消费者生产者模型的两种方法:

  1. 缓冲区
  2. 标识位
线程池

优点:

提高速度,降低资源消耗,便于线程管理:

  1. corePoolSize:线程池大小

  2. maximumPoolSize:最大线程数

  3. keepAliveTime:线程没有任务时,多久会终止

过程:

  1. 创建线程服务,参数为池子大小:ExectorService se = Exectors.newFieldThreadPool();

  2. se.execute(new Thread());

  3. se.execute(new Thread());

    se.execute(new Thread());

    se.execute(new Thread());

  4. 关闭连接se.shutdown()

其中ExectuoService是真正的线程池接口,创建之后如果有返回值用提交执行:futureresult1 = ser.submit(t1);

没有返回值用se.execute(new Thread());