多线程详解


1. 线程简介

 简介
任务 本质上在同一时间做了一件事情。吃饭玩手机,开车打电话。
进程(Process) 在操作系统中运行的程序。是执行程序的一次执行过程,动态概念。
程序(Thread) 指令和数据的有序集合,本身没有任何运行的含义,静态概念。
线程 一个进程中可以包含若干个线程,一个进程中至少有一个线程。
多线程 从软件或者硬件上实现多个线程并发执行的技术。王者荣耀开黑。

普通方法调用和多线程调用,如图:

2. 线程实现(重点)

1. 继承Thread class

//   步骤:
//   1. 自定义线程类继承Thread类
//   2. 重写run()方法,编写线程执行体
//   3. 创建线程对象,调用start()方法启动
复制代码
//注意:线程开启不一定不一定立即执行,由CPU调度执行
//创建线程方式一: 继承Thread类, 重写run()方法,调用Start()开启线程。(start()方法线程同时执行)
public class TestThread extends Thread {
    @Override
    public void run() {        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码----run----" + i);
        }
    }
​
    public static void main(String[] args) {        //main线程, 主线程
        TestThread t1 = new TestThread();
        t1.start();                  //创建线程对象,调用start()方法开启线程
//        new t1().start();        //创建线程对象,调用start()方法开启线程
//        new t1().run();        //先执行run方法。
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程----main----" + i);
        }
    }
}
复制代码

 


2. 实现 Runable 接口

//   步骤
//   1. 定义MyRunnable类实现Runnable接口 
//   2. 实现run()方法,编写线程执行体
//   3. 创建线程对象,调用start()方法启动线
复制代码
/创建线程方式2: 实现runnable接口, 重写run方法, 执行线程需要将runnable实现类对象丢入Thread中, 调用Start()方法。
//runnable里面没有start()方法, 所以需要开启线程的时候, 需要借用Thread类中的Start()方法。
public class TestRunnable implements Runnable {
​
    @Override
    public void run() {        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码----run----" + i);
        }
    }
​
    public static void main(String[] args) {        //main线程, 主线程
        TestRunnable T3 = new TestRunnable();//创建runnable接口的实现类对象
​
​
//        Thread thread = new Thread(T3);       //创建线程对象,通过线程对象代理开启我们的线程。
//        thread.start();                       //创建线程对象,通过线程对象代理开启我们的线程。
        new Thread(T3).start();               //创建线程对象,通过线程对象代理开启我们的线程。
​
​
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程----main----" + i);
        }
    }
}
复制代码

 


扩展:网页下载器

  1. 下载jar包,编写工具类

复制代码
class WebDownloader {        //文件下载工具类
    public void downloader(String url, String name) {    //远程路径  下载名字
        try {
            //FileUtils: 文件工具,复制url到文件
            //Commons IO: 针对开发IO流功能的工具库
            FileUtils.copyURLToFile(new URL(url), new File(name));  //将网络上的一个网页地址变为一个文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法下载异常。");
        }
    }
}
复制代码

 

  1. 继承Thread类,重写run方法

复制代码
class TestThread extends Thread {          //练习Thread, 实现多线程同步下载图片。
    private String url;     //网络图片地址
    private String name;    //保存的文件名
​
    public TestThread(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);
    }
​
}
复制代码

 

  1. 创建类对象,实现多线程

     public static void main(String[] args) {        //main方法
        TestThread2 T1 = new TestThread2("https://img3.doubanio.com/view/note/l/public/p83642840.webp","1.jpg");
        TestThread2 T2 = new TestThread2("https://img3.doubanio.com/view/note/l/public/p83642840.webp","2.jpg");
        TestThread2 T3 = new TestThread2("https://img3.doubanio.com/view/note/l/public/p83642840.webp","3.jpg");
        T1.start();
        T2.start();
        T3.start();
    }

 

完整代码

通过实现Thread类实现
复制代码
class TestThread extends Thread {          //练习Thread, 实现多线程同步下载图片。
    private String url;     //网络图片地址
    private String name;    //保存的文件名
​
    public TestThread(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);
    }
​
    public static void main(String[] args) {        //main方法
        TestThread T1 = new TestThread("https://img3.doubanio.com/view/note/l/public/p83642840.webp","1.jpg");
        TestThread T2 = new TestThread("https://img3.doubanio.com/view/note/l/public/p83642840.webp","2.jpg");
        TestThread T3 = new TestThread("https://img3.doubanio.com/view/note/l/public/p83642840.webp","3.jpg");
        T1.start();
        T2.start();
        T3.start();
    }
}
复制代码

 

通过实现Runnalbe接口实现
复制代码
class Testrunnable implements Runnable {          //练习Thread, 实现多线程同步下载图片。
    private String url;     //网络图片地址
    private String name;    //保存的文件名
​
    public Testrunnable(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);
    }
​
    public static void main(String[] args) {        //main方法
        Testrunnable T4 = new Testrunnable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "4.jpg");
        Testrunnable T5 = new Testrunnable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "5.jpg");
        Testrunnable T6 = new Testrunnable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "6.jpg");
​
        new Thread(T4).start();
        new Thread(T5).start();
        new Thread(T6).start();
    }
}
复制代码

 

扩展:龟兔赛跑

复制代码
public class Race implements Runnable {
    
    private static String winner; //胜利者
    
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //模拟兔子休息200ms
            if ((Thread.currentThread().getName().equals("兔子")) && (i % 10 == 0)) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameOver(i);
            if (flag) {
//                System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"米。");
                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) {
       Race race = new Race();
       new Thread(race, "乌龟").start();
       new Thread(race, "兔子").start();
  }
}
复制代码

 



扩展:购买火车票

复制代码
//多个线程同时操作一个对象
//发现问题: 多个线程操作同一个资源的情况下, 线程不安全, 数据紊乱。
//出现多个人抢到同一张票。
//Thread.currentThread().getName()   查看是哪个线程正在操作(当前运行线程的名字)
public class TestRunnable implements Runnable{
​
    private int ticketNums = 10; //票数
​
    @Override
    public void run() {
        while (true) {
            if (ticketNums < 1) {
                break;
            }
            try {       //模拟延迟。
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"---->>拿到了第"+(ticketNums--)+"票。");
        }
    }
​
    public static void main(String[] args) {
        TestRunnable testThread4 = new TestRunnable();
        //开启3个线程处理同一资源
        new Thread(testThread4,"小明").start();
        new Thread(testThread4,"小红").start();
        new Thread(testThread4,"黄牛").start();
    }
}
复制代码

 

 

初识并发问题

Thread-Runnable的小结:

继承Thread类实现Runnable接口
子类继承Thread类具备多线程能力 实现接口Runnable具有多线程能力
启动线程:子类对象.Start() 传入目标对象+Thread对象.Start()
不建议使用:避免OOP单继承局限性 推荐使用:避免OOP单继承局限性,灵活方便,方便同一个对象被多个线程使用。

3. 实现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接口
public class TestCallable implements Callable<Boolean> {//        1. 实现Callable接口,需要返回值类型
    private String url;     //网络图片地址
    private String name;    //保存的文件名

    public TestCallable(String url, String name) {   //构造器
        this.url = url;
        this.name = name;
    }

    @Override//        2. 重写call方法,需要抛出异常
    public Boolean call() {    //下载图片线程的执行体。
        try {
            WebDownloader WebDownloader = new WebDownloader();
            WebDownloader.downloader(url, name);
            System.out.println("下载的文件名为:" + name);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    public static void main(String[] args) {
        //        3. 创建目标对象
        TestCallable C1 = new TestCallable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "call01.jpg");
        TestCallable C2 = new TestCallable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "call02.jpg");
        TestCallable C3 = new TestCallable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "call03.jpg");

        //        4. 创建执行服务,创建三个服务
                ExecutorService ser = Executors.newFixedThreadPool(3);

        //        5. 提交执行, 将线程对象提交
        Future<Boolean> result1 = ser.submit(C1);
        Future<Boolean> result2 = ser.submit(C2);
        Future<Boolean> result3 = ser.submit(C3);

        //        6. 获取结果
        try {
            boolean r1 = result1.get();
            boolean r2 = result2.get();
            boolean r3 = result3.get();
            System.out.println(r1);
            System.out.println(r2);
            System.out.println(r3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
// 7. 关闭服务 ser.shutdownNow(); } }
复制代码

 

3. 线程状态

 

线程方法

方法说明
SetPriority(int newPriority) 更改线程的优先级
Static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待线程停止
Static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程(别用这种方式)
boolean isAlive() 测试线程是否处于活动状态

停止线程

//测试stop
/**
* 1. 建议线程正常停止 --> 利用次数,不建议死循环
* 2. 建议使用标志位 --> 设置一个标志位
* 3. 不要使用 stop() 或者 destroy() 等过时或者 JDK 不建议使用的方法
*/
public class TestStop implements Runnable {

   //1. 设置一个标志位
   private boolean flag = true;

   @Override
   public void run() {
       int i = 0;
       while (flag) {
           System.out.println("run.....Thread....." + i++);
      }
  }

   //2. 设置一个公开的方法停止线程,转换标志位
   public void stop() {
       this.flag = false;
  }

   public static void main(String[] args) {
       P11_TestStop stop = new P11_TestStop();
       Thread thread = new Thread(stop);
       thread.start();

       for (int i = 0; i <= 100; i++) {
           System.out.println("main..."+i);
           if(i==88){
               stop.stop();    //调用 stop() 方法, 改变标志位, 停止线程
               System.out.println("该线程已停止!!!");
          }
      }

  }
}

 

线程休眠

      /**
        * sleep (时间) 指定当前线程阻塞的毫秒数
        * sleep存在异常InterruptedException
        * sleep时间达到后线程进入就绪状态
        * sleep可以模拟网络延时,倒计时等
        * 每一个对象都有一个锁,sleep不会释放锁
         */
// 模拟网络延时: 放大问题的发生性
public class TestSleep_01 implements Runnable{
   private int ticketNums = 10; //票数

   @Override
   public void run() {
       while (true) {
           if (ticketNums < 1) {
               break;
          }
           try {       //模拟延迟。
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           System.out.println(Thread.currentThread().getName()+"---->>拿到了第"+(ticketNums--)+"票。");
      }
  }

   public static void main(String[] args) {
       P12_TestSleep_01 testThread4 = new P12_TestSleep_01();
       //开启3个线程处理同一资源
       new Thread(testThread4,"小明").start();
       new Thread(testThread4,"小红").start();
       new Thread(testThread4,"黄牛").start();
  }
}

 

线程礼让

/**
* 礼让线程,停止执行的线程,但不阻塞。
* 运行状态 --> 就绪状态
* 由 CPU 重新调度, 礼让随机, 可能失败
*/
// 测试礼让线程。
// 礼让不一定成功,看 CPU 心情
public class TestYield {
   public static void main(String[] args) {

       MyYield p1 = new MyYield();
       new Thread(p1, "a").start();
       new Thread(p1, "b").start();
  }
}

class MyYield implements Runnable {
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName() + "......线程开始执行......");
       Thread.yield();         // 线程礼让
       System.out.println(Thread.currentThread().getName() + "......线程停止执行......");
  }
}

 

强制执行 - join

//Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
public class TestJoin implements Runnable {
   @Override
   public void run() {
       for (int i = 0; i <= 200; i++) {
           System.out.println("线程 VIP 来了!!!" + i);
      }
  }

   public static void main(String[] args) {
       // 启动线程
       P14_TestJoin Join = new P14_TestJoin();
       Thread thread = new Thread(Join);
       thread.start();

       // 主线程
       for (int i = 0; i <= 100; i++) {
           if (i == 88) {
               try {
                   thread.join();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
           System.out.println("main......" + i);
      }

  }
}

观测线程状态

/**
* JDK1.8 文档搜索Thread State
* new     尚未启动的线程处于该状态
* runnable     在 Java虚拟机 中执行的线程处于该状态
* blocked     被阻塞等待监听器锁定的线程处于该状态
* waiting     正在等待开一个线程执行特定动作的线程处于该状态
* timed_waiting       正在等待另一个线程执行动作达到指定等待时间的线程处于该状态
* terminated       已退出的线程处于该状态
*/
//观察线程的状态
//创建 就绪 运行 阻塞 死亡
public class TestState {
   public static void main(String[] args) {
       Thread thread = new Thread(()->{
           for (int i = 0; i < 5; i++) {
               try {
                   Thread.sleep(1000);
                   System.out.println("等待中........"+(i+1));
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
           System.out.println("////////");
      });

       //观察状态   thread.getState(), Alt+回车创建变量state
       Thread.State state = thread.getState();
       System.out.println(state);      //NEW

       //观察启动后
       thread.start();
       state = thread.getState();
       System.out.println(state);      //RUN

       while (state != Thread.State.TERMINATED){   //只要线程不终止,就一直输出状态
           try {
               Thread.sleep(200);
               state = thread.getState();      //更新线程状态
               System.out.println(state);      //输出状态
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
}

线程优先级

/**
* 优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度
* 优先级的设定建议在start()调度前
*/
//测试线程优先级
public class TestPriority {
   public static void main(String[] args) {
       //主线程默认优先级
       System.out.println("主线程---" + Thread.currentThread().getName() + " ----> " + Thread.currentThread().getPriority());

       MyPriority myPriority = new MyPriority();
       Thread t1 = new Thread(myPriority);
       Thread t2 = new Thread(myPriority);
       Thread t3 = new Thread(myPriority);
       Thread t4 = new Thread(myPriority);
       Thread t5 = new Thread(myPriority);

       //先设置优先级,在启动
       t1.start();

       t2.setPriority(1);
       t2.start();

       t3.setPriority(4);
       t3.start();

       t4.setPriority(Thread.MAX_PRIORITY);    //Thread.MAX_PRIORITY=10
       t4.start();

       t5.setPriority(8);
       t5.start();

  }
}

class MyPriority implements Runnable {
   @Override
   public void run() {
       //Thread.currentThread().getName() 获取当前运行线程的名字
       //Thread.currentThread().getPriority() 返回当前运行线程的优先级
       System.out.println(Thread.currentThread().getName() + " ----> " + Thread.currentThread().getPriority());
  }
}

守护线程

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

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

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

注意:用户进程执行完毕之后守护进程结束。

//测试守护进程
//上帝守护你
public class TestDaemon {
   public static void main(String[] args) {
       God god = new God();
       Me me = new Me();

       Thread thread = new Thread(god);
       //设置守护进程
       thread.setDaemon(true); //默认是false表示是用户线程, 正常的线程都是用户线程......

       thread.start(); //上帝守护进程启动......
       new Thread(me).start(); //用户线程启动......
  }
}

//上帝
class God implements Runnable{
   @Override
   public void run() {
       while (true){
           System.out.println("上帝保佑着你......");
      }
  }
}


//你
class Me implements Runnable {
   @Override
   public void run() {
       for (int i = 0; i <= 36500; i++) {
           System.out.println("你一生都开心的活着......");
      }
       System.out.println("---- goodbye! world! ----");    // hello world!
  }
}

 

4. 线程同步(重点)

线程同步机制

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

线程同步:多个线程访问同一个对象,利用对象等待池(等待机制)形成队列,挨个使用。

形成条件:队列和锁

队列和锁:线程同步需要队列和锁

锁机制(synchronized):当一个线程获得对象的排它锁 , 独占资源 , 其他线程必须等待 , 使用后释放锁即可 . 存在以下问题 :

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

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

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

三大不安全问题

不安全取钱

//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
   public static void main(String[] args) {
       Account account = new Account(1000, "理财基金");

       Drawing you = new Drawing(account,200,"你");
       Drawing girlFriend = new Drawing(account,900,"girlFriend");

       you.start();
       girlFriend.start();
  }

}

class Account {
   int money;  // 余额
   String name;   // 卡名

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

//银行:模拟取款
class Drawing extends Thread {

   Account account;//账户
   int drawingMoney;//取了多少钱
   int nowMoney;//现在手里多少钱

   public Drawing(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() + "余额不足......");
//           System.out.println(this.getName()+"余额不足......");
      }

       try {
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       account.money = account.money - drawingMoney;   //卡内余额 = 余额 - 你取的钱
       nowMoney = drawingMoney + nowMoney;  //你手里的钱

       System.out.println(account.name+"余额为: "+account.money);
//等同:   Thread.currentThread().getName() ==   this.getName()
       System.out.println(this.getName()+"手里的钱为: "+nowMoney);
  }
}

不安全买票

//不安全的买票
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket buyTicket = new BuyTicket();

       new Thread(buyTicket, "张三").start();
       new Thread(buyTicket, "李四").start();
       new Thread(buyTicket, "黄牛").start();
  }

}

class BuyTicket implements Runnable {

   private int ticketNums = 10; //票
   boolean flag = true; //外部停止方式

   @Override
   public void run() {     //买票
       while (flag) {  //模拟延时
           buy();
      }
  }

   private void buy() {
       if (ticketNums <= 0) {//判断是否有票
           flag = false;
           return;
      }
       try {
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums-- + "张票...");
  }
}

不安全集合

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {
   public static void main(String[] args) {
       List<String> list = new ArrayList<String>();
       for (int i = 0; i < 10000; i++) {
           new Thread(()->{
               list.add(Thread.currentThread().getName());
          }).start();
      }
       try {
           Thread.sleep(3000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(list.size());
  }
}

同步方法及同步块(synchronized)

同步方法

通过 private 关键字来保证数据对象只能被方法访问

同步方法(synchronized) 包括两种用法 : synchronized 方法synchronized 块

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

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

安全买票-同步方法

//安全的买票
//利用同步块处理安全买票事件在【个人练习】
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket2 buyTicket = new BuyTicket2();

       new Thread(buyTicket, "张三").start();
       new Thread(buyTicket, "李四").start();
       new Thread(buyTicket, "黄牛").start();
  }
}

class BuyTicket2 implements Runnable {

   private int ticketNums = 10; //票
   boolean flag = true; //外部停止方式

   @Override
   //synchronized 默认锁的是 this
   public synchronized void run() { //同步方法
       while (flag) {  //模拟延时
           try {
               buy();
               Thread.sleep(100);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
   
   private void buy() throws InterruptedException {
       if (ticketNums <= 0) {//判断是否有票
           flag = false;
           return;
      }
       Thread.sleep(100);
       System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums-- + "张票...");
  }
}

同步块

同步块:synchronized(Obg){ }

Obj 称为同步监视器

1. obj 可以是任何对象,但是使用共享资源作为同步监视器
1. 同步方法中无需指定同步监视器 , 因为同步方法的同步监视器就是this , 就是这个对象本身 , 或者是 class [ 反射中讲解 ]

同步监视器的执行过程

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

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

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

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

安全取钱-同步块

//安全的取钱
//两个人去银行取钱,账户
//利用同步方法处理安全取钱事件在【个人练习】
public class UnsafeBank {
   public static void main(String[] args) {

       Account2 account = new Account2(100, "理财基金");
       Drawing2 you = new Drawing2(account, 50, "你");
       Drawing2 girlFriend = new Drawing2(account, 100, "girlFriend");

       you.start();
       girlFriend.start();
  }
}

class Account2 {
   int money;  // 余额
   String name;   // 卡名

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

//银行:模拟取款
class Drawing2 extends Thread {

   Account2 account;//账户 包含了里面的余额和卡名称
   int drawingMoney;//取了多少钱
   int nowMoney;//现在手里多少钱

   public Drawing2(Account2 account, int drawingMoney, String name) {
       super(name);
       this.account = account;
       this.drawingMoney = drawingMoney;
  }

   //取钱
   //synchronized 默认锁的是 this
   @Override
   public void run() {
       synchronized (account) {
           if (account.money - drawingMoney < 0) {
               System.out.println(Thread.currentThread().getName() + "余额不足......");
//           System.out.println(this.getName()+"余额不足......");
               return;
          }
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }

           account.money = account.money - drawingMoney;   //卡内余额 = 余额 - 你取的钱
           nowMoney = drawingMoney + nowMoney;  //你手里的钱

           System.out.println(account.name + "余额为: " + account.money);
//       Thread.currentThread().getName() ==   this.getName()
           System.out.println(this.getName() + "手里的钱为: " + nowMoney);
      }
  }
}

 

同步方法和同步代码块中的锁对象是什么?

注意:这里的锁处理的是同一个对象,是可以进行增删改的对象。

  1. 对于普通同步方法,锁是当前实例对象,如果有多个实例,锁对象必然不同,无法实现同步

  2. 对于静态同步方法,锁是当前类的 class 对象,有多个实例,但是锁对象是相同的,可以实现同步

个人练习

安全取钱-同步方法

//安全的取钱
//两个人去银行取钱,账户
class P20_MyTest02 {
   public static void main(String[] args) {
       Test01 test01 = new Test01();
       test01.setAccount(1000,600);
       new Thread(test01, "小猫").start();
       new Thread(test01, "小狗").start();
  }
}

//银行:模拟取款
class Test01 implements Runnable {

   private int account;//账户 包含了里面的余额和卡名称
   int drawingMoney;//取了多少钱
   int nowMoney;//现在手里多少钱

   public void setAccount(int account, int drawingMoney) {
       this.account = account;
       this.drawingMoney = drawingMoney;
  }


   //取钱
   //synchronized 默认锁的是 this
   @Override
   public synchronized void run() {
       if (account - drawingMoney < 0) {
           System.out.println(Thread.currentThread().getName() + "余额不足......");
//           System.out.println(this.getName()+"余额不足......");
           return;
      }
       try {
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       account = account - drawingMoney;   //卡内余额 = 余额 - 你取的钱
       nowMoney = drawingMoney + nowMoney;  //你手里的钱

       System.out.println(account + "余额为: " + account);
//       Thread.currentThread().getName() ==   this.getName()
       System.out.println(Thread.currentThread().getName() + "手里的钱为: " + nowMoney);
  }
}

安全买票-同步代码块

//利用同步代码块解决安全买票问题。
//1. 创建共享资源为同步监听器。(锁是当前平台类的 class 对象)
//2. 执行同步代码块。
//3. 执行完毕,解锁同步监视器
//4. 第二个线程访问,发现无锁,后锁定访问.
public class P20_MyTest {
   public static void main(String[] args) {
       xiecheng xc = new xiecheng(100, "火车");  //第一步
       money money = new money(xc,80 ,"黄牛" );
       money money02 = new money(xc,100 ,"大米" );
       Thread t1 = new Thread(money,"张三");
       Thread t2 = new Thread(money02,"李四");
       t1.start();
       t2.start();
  }
}

//平台
class xiecheng {
   static int nums; //票数
   String car; //汽车票

   public xiecheng(int num, String car) {
       this.nums = num;
       this.car = car;
  }
}

class money implements Runnable { //模拟买票

   xiecheng xc; //车票名字
   int go_;    //买了多少
   int tu_;    //剩余多少
   String name;

   public money(xiecheng xc, int go_, String name) {
       this.xc = xc;
       this.go_ = go_;
       this.name = name;
  }

   @Override
   public void run() {
       synchronized (xc) {     //第二步
           if (xc.nums - go_ < 0) {
               System.out.println(Thread.currentThread().getName() + xc.car+"票需要: "+go_);
               System.out.println(xc.car+"票余额不足......");
               return;
          }
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }

           xc.nums = xc.nums - go_;   //卡内余额 = 余额 - 你取的钱
           tu_ = go_ + tu_;  //你手里的钱

           System.out.println(xc.car + "票余额为: " + xc.nums);
//       Thread.currentThread().getName() ==   this.getName()
           System.out.println(Thread.currentThread().getName() + "手里的票为: " + tu_);
      }
  }
}

死锁

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

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

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

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

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

处理死锁的方法: 破其中的任意一个或多个条件就可以避免死锁发生

//死锁: 多个线程相互占用对方需要的资源,然后形成僵持
public class P22_DeadLock {
   public static void main(String[] args) {
       Makeup g1 = new Makeup(0, "灰姑娘");
       Makeup g2 = new Makeup(1, "白雪公主");
       g1.start();
       g2.start();
  }
}

//口红
class Lipstick {
   
}

//镜子
class Mirror {
   
}

//化妆
class Makeup extends Thread {

   //需要的资源只有一份,用 static 修饰
   static Lipstick lipstick = new Lipstick();
   static Mirror mirror = new Mirror();

   int choice; //选择
   String girlName; //使用化妆品的人

   Makeup(int choice, String girlName) {
       this.choice = choice;
       this.girlName = girlName;
  }


   //化妆
   @Override
   public void run() {
       try {
           makeup();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  }

   //死锁情况
   //   //化妆, 相互占用对方的资源, 拿到对方的资源
//   private void makeup() throws InterruptedException {
//       if (choice == 0) {
//           synchronized (lipstick) {       //获得口红锁
//               System.out.println(this.girlName + "获得口红锁...");
//               Thread.sleep(1000);
//               synchronized (mirror) {         // 1秒钟后获得镜子锁
//                   System.out.println(this.girlName + "获得镜子锁...");
//               }
//           }
//       } else {
//           synchronized (mirror) {     //获得镜子锁
//               System.out.println(this.girlName + "获得镜子锁...");
//               Thread.sleep(2000);
//               synchronized (lipstick) {   // 2秒钟后获得镜子锁
//                   System.out.println(this.girlName + "获得口红锁...");
//               }
//           }
//       }
//   }
   
   //解决死锁
   //化妆, 相互占用对方的资源, 拿到对方的资源
   private void makeup() throws InterruptedException {
       if (choice == 0) {
           synchronized (lipstick) {       //获得口红锁
               System.out.println(this.girlName + "获得口红锁...");
               Thread.sleep(1000);
          }
           synchronized (mirror) {         // 1秒钟后获得镜子锁
               System.out.println(this.girlName + "获得镜子锁...");
          }
      } else {
           synchronized (mirror) {     //获得镜子锁
               System.out.println(this.girlName + "获得镜子锁...");
               Thread.sleep(2000);
          }
           synchronized (lipstick) {    // 2秒钟后获得口红锁
               System.out.println(this.girlName + "获得口红锁...");
          }
      }
  }
}

 

Lock(锁)

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

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

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

     

     

测试Lock锁

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class P23_TestLock {
   public static void main(String[] args) {
       TestLock2 testLock2 = new TestLock2();
       new Thread(testLock2).start();
       new Thread(testLock2).start();
       new Thread(testLock2).start();
  }
}

class TestLock2 implements Runnable {

   int tickNums = 10;

   //定义lock锁
   private final ReentrantLock lock = new ReentrantLock();

   @Override
   public void run() {
       while (true) {
           lock.lock();    // 加锁
           try {
               if (tickNums > 0) {
                   try {
                       Thread.sleep(1000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   System.out.println("剩余 "+tickNums--);
              }else {
                   break;
              }
          } catch (Exception e) {
               e.printStackTrace();
          } finally {
               lock.unlock();  // 解锁
          }
      }
  }
}

 

syuchronized 与 Lock 的对比

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

  2. Lock只有代码块锁,synchronized有代码块锁和方法锁 u 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  3. 优先使用顺序: Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

5. 线程通信问题

生产者消费者问题

 

线程同步问题:生产者和消费者共享一个资源,互为依赖,互为条件。

  1. 对于生产者 , 没有生产产品之前 , 要通知消费者等待,而生产了产品之后 , 又需要马上通知消费者消费

  2. 对于消费者 , 在消费之后 , 要通知生产者已经结束消费 , 需要生产新的产品以供消费

在生产者消费者问题中 , 仅有synchronized是不够的

  1. synchronized 可阻止并发更新同一个共享资源 , 实现了同步

  2. synchronized 不能用来实现不同线程之间的消息传递 (通信)

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

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

解决方式1---管称法

并发协作模型 “ 生产者 / 消费者模式 ” --->管程法

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

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

缓冲区 : 消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区 生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出

//测试生产者消费者模式 ---> 利用缓冲区解决: 管称法
//生产者 消费者 产品 缓冲区

public class P25_TestPC {
   public static void main(String[] args) {
       SynContainer synContainer = new SynContainer(); //缓冲区
       new Productor(synContainer).start();
       new Consumer(synContainer).start();
  }
}

//生产者
class Productor extends Thread {

   SynContainer synContainer;

   public Productor(SynContainer synContainer) {
       this.synContainer = synContainer;
  }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           synContainer.push(new Chicken(i));
           System.out.println("生产了" + i + "个产品...");
      }
  }
}

//消费者
class Consumer extends Thread {
   SynContainer synContainer;

   public Consumer(SynContainer synContainer) {
       this.synContainer = synContainer;
  }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println("消费了 ---> " + (synContainer.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的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了*/
       while (count == chickens.length) {
           try {
               this.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       //如果没有满,就需要放入产品
       chickens[count] = chicken;
       count++;
       //通知消费者消费
       this.notifyAll();
  }

   //消费者消费产品
   public synchronized Chicken pop() {
       while (count <= 0) {
           //不能消费时(实际数为0时) 消费者等待 生产
           try {
               this.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       //如果可以消费
       count--;
       Chicken chicken = chickens[count];
       this.notifyAll();
       return chicken;
  }
}

 

解决方式2---信号灯法

//测试生产者消费者问题2: 信号灯法,标志位解决
public class P26_TestPC2 {
   public static void main(String[] args) {
       TV tv = new TV();
       new Player(tv).start();
       new Watcher(tv).start();
  }
}

//生产者--> 演员
class Player extends Thread{
   TV tv;
   public Player(TV tv){
       this.tv=tv;
  }

   @Override
   public void run() {
       for (int i = 1; i <= 20; i++) {
           if(i%2==0){
               this.tv.play("快乐大本营播放中...."+i);
          }else {
               this.tv.play("抖音: 记录美好生活...."+i);
          }
      }
  }
}

//消费者--> 观众
class Watcher extends Thread{
   TV tv;
   public Watcher(TV tv){
       this.tv=tv;
  }

   @Override
   public void run() {
       for (int i = 1; i <= 20; i++) {
           tv.watch();
      }
  }
}

//产品--> 节目
class TV{
   //演员表演,观众等待
   //观众观看,演员等待
   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;
  }
}

线程池

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

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

类似生活中的公共交通工具。

使用线程池

  1. JDK 5.0起提供了线程池相关API:ExecutorService(线程池) 和 Executors

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

    //创建一个大小为10的一个线程池
    ExecutorService service = Executors.newFixedThreadPool(10);
  2. ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

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

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

  3. void shutdown() :关闭连接池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class P27_TestPool {
   public static void main(String[] args) {

       // 1. 创建服务, 创建线程池
       // newFixedThreadPool 参数为线程池大小 线程池大小为10
       ExecutorService service = Executors.newFixedThreadPool(10);

       // 2. 执行命令
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());
       service.execute(new MyThread());

       // 3. 关闭线程池
       service.shutdown();

  }
}

class MyThread implements Runnable {
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
  }
}

好处

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

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

  3. 便于线程管理(....)1. corePoolSize:核心池的大小 2. maximumPoolSize:最大线程数 3. keepAliveTime:线程没有任务时最多保持多长时间后会终止

6. 高级主题

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class P27_ThreadNew {
   public static void main(String[] args) {
       new Thread(new MyThread01(),"Thread").start();
       new Thread(new MyThread02(),"Runnable").start();

       FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread03());
       new Thread(futureTask,"Callable").start();
       try {
           Integer integer = futureTask.get();
           System.out.println(Thread.currentThread().getName()+"---"+integer);
      } catch (InterruptedException e) {
           e.printStackTrace();
      } catch (ExecutionException e) {
           e.printStackTrace();
      }
  }
}

class MyThread01 extends Thread{
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName()+"---MyThread01");
  }
}

class MyThread02 implements Runnable{
   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName()+"---MyThread02");
  }
}

class MyThread03 implements Callable <Integer>{

   @Override
   public Integer call() throws Exception {
       System.out.println(Thread.currentThread().getName()+"---MyThread03");
       return 100;
  }
}
 
 
posted @   爱意便是常态  阅读(158)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示