多线程

多线程详解

线程简介

  • 程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

  • 进程:执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位。

  • 线程:一个进程可以包含若干个线程,至少包含一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

    注意:很多 多线程 是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的服务器,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以有同时执行的错局。

一些核心概念:

  • 线程就是独立的执行路径;

  • 在程序执行时,即使没有自己创建线程,后台也会有多个线程,如主线程(main()线程,用户线程)、gc线程(垃圾回收,守护线程);

  • main()被成为主线程,为系统的入口,用来执行整个程序;

  • 在一个进程中,如果开辟了多个线程,线程的执行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序是不能认为被干预的;

  • 对同一份资源操作时,回存在资源抢夺的问题,需要加入并发控制;

  • 线程会带来额外的开销,如cpu调度时间,并发控制开销;

  • 每个线程在自己的工作内存交互,内存控制不当,会造成数据不一致

线程创建(三种方式)

继承Thread类(重点)

    1. 自定义线程类继承Thread类

    2. 重写run()方法**,编写线程执行体

    3. 创建线程对象,调用start()方法启动线程

 package com.threads;
 //创建方式一:继承Thread类,重写run方法,调用start方法
 public class TestThread extends Thread {
     public static void main(String[] args) {
         //创建线程对象
         TestThread testThread = new TestThread();
         testThread.start();
 
         for (int i = 0; i < 200; i++) {
             System.out.println("我很好");
        }
    }
 
     @Override
     public void run() {
         //线程体
         for (int i = 0; i < 200; i++) {
             System.out.println("你好");
        }
    }
 }
 //输出结果:你很好,我很好 相互掺杂 说明这两个线程一起执行的

案例:下载网络图片

 package com.threads;
 
 import org.apache.commons.io.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 
 public class TestThreads02{
     public static void main(String[] args) {
         Threads02 threads01 = new Threads02("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png", "bd.png");
         Threads02 threads02 = new Threads02("https://img-home.csdnimg.cn/images/20201124032511.png", "csdn.png");
         Threads02 threads03 = new Threads02("https://i0.hdslb.com/bfs/archive/ffc9365c13d04c7b1e5dc25ab1346ca00b02a147.png", "bilibili.png");
         threads01.start();
         threads02.start();
         threads03.start();
    }
 }
 
 class Threads02 extends Thread{
     private String url;
     private String name;
     public Threads02(String url,String name){
         this.url=url;
         this.name=name;
    }
     @Override
     public void run() {
         WebDownLoader webDownLoader = new WebDownLoader();
         webDownLoader.downLoader(url,name);
         System.out.println("成功下载了文件名为:"+name+"的照片");
    }
 }
 
 //下载器
 class WebDownLoader{
     public void downLoader(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,downLoad方法出现问题");
        }
    }
 }
 /* 下载顺序不是按照写的顺序下载的   多线程是同步下载照片
 成功下载了文件名为:bilibili.png的照片
 成功下载了文件名为:bd.png的照片
 成功下载了文件名为:csdn.png的照片
  */
 

注意:线程不一定立即执行,cpu会安排调度

 

实现Runnable接口(重点)

    1. 定义MyRunnable类实现Runnable接口

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

    3. 创建线程对象,调用start()方法启动线程 (启动线程:传入目标对象+Thread对象.start() )

 package com.threads;
 
 public class TestThreads03 implements Runnable {
     @Override
     public void run() {
         for (int i = 0; i < 200; i++) {
             System.out.println("你好你好");
        }
    }
 
     public static void main(String[] args) {
         TestThreads03 testThreads03 = new TestThreads03();
         new Thread(testThreads03).start();
 
         for (int i = 0; i < 2000; i++) {
             System.out.println("同好同好");
        }
    }
 }
 //输出结果:同好同好和你好你好相互掺杂输出
 
 

 

推荐使用 实现Runnable接口的方法,可以避免了单继承的局限性,灵活方便,方便同一个对象被多个线程使用

实例:实现Runnable接口 方便同一个对象被多个线程使用

 package com.threads;
 
 //多个线程同时操作同一个对象
 //买火车票的例子
 
 //最后根据输出结果发现问题:多个线程操作同一个资源时,线程不安全,出现数据紊乱
 public class TestThreads04 implements Runnable {
     private int ticket=10;
     @Override
     public void run() {
         //Thread.currentThread().getName() :获取对象的名字
         while (true){
             //模拟延时
             try {
                 Thread.sleep(100);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
             System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票");
             if (ticket<=0){
                 break;
            }
        }
 
    }
 
     public static void main(String[] args) {
         TestThreads04 ticket = new TestThreads04();
         new Thread(ticket,"小超").start();
         new Thread(ticket,"老师").start();
         new Thread(ticket,"牛牛").start();
    }
 }
 /* 输出结果:
 小超拿到了第9张票
 牛牛拿到了第10张票
 老师拿到了第10张票
 小超拿到了第8张票
 牛牛拿到了第7张票
 老师拿到了第6张票
 小超拿到了第5张票
 牛牛拿到了第4张票
 老师拿到了第3张票
 牛牛拿到了第1张票
 小超拿到了第2张票
 老师拿到了第0张票
  */

龟兔赛跑:

  1. 首先来个赛道距离。然后要距离终点越来越近

  2. 判断比赛是否结束

  3. 打印出胜利者

  4. 龟兔赛跑开始

  5. 故事中乌龟是赢的,兔子要睡觉,所以我们模拟兔子睡觉

  6. 终于,乌龟取得胜利

 package com.threads;
 /*
 龟兔赛跑:
 
 1. 首先来个赛道距离。然后要距离终点越来越近
 2. 判断比赛是否结束
 3. 打印出胜利者
 4. 龟兔赛跑开始
 5. 故事中乌龟是赢的,兔子要睡觉,所以我们模拟兔子睡觉
 6. 终于,乌龟取得胜利
  */
 public class TestRace {
 
     public static void main(String[] args) {
         Race race = new Race();
         new Thread(race,"兔子").start();
         new Thread(race,"乌龟").start();
 
    }
 }
 class Race implements Runnable {
     private int track = 50;     //赛道50米
     private String winner;
 
     @Override
     public void run() {
         while (true) {
             for (int i = 1; i <= track; i++) {
                 //模拟兔子睡觉 延时
                 if (Thread.currentThread().getName().equals("兔子") && i%20==0){  //每跑20步,延时5毫秒
                     try {
                         Thread.sleep(5);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                         System.out.println("模拟延时出现错误");
                    }
                }
                 //判断比赛是否结束
                 boolean flag=gameOver(i);
                 if (flag){
                     break;
                }
                 System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
            }
        }
    }
     //判断是否完成比赛
     private boolean gameOver(int i){
         //判断是否有胜利者
         if (winner!=null){
             return true;
        }{
             if (i >= track) {
                 winner = Thread.currentThread().getName();
                 System.out.println(winner + "获得了胜利");
                 return true;
            }
        }
         return false;
    }
 }
 //结果是每次乌龟取得胜利

 

实现Callable接口(了解)

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

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

  3. 创建目标对象

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

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

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

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

 

演示:利用callable下载图片案例

 package com.threads;
 
 import org.apache.commons.io.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.util.concurrent.*;
 
 public class TestThreads05 {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         Threads05 t1 = new Threads05("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png", "bd.png");
         Threads05 t2 = new Threads05("https://img-home.csdnimg.cn/images/20201124032511.png", "csdn.png");
         Threads05 t3 = new Threads05("https://i0.hdslb.com/bfs/archive/ffc9365c13d04c7b1e5dc25ab1346ca00b02a147.png", "bilibili.png");
         
         //3. 创建执行服务:
         ExecutorService ser= Executors.newFixedThreadPool(3);
         
         //4. 提交执行:
         Future<Boolean> result1=ser.submit(t1);
         Future<Boolean> result2=ser.submit(t2);
         Future<Boolean> result3=ser.submit(t3);
         
         //5. 获取结果: 需要抛出异常
         boolean r1=result1.get();
         
         //6. 关闭服务:
         ser.shutdownNow();
    }
 }
 class Threads05 implements Callable<Boolean>{
 
     private String url;
     private String name;
     
     //构造器
     public Threads05(String url, String name) {
         this.url=url;
         this.name=name;
    }
 
 
     @Override
     public Boolean call() throws Exception {
         new WebDownLoader02().downLoader(url,name);
         System.out.println("成功下载了文件名为"+name+"的文件");
         return true;
    }
 }
 //图片下载器
 class WebDownLoader02{
     public void downLoader(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("图片下载器出现错误");
        }
    }
 }
 /*
 成功下载了文件名为bd.png的文件
 成功下载了文件名为bilibili.png的文件
 成功下载了文件名为csdn.png的文件
  */
 

静态代理

举例:

  • 你:真实角色

  • 婚庆公司:代理你,帮你处理结婚的事

  • 结婚:都实现结婚接口即可

演示:实现静态代理 (static agent) 对比Thread

 package com.threads;
 //静态代理演示
 public class TestStaticAgent {
     public static void main(String[] args) {
         new WeddingCompany(new MySelf()).HappyMarry();
    }
 }
 //结婚 接口
 interface Marry{
     void HappyMarry();
 }
 //真实角色
 class MySelf implements Marry{
 
     @Override
     public void HappyMarry() {
         System.out.println("我要结婚");
    }
 }
 //代理角色
 class WeddingCompany implements Marry{
     private Marry target;
 
     public WeddingCompany(MySelf target) {
         this.target = target;
    }
 
     @Override
     public void HappyMarry() {
         before();
         this.target.HappyMarry();
         after();
    }
 
     private void before() {
         System.out.println("布置婚礼现场");
    }
     private void after() {
         System.out.println("要代理费用");
    }
 }

静态代理模式总结:

真实对象和代理对象都要实现同一个接口

代理对象要代理真实角色

好处:

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

真实对象专注做自己的事情

Lamda表达式

λ :希腊字母表中排序第十一位的字母,英文名字为Lambda

它的好处:是为了避免匿名内部类定义过多,可以让代码看起来更加简洁

其实质是属于函数式编程的概念

函数式接口的定义:

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

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

 package com.threads;
 
 public class TestLamda {
     public static void main(String[] args) {
 
         /*匿名内部类02
         ILove iLove = new ILove() {
             @Override
             public void Love() {
                 System.out.println("i love you 02");
             }
         };
         iLove.Love();*/
 
         //Lamda表达式 单行代码
         ILove ilove=()-> System.out.println("i love you 03");
         ilove.Love();
         //Lamda表达式 单多行代码
         ILove ilove02=()-> {
             System.out.println("i love you 03");
             System.out.println("ha ha ha ");
        };
         ilove02.Love();
    }
 }
 //接口类
 interface ILove{
     void Love();
 }
 /*实现类01
 class I implements ILove{
 
     @Override
     public void Love() {
         System.out.println("i love you 01");
     }
 }*/
 

注意:

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

  • 前提是接口是函数式接口

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

线程状态

 

细化后:

 

线程停止

  • 不推荐使用JDK提供的stop()、destroy() 方法,(已废弃)

  • 推荐线程自己停止下来

  • 建议使用一个标志位进行终止变量,当flag=flase,则终止线程运行。

 package com.threads;
 //测试线程停止
 //1. 建议线程正常停止--->利用次数,不建议死循环
 //2. 建议使用标志位--->定义一个标志位
 //3. 不使用stop()、destroy() 方法,(已废弃)
 
 public class TestThreadStop {
     public static void main(String[] args) {
         TestStop testStop = new TestStop();
         new Thread(testStop).start();
 
         for (int i = 0; i < 2000; i++) {
             if (i==1900){
                 testStop.stop();
                 System.out.println("该线程停止了");
            }
             System.out.println("main"+i);
        }
    }
 }
 class TestStop implements Runnable{
     //设置一个标志位
     private boolean flag=true;
     @Override
     public void run() {
         int i=0;
         while (flag){
             System.out.println("run-------"+i++);
        }
    }
 
     //设置一个公开的方法停止线程
     public void stop(){
         this.flag=false;
    }
 }
 

线程休眠

  • sleep(时间) 指定当前线程阻塞的毫秒数-->1000ms=1s

  • sleep存在异常InterruptedException

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

  • sleep可以模拟网络延时、倒计时等

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

演示1:网络延时

package com.threads;

public class TestThreadSleep {
    public static void main(String[] args) {
        ThreadSleep threadSleep = new ThreadSleep();
        new Thread(threadSleep,"张三").start();
        new Thread(threadSleep,"李四").start();
        new Thread(threadSleep,"王五").start();
    }
}
class ThreadSleep implements Runnable{

    private int i=10;
    @Override
    public void run() {
        while (true){
            if (Thread.currentThread().getName().equals("王五")||Thread.currentThread().getName().equals("张三")){
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("模拟延时出现问题");
                }
            }
            System.out.println(Thread.currentThread().getName()+"拿了第"+i--+"张票");
            if (i<0){
                break;
            }
        }
    }
}
/*输出结果
王五拿了第8张票
张三拿了第9张票
李四拿了第10张票
李四拿了第7张票
李四拿了第6张票
李四拿了第5张票
李四拿了第4张票
李四拿了第3张票
李四拿了第2张票
李四拿了第1张票
李四拿了第0张票
王五拿了第-1张票
张三拿了第-2张票
*/

演示二:倒计时

package com.threads;

public class TestThreadSleep02 {
    public static void main(String[] args) {
        ThreadSleep02 threadSleep02 = new ThreadSleep02();
        new Thread(threadSleep02).start();
    }
}
class ThreadSleep02 implements Runnable{

    @Override
    public void run() {
        for (int i = 10; i >= 0; i--) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("模拟延时出现错误");
            }
            System.out.println(i);
        }
    }
}

演示三:读系统时间

package com.threads;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestThreadSleep03 {
    public static void main(String[] args) throws InterruptedException {
        Date date = new Date(System.currentTimeMillis());  //获取系统当前时间
        while (true){
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH-mm-ss").format(date));
            date=new Date(System.currentTimeMillis());  //更新当前系统时间
        }
    }
}


线程礼让

  • 礼让线程(yield),让当前正在执行的线程停止,但不阻塞

  • 将线程从运行状态转为就绪状态(比如有两个线程,一个线程执行呢,礼让之后,变为就绪状态,两个线程依旧是同一个起跑线,下一个调度谁,看cpu心情)

  • 让cpu重新调度,所以礼让不一定成功,看cpu心情

package com.threads;

public class TestYield {
    public static void main(String[] args) {
        Yield yield = new Yield();
        new Thread(yield,"A").start();
        new Thread(yield,"B").start();
    }
}
class Yield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程执行结束");
    }
}
/*  礼让成功
A线程开始执行
B线程开始执行
B线程执行结束
A线程执行结束
礼让失败
A线程开始执行
A线程执行结束
B线程开始执行
B线程执行结束
 */

线程强制执行

Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞 可以想象成插队

package com.threads;

public class TestJoin {
    public static void main(String[] args) {

        Join01 join01 = new Join01();
        Thread thread = new Thread(join01);
        thread.start();

        for (int i = 0; i < 3; i++) {
            System.out.println("我在执行");
            if (i==1){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class Join01 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            System.out.println("我来插队了");
        }
    }
}
/* 输出结果
我在执行
我在执行
我来插队了
我来插队了
我在执行
 */

线程状态观测

Thread.Stade

线程状态。线程可以处于以下状态之一:

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

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

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

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

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

  • TERMINATED 已退出的线程处于此状态

 package com.threads;
 
 public class TestState {
     public static void main(String[] args) throws InterruptedException {
         State01 state01 = new State01();
         Thread thread = new Thread(state01);
 
         //观察状态 :启动前
         Thread.State state = thread.getState();
         System.out.println(state);   //NEW
 
         //启动后
         thread.start();
         state=thread.getState();
         System.out.println(state);  //RUNNABLE
 
         //执行过程中
         while (state!=Thread.State.TERMINATED){
             Thread.sleep(100);
             state=thread.getState();  //更新线程状态
             System.out.println(state);
        }
    }
 }
 class State01 implements Runnable{
 
     @Override
     public void run() {
         for (int i = 0; i < 2; i++) {
             try {
                 Thread.sleep(100);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         System.out.println("------");
    }
 }
 /*
 NEW
 RUNNABLE
 TIMED_WAITING
 TIMED_WAITING
 ------
 TERMINATED
  */

线程优先级

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

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

    • Thread.MIN_PRIOPITY=1 //最小优先级

    • Thread.MAX_PRIOPITY=10 //最大优先级

    • Thread.NORM_PRIOPITY=5 //默认的优先级

  • 使用以下方式设置或者获取优先级

    getPriority() setPriority(int ***)

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

 

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

package com.threads;

public class TestPriority {
    public static void main(String[] args) {
        //主线程
        System.out.println(Thread.currentThread().getName()+"被调用");

        MyPriority myPriority = new MyPriority();
        Thread thread01 = new Thread(myPriority,"默认");
        thread01.start();

        Thread thread02 = new Thread(myPriority,"最小");
        Thread thread03 = new Thread(myPriority,"最大");
        Thread thread04 = new Thread(myPriority," 二");

        thread02.setPriority(Thread.MIN_PRIORITY);
        thread02.start();

        thread03.setPriority(Thread.MAX_PRIORITY);
        thread03.start();

        thread04.setPriority(2);
        thread04.start();

    }
}
class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"  被调用了");
    }
}
/*
main被调用
最大  被调用了
默认  被调用了
最小  被调用了
 二  被调用了
 */

守护线程

daemon 守护线程

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

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

  • 虚拟机不用等待守护线程(例如gc线程)执行完毕

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

package com.threads;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);  //设置god为守护线程,默认都是false   正常的都是用户线程
        thread.start();           //守护线程启动
        new Thread(you).start();  //用户线程启动
    }
}
//上帝   守护线程
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("我一直都在");
        }
    }
}
//你  用户线程
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("人生在世,开心每一天");
        }
        System.out.println("---------------------");
    }
}

线程同步

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

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

形成条件:队列和锁

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

但是也会存在以下问题:

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

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

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

三个不安全的案例

案例一:不安全的买票

 package com.threads.syn;
 
 //案例一:买票
 public class UnSafe01 {
     public static void main(String[] args) {
         BuyTicket buyTicket = new BuyTicket();
         Thread thread01 = new Thread(buyTicket,"小明");
         Thread thread02 = new Thread(buyTicket,"小菲");
         Thread thread03 = new Thread(buyTicket,"牛牛");
 
         thread01.start();
         thread02.start();
         thread03.start();
 
    }
 }
 //买票
 class BuyTicket implements Runnable{
 
     private int ticket=10;
     boolean flag=true;  //外部停止方式
 
     @Override
     public void run() {
 
         while (flag){
             buy();
        }
 
    }
 //判断是否有票
     private void buy(){
         if (ticket<=0){
             flag=false;
             return;
        }
         //模拟延时 放大问题
         try {
             Thread.sleep(100);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
         System.out.println(Thread.currentThread().getName()+"买了第"+ticket--+"张票");
    }
 }
 /*
 牛牛买了第10张票
 小明买了第9张票
 小菲买了第9张票
 小明买了第8张票
 牛牛买了第6张票
 小菲买了第7张票
 小明买了第5张票
 小菲买了第4张票
 牛牛买了第3张票
 小明买了第2张票
 小菲买了第1张票
 牛牛买了第0张票
 小明买了第-1张票
  */
 

案例二:不安全的取钱

package com.threads.syn;

//案例二:不安全的取钱
//两个人去银行同时取一个账户的钱
public class UnSafe02 {
    public static void main(String[] args) {
        Account account = new Account(100, "基金理财");

        Back i = new Back(account, 50, "我");
        Back girl = new Back(account, 100, "女朋友");

        i.start();
        girl.start();
    }
}


//账户
class Account{
    int money;  //账户余额
    String name;  //卡里钱的用处

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//银行  模拟取款
class Back extends Thread{

    Account account;//账户
    int drawingMoney;  //取了多少钱
    int nowMoney;  //你手里的钱
    public Back(Account account,int drawingMoney,String name){
        super(name);
        this.account=account;
        this.drawingMoney=drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        //先判断有没有钱
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+" 余额不足,取不了");
            return;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("模拟延时出现问题");
        }

        account.money=account.money-drawingMoney;  //取完后卡里还有这些钱
        nowMoney=nowMoney+drawingMoney;  //你手里的钱

        System.out.println(Thread.currentThread().getName()+"  手里有"+nowMoney);
        System.out.println("卡内余额为"+account.money);

    }
}
/*
我  手里有50
女朋友  手里有100
卡内余额为-50
卡内余额为-50
 */

案例三:不安全的集合

 package com.threads.syn;
 
 import java.util.ArrayList;
 
 public class UnSafe03 {
     public static void main(String[] args) throws InterruptedException {
         ArrayList<String> list = new ArrayList<String>();
         for (int i = 0; i <10000 ; i++) {
             new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
         Thread.sleep(300);
         System.out.println(list.size());
    }
 }
 //输出<10000    

同步方法及同步块

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

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

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

 

同步块:synchronized(Obj){}

Obj 称之为同步监视器

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

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

同步监视器的执行过程:

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

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

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

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

修改第一个案例:

//synchronized  同步方法
private synchronized void buy(){

修改第二个案例:

 public void run() {
        synchronized (account){
            //先判断有没有钱
            if (account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+" 余额不足,取不了");
                return;
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("模拟延时出现问题");
            }

            account.money=account.money-drawingMoney;  //取完后卡里还有这些钱
            nowMoney=nowMoney+drawingMoney;  //你手里的钱

            System.out.println(Thread.currentThread().getName()+"  手里有"+nowMoney);
            System.out.println("卡内余额为"+account.money);

        }

    }

锁的对象就是变化的量,需要增删改的对象

修改第三个案例:

package com.threads.syn;

import java.util.ArrayList;

public class UnSafe03 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> list = new ArrayList<String>();
        for (int i = 0; i <100000 ; i++) {
            //Lambda表达式
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(300);
        System.out.println(list.size());
    }
}

死锁

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

 package com.threads.syn;
 
 public class TestDeadlock {
     public static void main(String[] args) {
         Painting p1 = new Painting(0);
         Painting p2 = new Painting(1);
 
         new Thread(p1,"明明").start();
         new Thread(p2,"菲菲").start();
 
    }
 }
 
 //镜子
 class Mirror{
 
 }
 //口红
 class Rouge{
 
 }
 
 //化妆
 class Painting implements Runnable{
 
     //用static 以确保需要的资源只有一份
     private static Mirror mirror=new Mirror();
     private static Rouge rouge=new Rouge();
 
     int choice;  //定义选择
     
     public Painting(){
    }
     public Painting(int choice) {
         this.choice = choice;
    }
 
 
     @Override
     public void run() {
         //化妆
         if (choice==0){
             synchronized(mirror){
                 System.out.println(Thread.currentThread().getName()+"有镜子");
                 try {
                     Thread.sleep(2000);
                } catch (InterruptedException e) {
                     e.printStackTrace();
                     System.out.println("模拟延时出现问题");
                }
                 synchronized (rouge){
                     System.out.println(Thread.currentThread().getName()+"想要口红");
                }
            }
        }else {
             synchronized(rouge){
                 System.out.println(Thread.currentThread().getName()+"有口红");
                 try {
                     Thread.sleep(5000);
                } catch (InterruptedException e) {
                     e.printStackTrace();
                     System.out.println("模拟延时出现问题");
                }
                 synchronized (mirror){
                     System.out.println(Thread.currentThread().getName()+"想要镜子");
                }
            }
        }
    }
 }
 

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

只要想办法破其中的任意一个或多个条件就可以避免死锁的发生

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

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

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

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

线程协作

生产者消费者问题:

 

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

 

在生产者消费者问题中,仅有synchronized是不够的,它可以阻止并发更新同一个共享资源,实现了同步,但是不能用来实现不同进程之间的消息传递。

 

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

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

两个解决方式:

解决方式一:

 

 

管程法:

 package com.threads.syn;
 
 public class TestPC {
     public static void main(String[] args) {
         Buffer buffer = new Buffer();
         new Productor(buffer).start();
         new Customer(buffer).start();
    }
 }
 //生产者
 class Productor extends Thread{
     Buffer buffer;
     public Productor(Buffer buffer){
         this.buffer=buffer;
    }
     //生产
     @Override
     public void run() {
         for (int i = 1; i < 100; i++) {
             System.out.println("放了第"+i+"个苹果");
             try {
                 buffer.produce(new Apple(i));
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
    }
 }
 
 //消费者
 class Customer extends Thread{
 
     Buffer buffer;
     public Customer(Buffer buffer){
         this.buffer=buffer;
    }
     //消费
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             try {
                 System.out.println("拿了第"+buffer.consume().id+"个苹果");
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
 
        }
    }
 }
 
 //产品
 class Apple{
     int id;  //产品标号
 
     public Apple(int id) {
         this.id = id;
    }
 }
 
 
 //缓冲区
 class Buffer{
     Apple[] container=new Apple[10];  //容器 里面有10个位子
     int number=0;  //初始的时候里面有0个
 
     //生产者放苹果
     public synchronized void produce(Apple apple) throws InterruptedException {
         //如果容器满了,就需要等待消费者取苹果
         if (number==container.length){
             //通知消费者取苹果
             this.wait();
        }
         //容器里面没有满 放苹果
         container[number]=apple;
         number++;
 
         //可以通知消费者消费了
         this.notifyAll();
    }
 
 
     //消费者取苹果
     public synchronized Apple consume() throws InterruptedException {
         //如果容器里没有苹果,就需要等待生产者放苹果
         if (number==0){
             //通知生产者放苹果
             this.wait();
        }
         //容器里有苹果,拿苹果
         number--;
         Apple apple=container[number];
 
         //吃完了 通知生产者生产
         this.notifyAll();
         return apple;
 
    }
 }

 

解决方式二:

 

 package com.threads.syn;
 
 public class TestPC03 {
     public static void main(String[] args) {
         Process process = new Process();
         new Serve(process).start();
         new Consume(process).start();
    }
 }
 
 //生产者 做菜
 class Serve extends Thread{
     Process process;
     public Serve(Process process){
         this.process=process;
    }
 
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             if (i%2==0){
                 this.process.cook("红烧肉");
            }else {
                 this.process.cook("炖排骨");
            }
        }
    }
 }
 
 //消费者 吃菜
 class Consume extends Thread{
     Process process;
     public Consume(Process process){
         this.process=process;
    }
 
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             this.process.eat();
        }
    }
 }
 
 //产品   菜
 class Process{
     String food;
     boolean flay=true;
     //有菜 客人吃 厨师等T       没有菜   厨师做 客人等F
 
     //做菜
     public synchronized void cook(String food){
         //如果有菜 true
         while (flay){
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         //没菜 做菜
         System.out.println("厨师做了"+food);
         //做完了,通知客人吃
         this.notifyAll();  //通知唤醒
         this.food=food;
         this.flay=!this.flay;
    }
 
     //吃菜
     public synchronized void eat(){
         //如果没有菜 就等待
         while (!flay){
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         //有了,就吃
         System.out.println("吃了"+food);
         this.notifyAll();
         this.flay=!this.flay;
    }
 }

 

使用线程池:

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

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放入池中。可以避免频繁创建销毁、实现重复使用。

好处:

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

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

  • 便于线程管理

    • corePoolSize :核心池的大小

    • maximumPoolSize:最大线程数

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

 

线程池相关API:ExxcutorService 和 Executors

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

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

 package com.threads.syn;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 public class TestPool {
     public static void main(String[] args) {
         MyPool myPool = new MyPool();
 
         //创建服务,创建线程池
         ExecutorService service = Executors.newFixedThreadPool(10);
 
         service.execute(myPool);
         service.execute(myPool);
         service.execute(myPool);
         service.execute(myPool);
 
         //用完 关闭线程池
         service.shutdown();
    }
 }
 class MyPool implements Runnable{
 
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"   ");
    }
 }

 

posted @ 2022-04-04 16:14  超、自律即自由  阅读(16)  评论(0编辑  收藏  举报