MIKU MOE

Java_多线程

多线程


 

1. 线程简介

程序 (指令和数据的有序集合 , 静态概念)

进程 Process (执行程序的一次执行过程,是系统资源分配的单位)

线程 Thread (通常一个进程可包含若干线程 , 是CPU调度和执行的单位)


Thread class -->继承Thread类 (Thread类实现了Runnable接口)(不建议:避免OOP单继承局限)

  • 自定义线程类继承Thread类

  • 重写run()方法

  • 创建线程对象,调用start()启动

Runnable接口 -->实现Runnable接口(推荐:避免单继承局限,灵活方便,方便同一个对象被多个线程使用)

  • 定义类实现Runnable接口

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start()

Callable接口 -->实现Callable接口(了解)

2. 线程实现 **

  2.1 实现 Thread

/*
 线程测试
 线程开启不一定拟机执行,由CPU调度
 */
 public class ThreadT extends Thread {
     @Override
     public void run() {
         //run方法线程体
         for (int i = 0; i < 200; i++) {
             System.out.println("i");
         }
     }
     public static void main(String[] args) {
         //main线程,主线程
         //创建一个线程对象
         ThreadT threadT = new ThreadT();
         //threadT.run();//先执行run()方法体
         threadT.start();//同时执行,交替输出
         for (int i = 0; i < 1000; i++) {
             System.out.println("-------"+i);
         }
     }
 }

练习Thread,实现多线程同步下载图片

 public class ThreadT2 extends Thread {
     private String url; //文件地址
     private String name;//保存文件名
     //构造函数
     public ThreadT2(String url, String name) {
         this.url = url;
         this.name = name;
     }
     //下载执行体
     @Override
     public void run() {
         WebDownloader downloader = new WebDownloader();
         downloader.downloader(url, name);
         System.out.println("下载了文件名为:"+name);
     }
     //主进程 开启三个进程分别下载图片
     public static void main(String[] args) {
         ThreadT2 t1 = new ThreadT2("请修改图片路径", "1.jpg");
         ThreadT2 t2 = new ThreadT2("请修改图片路径", "2.jpg");
         ThreadT2 t3 = new ThreadT2("请修改图片路径", "3.jpg");
         t1.start();
         t2.start();
         t3.start();
     }
 }
 //下载器
 class WebDownloader {
     //下载方法
     public void downloader(String url,String name) {
         try {
             //将URL变成文件
             FileUtils.copyURLToFile(new URL(url), new File(name));
         } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO一场,downloader方法出现问题!");
         }
     }
 }

  2.2 实现 Runnable (推荐使用)

public class RunnableT implements Runnable {
     @Override
     public void run() {
         //run方法线程体
         for (int i = 0; i < 200; i++) {
             System.out.println("i");
         }
     }
     public static void main(String[] args) {
         //创建runnable接口的实现类对象
         RunnableT runable = new RunnableT();
         //创建一个线程对象,通过线程对象来开启我们的线程(代理)
         new Thread(runable).start();
         //同时执行交替输出
         for (int i = 0; i < 1000; i++) {
             System.out.println("111111"+i);
         }
     }
 }

使用Runnable改写"实现多线程同步下载图片"代码

 //只需要修改开启方法
 new Thread(t1).start();
 new Thread(t2).start();
 new Thread(t3).start();

  # 案例:龟兔速跑

public class T1 implements Runnable {
     private static String winner;
     @Override
     public void run() {
         for (int i = 1; i <= 100; i++) {
             //模拟兔子睡觉
             if (Thread.currentThread().getName().equals("兔")&&i/10==0) {
                 try {
                     Thread.sleep(1);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             //判断是否结束
             boolean flag = gameOver(i);
             if (flag) {
                 break;
             }
             System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
         }
     }
     //判定是否完成比赛
     private boolean gameOver(int steps) {
         if (winner!=null) {
             return true;
         } else {
             if (steps==100) {
                 winner = Thread.currentThread().getName();
                 System.out.println("winner is:"+winner);
                 return true;
             }
         }
         return false;
     }
     public static void main(String[] args) {
         T1 t1 = new T1();
         new Thread(t1,"兔").start();
         new Thread(t1,"龟").start();
     }
龟兔赛跑

  2.3 实现Callable (了解)

实现Callable接口,需要返回值类型

重写call方法,需要抛出异常

创建目标对象

  1. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);

  2. 提交执行:Future<Boolean> result1 = ser.submit(t1);

  3. 获取结果: boolean r1 = result1.get()

  4. 关闭服务: ser.shutdownNow();

优点:可以定义返回值 可以抛出异常

 //只需要修改开启方法
 
 //1. 创建执行服务: newFixedThreadPool(new线程池)
 ExecutorService ser = Executors.newFixedThreadPool(3);
 //2. 提交执行:
 Future<Boolean> r1 = ser.submit(t1);
 Future<Boolean> r2 = ser.submit(t2);
 Future<Boolean> r3 = ser.submit(t3);
 //3. 获取结果:
 boolean rs1 = r1.get();
 boolean rs2 = r2.get();
 boolean rs3 = r3.get();
 //4. 关闭服务:
 ser.shutdownNow();

  2.4 静态代理

静态代理模式:

优点:代理可以做很多真实对象做不了的事情 真实对象专注自己的事

真实对象 和 代理对象实现同一接口 , 代理对象

 You you = new You ();
 Company company = new Company (you);
 company.HappyMarry();
 /********真实角色及代理********/
 //真实角色
 class You implements Marry {
     @Override
     public void HappyMarry() {
         System.out.println ("本人结婚");
     }
 }
 //代理角色
 class Company implements Marry {
     private Marty target;//代理真实角色对象
     public weddingcompany (Marry target) {
         this.target = target;
     }
     @Override
     public void HappyMarry() {
         before();//System.out.println("前代理");
         this.target.HappyMarry();//真实对象
         after();//System.out.println("后代理");
     }
 }
 /********输出********/
 前代理
 本人结婚 
 后代理

  2.5 Lamda表达式(λ) - JDK8

Functional Interface(函数式接口) :

  • 定义:

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

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

/*接口,实现类 - 静态内部类 - 局部内部类 - 匿名内部类 - 函数式接口*/
 public class TestLambda {
     //3.静态内部类 - 将外部实现类提入,加static
     static class Like2 implements ILike {
         @Override
         public void lambda() {
             System.out.println("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("lambda3");
             }
         }
         like = new Like3();
         like.lambda();//局部内部类
         
         //5.匿名内部类 - 没有类名,必须借助接口或者父类
         like = new Ilike() {
             @Override
             public void lambda() {
                 System.out.println("lambda4");
             }
         };
         like.lambda();//匿名内部类
         
         //6.lambda简化
         like = () ->{
             System.out.println("lambda5");
         };
         like.lambda();//lambda简化
         /*再简化:eee = (int a)->{ sout };
         *简化1:删除()内参数类型
         *简化2:简化删除括号 - eee = a->{ sout }
         *简化3(仅一行代码时):去掉花括号 - eee = a-> sout;
         *说明:
             仅有一行才能去掉花括号
             前提是接口为函数式接口
             多参数也可以去掉参数类型,去类型都去掉或者都不去,多参数必须要括号
         * */
     }
 }
 //1.定义一个函数式接口
 interface ILike {
     void lambda ();
 //2.实现类
 class Like implenents ILike {
     @Override
     public void lambda() {
         System.out.println("lambda");
 }

3. 线程状态

线程状态1

线程状态2

线程方法

终止进程 :

  1. 推荐线程正常停止 , 循环次数,不建议死循环

  2. 推荐使用标志位

  3. 不要使用stop或destory等过时或jdk不建议方法

  3.1 线程休眠

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

  sleep存在异常lnterruptedException

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

  sleep可以模拟网络延时(放大问题的发生性),倒计时等

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

//打印当前系统时间
 Date time = new Date(System.currentTimeMillis());//获取系统当前时间
 while (true){
     try {
         Thread.sleep( millis: 1000);
         System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));
         time = new Date(System.currentTimeMillis());//更新当前时间
     } catch (InterruptedException e) {
         e.printstackTrace( );
     }
 }

  3.2 线程礼让 - yield

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

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

  让CPU重新调度,你让不一定成功

  3.3 Join

  Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

  可以想象成插队

@Override
 public void run() {
     for (int i = 0; i < 1000; i++) {
         System.out.println("插队进程"+i);
     }
 public static void main(String[] args) throws InterruptedException {
     //启动线程
     Test testJoin = new Test( );
     Thread thread = new Thread (test3oin);
     thread.start();
     //主线程
     for (int i = 0; i < 500; i++) {
         if (i==200){
         thread.join();//插队
     }
     System.out.println("主进程"+i);
     }
 }

  3.4 线程状态观测

  线程可以处于以下状态之—:

  • new

    • 尚未启动的线程处于此状态

  • runnable

    • 在Java虚拟机中执行的线程处于此状态

  • blocked

    • 被阻塞等待监视器锁定的线程处于此状态

  • waiting

    • 正在等待另一个线程执行特定动作的线程处于此状态

  • timed_waiting

    • 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

  • terminated

    • 己退出的线程处于此状态。

 Thread.State state = thread.getState();
 
 while(state != Thread.State.TERMINATED){
  Thread.sleep(100);
  state = thread.getState();//更新线程状态
     System.out.println(state);//输出状态
 }

  3.5 线程优先级

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

  • 线程的优先级用数字表示,范围从1~10.

    • Thread.MIN_PRIORITY = 1;

    • Thread.MAX_PRIORITY = 10;

    • Thread.NORM_PRIORITY = 5; (main默认5)

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

  优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度

 Thread t = new Thread(myPriority);
 t.setPriority(Thread.MAX_PRIORITY);//10

  3.6 守护线程

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

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

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

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

 Thread thread = new Thread(xxx);
 thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程...
 thread.start();//不用等待守护线程执行完毕,用户进程结束自动结束

4. 线程同步 **

多个线程操作同一个资源 并发synchronized

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源﹐其他线程必须等待使用后释放锁即可。存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;

  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.

同步方法:

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法 : synchronized方法和synchronized块。

    同步方法: public synchronized void method(int args) { }

  • synchronized方法控制对“对象”的访问,每个对象对应一把锁。每个synchronized方法都必须获得调用该方法的对象的锁才能执行。否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁。后面被阻塞的线程才能获得这个锁,继续执行

    缺陷 : 若将一个大的方法申明为synchronized将会影响效率

synchronized默认锁的是this

  • 同步块:synchronized (Obj ){ }

  • Obj称之为同步监视器

    • Obj可以是任何对象﹐但是推荐使用共享资源作为同步监视器

    • 同步方法中无 需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class [反射中讲解]

  • 同步监视器的执行过程

    • 1.第一个线程访问,锁定同步监视器﹐执行其中代码.

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

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

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

public class UnsafeList {
     public static void main( String[] args) {
         //CopyOnWriteArrayList<>安全类(JUC)
         List<String> list = new ArrayList<String>();
         for (int i = 0; i < 10000; i++) {
             new Thread (()->{
                 //如果不锁可能会导致不同线程向相同下标添加数据,导致数据不全
                 synchronized (list){
                     list.add(Thread.currentThread().getName());
                 }
             }).start();
         }
         try {
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(list.size());
     }
 }

  4.1 死锁

互相持有对方的锁,需要拿到对方锁住的资源

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

破坏任意一个或多个条件就可避免死锁的发生

private void makeup () throws InterruptedException {
     if (choice==0){
         synchronized (lipstick){//获得口红的锁
             System.out.println(this.girlNmae+"获得口红的锁");
             Thread.sleep(1000);//1s后释放口红锁
             synchronized (mirror) {
                 System.out.println (this.girlNmae+"获得镜子的锁");
             }
         }
     } else {
         synchronized (mirror){//获得镜子的锁
             System.out.println(this.girlNmae+"获得镜子的锁");
             Thread.sleep(2000);
             synchronized (lipstick) {//一秒钟后想获得口红
                 System.out.println(this.girlNmae+"获得口红的锁");
             }
         }
     }
 }
 //解决方法,将第二个锁提到外面,第一个资源使用完后解锁,再锁住另外的锁
 synchronized (lipstick){//获得口红的
     System.out.println(this.girlNmae+"获得口红的锁");
     Thread.sleep(1000);//1s后释放口红锁
 }
 synchronized (mirror) {
     System.out.println (this.girlNmae+"获得镜子的锁");
 }

  4.2 Lock锁

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象

  • ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

class TestLock implements Runnable {
     int ticketNums = 10;
     //定义lock锁
     private final ReentrantLock lock = new ReentrantLock();
     @Override
     public void run() {
         while (true){
             try {
                 lock.lock();//加锁
                 if(ticketNums>0){
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printstackTrace();
                     }
                     System.out.println(ticketNums--);
                 } else { break; }
             } finally {
                 //解锁
                 lock.unlock();
             }
         }
     }
 }

  4.3 synchronized与Lock对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) ,synchronized是隐式锁,出了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  • 优先使用顺序:

    • Lock > 同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

5. 线程通信问题 线程协作

生产者消费者问题

应用场景 : 生产者和消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费.

  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止.

  • 如果仓库中放有产品﹐则消费者可以将产品取走消费﹐否则停止消费并等待,直到仓库中再次放入产品为止.

Java提供了几个方法解决线程之间的通信问题

注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常legalMonitorStateException

方法作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程﹐优先级别高的线程优先调度

解决方法:

  • 1.并发协作模型“生产者/消费者模式”利用缓冲区解决--->管程法

    • 生产者:负责生产数据的模块(可能是方法﹐对象﹐线程﹐进程);

    • 消费者:负责处理数据的模块(可能是方法﹐对象﹐线程,进程);

    • 缓冲区:消费者不能直接使用生产者的数据﹐他们之间有个“缓冲区

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

/*案例 - 解决死锁 - 利用缓冲区解决-->管程法*/
 public class Case {
     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 {
                 wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
         //如果可以消费
         count--;
         Chicken chicken = chickens[count];//取出鸡
         //通知生产者生产
         this.notifyAll();
         return chicken;
     }
 }
  • 2.并发协作模型“生产者/消费者模式”--->信号灯法

/* - 案例 - 解决死锁 - 信号灯法-->标志位*/
 public class Case2 {
     public static void main(String[] args) {
         TV tv = new TV();
         new Player(tv).start();
         new Watcher(tv).start();
     }
 }
 //生产者 - 演员
 class Player extends Thread {
     TV tv;
     Player(TV tv) {
         this.tv = tv;
     }
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             if (i%2==0) {
                 this.tv.play("节目1");
             } else {
                 this.tv.play("节目2");
             }
         }
     }
 }
 //消费者 - 观众
 class Watcher extends Thread {
     TV tv;
     Watcher(TV tv) {
         this.tv = tv;
     }
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             tv.watch();
         }
     }
 }
 //产品 - 节目
 class TV {
     //演员表演,观众等待 T
     //观众观看,演员等待 F
     String voice;//表演的节目
     boolean flag = true;
     //表演
     public synchronized void play(String voice) {
         if (!flag) {
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
         System.out.println("演员表演了:"+voice);
         //通知观众观看
         this.notifyAll();
         this.voice = voice;
         this.flag = !this.flag;
     }
     //观看
     public synchronized void watch() {
         if (flag) {
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
         System.out.println("观看了:"+voice);
         //通知演员表演
         this.notifyAll();
         this.flag = !this.flag;
     }
 }

6. 线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

  • 好处:

    • 提高响应速度(减少了创建新线程的时间)

    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    • 便于线程管理(...)

      • corePoolSize:核心池的大小

      • maximumPoolSize:最大线程数

      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

使用:

  • JDK 5.0起提供了线程池相关API: ExecutorService和Executors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable

    • <T> Future<T>submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable

    • void shutdown():关闭连接池

  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

/*测试线程池*/
 public class Pool {
     public static void main(String[] args) {
         //1.创建服务,创建线程池
         //newFixedThreadPool参数为:线程池大小
         ExecutorService service = Executors.newFixedThreadPool(10);
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
         //2.关闭连接
         service.shutdown();
     }
 }
 class MyThread implements Runnable {
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName());
     }
 }

 

posted @ 2021-08-18 11:39  miku_moe  阅读(43)  评论(0)    收藏  举报