博客
关于我
多线程复习——B站
阅读量:526 次
发布时间:2019-03-08

本文共 7197 字,大约阅读时间需要 23 分钟。

目录

程序:指令的集合,是静态的
进程:程序执行后的动态行式,是系统分配资源的基本单元
线程:一个进程至少有一个线程,是CPU调度执行的基本单位

多线程:真多线程是指多个CPU同时服务(并行),伪多线程是指一个CPU的多次切换模拟出的效果(并发)

线程创建

三种方法:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

其实:Thread类相当于Runnable接口的静态代理,将Runnable接口的实现类传入,然后生成代理对象,去做一些事情,事情怎么做就在start函数中。

继承Thread类

步骤:继承Thread–重写run–调用start方法

该方式过于简单,懒得写过程了
注意:线程执行顺序,无法确定!

实现Runnable接口

步骤:实现Runable–new Thread(实现类)–调用new出来的对象的start方法

该方式过于简单,懒得写过程了
注意:推荐使用该方法,可以避免单继承,同时方便同一个对象创建多个线程
由此产生一个问题:线程不安全。
线程不安全是因为多个线程操作同一个资源导致的。

线程之间的资源共享

静态变量:由于是类所有的,存在于方法区,所有该类实例都能看到该变量,都可以修改,所以线程不安全
实例变量:如果多线程共享一个实例则等同于静态变量的情况,线程不安全。如果每个线程都有一个类的实例,则实例变量互相隔离,线程安全
局部变量:局部变量是线程安全

实现Callable接口

步骤:★★★

  1. 实现Callable接口,带上返回值
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 开启服务
  5. 提交执行
  6. 获取结果
  7. 关闭服务

参考demo

public class Demo01 implements Callable
{ private int num; @Override public Integer call() throws Exception { int x; while(6 != (x=(int)(Math.random()*10))){ num++;// System.out.println(Thread.currentThread().getId()+":running,x="+x); } return num+1; } public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建实例 Demo01 d1 = new Demo01(); Demo01 d2 = new Demo01(); Demo01 d3 = new Demo01(); // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); Future
f1 = executorService.submit(d1); Future
f2 = executorService.submit(d2); Future
f3 = executorService.submit(d3); // 获取值 Integer r1 = f1.get(); Integer r2 = f2.get(); Integer r3 = f3.get(); //关闭线程池 executorService.shutdown(); System.out.println("r1="+r1+",r2="+r2+",r3="+r3); }}

Lambda表达式

避免匿名内部类定义过多,属于函数式编程

函数式接口

定义:任何接口,如果只包含一个方法且为抽象方法,那他就是函数式接口(Functional Interface,Java8)

public interface Abc(){   	public abstract void go()}

对于函数式接口,可以用lambda创建该接口对象

Abc abc = ()->{   //实现};abc.go();

注意:如果带参数:单参数可以不写参数括号。实现语句如果是一句,可以不写花括号。入参类型可以省略。

线程状态

在这里插入图片描述

注意点:就绪和运行状态是可以互相转化的取决于【CPU资源是否释放和获得】,线程一旦结束不能再被启动!
线程常用方法:
在这里插入图片描述

线程停止

注意:不推荐JDK提供的线程停止方法:stop、destory,推荐使用自定义标志位来作为停止的标识

在这里插入图片描述

线程休眠

sleep:

  1. 参数是毫秒数
  2. 会抛出InterruptedException
  3. 结束后恢复就绪状态
  4. 不释放锁!!

线程礼让

让线程暂停,直接回到就绪状态,不会释放锁!!

Join插队

thread.join的含义是当前线程需要等待previousThread线程终止之后才从thread.join返回。简单来说,就是线程没有执行完之前,会一直阻塞在join方法处。

在这里插入图片描述

线程状态

Thread.State

  1. NEW状态:没有启动的线程
  2. RUNNABLE:运行态
  3. BLOCKED:阻塞态
  4. WATING:等待另一个线程执行工作
  5. TIMED_WATING:等待另一个线程执行工作到指定时间
  6. TERMINATED:退出的线程

线程优先级

Java提供给线程调度器来使用,优先调用优先级高的,但是否一定会调用是不确定的。主线程优先级是默认的5

优先级范围:1-10
其中:
Thread.MIN_PRIORITY=1
Thread.MAX_PRIORITY=10
Thread.NORM_PRIORITY=5

使用方法:getPriority() setPriority(int xx)

注意:优先级超过1-10范围会报错,优先级设置不能超过当前线程所属组的最大线程优先级
如果对于性能要求比较高则可以设置线程优先级防止出现性能倒置。

守护线程Daemon

我们创建的线程都是用户线程,除非设置了为守护线程

thread.setDaemon(true)必须要在start前设置

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

线程同步

队列+锁:

  • 一个线程持有锁,会导致其他想要该锁的线程挂起
  • 多线程下,加锁和释放锁会产生性能开销
  • 高优先级线程等待低优先级线程释放锁会导致性能倒置

同步方法:在方法上加sychronized关键字,同时只能有一个线程在该方法内执行:pubic synchronized void a(){}

同步代码块:在代码块上加锁:synchronized(obj){}

一般而言,只在需要修改的部分加锁,防止性能浪费

同步方法

在方法上加锁,锁的是调用该方法的对象,this

同步块

同步块锁定的是一个对象,该对象称为同步监视器,推荐使用共享资源对象作为锁,比如要修改的那个对象。

JUC

java util concurrent 并发包,专门用来做并发编程的

CopyOnWriteArrayList 线程安全的ArrayList,使用volatile和transient来修饰,限制了内存可见性和指令重排

死锁

多个线程各子占有一部分资源(锁)又等待去占有其他资源(锁)时,容易出现,通俗点讲就是,想同时占有多个锁

解决方法:获取对方线程可能占有的锁时,释放掉自己的锁
在这里插入图片描述

Lock锁

JDK5开始,显示定义同步锁对象,使用Lock对象充当,JUC下的包,用于控制多个线程对共享资源进行访问

Lock是一个接口,ReentrantLock是其一个实现类,可重入锁

使用示例:

package com.xiaopi3;import java.util.concurrent.locks.ReentrantLock;public class Demo01 {       public static void main(String[] args) {           Test t = new Test();        new Thread(t).start();        new Thread(t).start();    }}class Test implements Runnable{       private int num=10;    /**     * static 保证new多个对象时,锁只有一份     * final 保证该锁不会被重新赋值     */    private static final ReentrantLock lock = new ReentrantLock();    @Override    public void run() {           while(true){           	// 一定要在判断前加锁,要不然会出现同时判断成功的错误             lock.lock();            if(num>=0){                   try {                       Thread.sleep(100);                } catch (InterruptedException e) {                       e.printStackTrace();                }                System.out.println(Thread.currentThread().getId()+" : "+num--);            }else{               	lock.unlock();                break;            }            lock.unlock();        }    }}

注意:一般而言,lock.unlock()在try catch中需要写在finally中,也可以直接try finally中写unlock!(推荐)

Lock和synchronized区别

  1. 前者显示锁后者隐式锁
  2. 前者只能锁代码块,后者还可以锁方法
  3. 前者调度线程消耗少于后者,扩展性高
  4. 建议:Lock>同步代码块>同步方法

线程通信

明确一个模型:生产者和消费者模型,两条线程进行通信

解决方式一:管程法
生产者:产出产品到缓冲区,缓冲区满则停产,不满则生产
消费者:从缓冲区消费,缓冲区空则停止,不空则消费
线程通信的方法:wait和notify
示例:

package com.xiaopi3;public class Demo {       public static void main(String[] args) {           MyPool myPool = new MyPool();        new Producer(myPool).start();        new Consumer(myPool).start();    }}class MyPool{       private int[] pool = new int[10];    private int num=0;    public synchronized void push(int x){           System.out.println("生产");        if(num==pool.length){               try {                   System.out.println("仓库满了,等待消费!");                wait();// 等待并释放MyPool的push上的锁            } catch (InterruptedException e) {                   e.printStackTrace();            }        }else{               num++;            System.out.println("存入,当前存量为:"+num);            notifyAll();// 通知所有在MyPool的push上等待锁的线程        }    }    public synchronized void pop(){           System.out.println("消费");        if(num==0){               try {                   System.out.println("仓库空了,等待生产!");                wait();            } catch (InterruptedException e) {                   e.printStackTrace();            }        }else{               num--;            System.out.println("取出,当前存量:"+num);            notifyAll();        }    }}class Producer extends Thread{       private MyPool myPool;    public Producer(MyPool o){           this.myPool=o;    }    @Override    public void run() {           for (int i = 0; i < 100; i++) {               myPool.push(i);        }    }}class Consumer extends Thread{       private MyPool myPool;    public Consumer(MyPool o){           this.myPool=o;    }    @Override    public void run() {           for (int i = 0; i < 100; i++) {               myPool.pop();        }    }}

解决方式二:信号灯法

生产者和消费者通过标志位来判断是否生产或者消费,原理是(借助第三方中介来作为判断人),信号灯法适用于生产者产出1个消费者消费1个的情况。

这里讲一下思路:第三方负责生产和消费的具体实现,生产者负责调用第三方生产方法,消费者负责调用第三方消费方法,第三方设置了标志位,生产还是消费取决于标志位。

线程池

JDK5提供了线程池相关接口

在这里插入图片描述
Executors:线程池工具类

public class Demo {       public static void main(String[] args) {           ExecutorService executorService = Executors.newFixedThreadPool(10);        Test test = new Test();        executorService.execute(test);        executorService.execute(test);        executorService.execute(test);        executorService.shutdown();    }}class Test implements Runnable{       @Override    public void run() {           System.out.println("123");    }}

转载地址:http://aamnz.baihongyu.com/

你可能感兴趣的文章
MySQL 用户权限管理:授权、撤销、密码更新和用户删除(图文解析)
查看>>
mysql 用户管理和权限设置
查看>>
MySQL 的 varchar 水真的太深了!
查看>>
mysql 的GROUP_CONCAT函数的使用(group_by 如何显示分组之前的数据)
查看>>
MySQL 的instr函数
查看>>
MySQL 的mysql_secure_installation安全脚本执行过程介绍
查看>>
MySQL 的Rename Table语句
查看>>
MySQL 的全局锁、表锁和行锁
查看>>
mysql 的存储引擎介绍
查看>>
MySQL 的存储引擎有哪些?为什么常用InnoDB?
查看>>
Mysql 知识回顾总结-索引
查看>>
Mysql 笔记
查看>>
MySQL 精选 60 道面试题(含答案)
查看>>
mysql 索引
查看>>
MySQL 索引失效的 15 种场景!
查看>>
MySQL 索引深入解析及优化策略
查看>>
MySQL 索引的面试题总结
查看>>
mysql 索引类型以及创建
查看>>
MySQL 索引连环问题,你能答对几个?
查看>>
Mysql 索引问题集锦
查看>>