多线程详解

线程、进程、多线程

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

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

一个进程可能包含多个线程,一个进程至少有一个线程。线程是CPU调度和执行的单位。

创建线程方式

  1. 继承Thread类,重写run方法。创建线程体,调用start开启线程

    public class testThread extends Thread{
        @Override
        public void run(){
            for (int i=0;i<200;i++){
                System.out.println("一个线程");
            }
        }
    
        public static void main(String[] args) {
            testThread testThread = new testThread();
            //start()方法开启线程
            testThread.start();
            for (int i=0;i<1000;i++){
                System.out.println("另一个线程");
            }
        }
    }
    
    

     ​  ​

一个线程与另一个线程交替执行。

2. 定义MyRunnable类实现Runnable接口;实现run()方法,编写线程执行体;创建线程对象,调用start()方法启动线程

 public class testThread2 implements Runnable{
     @Override
     public void run() {
         for (int i=0;i<200;i++){
             System.out.println("一个线程");
         }
     }
 ​
     public static void main(String[] args) {
         //创建runnab接口的实现类对象
         testThread2 testThread2 = new testThread2();
 //        创建线程对象,通过对象来开启线程,代理
 //        Thread thread = new Thread(testThread2);
 //        thread.start();
         
         new Thread(testThread2).start();
         
         for (int i=0;i<1000;i++){
             System.out.println("另一个线程");
         }
     }
 }
 ​

相较于第一种方法继承Thread类,第二种方法实现Runnable接口时,开启线程需要new一个Thread对象,并将Runnable接口传入Thread对象,Thread对象调用start()方法。即new Thread(new Runnable()).start。

推荐使用此方法,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。

 //一个对象
 testThread2 testThread2 = new testThread2();
 ​
 //多个代理
 new Thread(testThread2,"thread1").start();
 new Thread(testThread2,"thread2").start();
 new Thread(testThread2,"thread3").start();
 ​

3. 线程创建方式三:实现callable接口

 public class testThread4 implements Callable<Boolean> {
 ​
 ​
     @Override
     public Boolean call() throws Exception {
         return null;
     }
 ​
     public static void main(String[] args) {
         testThread4 t1 = new testThread4();
 ​
         //创建执行服务
         ExecutorService ser = Executors.newFixedThreadPool(1);
         //执行提交
         Future<Boolean> r1 = ser.submit(t1);
 ​
         //获得结果
         try {
             Boolean result = r1.get();
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
         //关闭服务
         ser.shutdown();
 ​
     }
 }
 ​

优点:

  • 可以定义返回值

  • 可以抛出异常

Lambda表达式

为什么要使用Lambda表达式

  • 避免匿名内部类定义过多

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

  • 可以让代码更加简洁

函数式接口的定义

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

 public interface Runnable{
     public abstract void run();
 }

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

示例一

 package lambda;
 ​
 import javax.jws.Oneway;
 ​

 ​
 //1。定义一个函数式接口
 ​
 interface ILike{
     void lambda();
 }
 //2.实现类
 class Like implements ILike{
     @Override
     public void lambda() {
         System.out.println("i like lambda");
     }
 }
 public class TestLambda1 {
 //    3.静态内部类
     static class Like2 implements ILike{
     @Override
     public void lambda() {
         System.out.println("i like 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("i like lambda3");
             }
         }
         like = new Like3();
         like.lambda();
 ​
 //        5.匿名内部类,没有类的名称,必须借助接口或者父类
         like = new ILike() {
             @Override
             public void lambda() {
                 System.out.println("i like lambda4");
             }
         };
         like.lambda();
 ​
 ​
 //        6.用lambda简化
         like = ()->{
             System.out.println("i like lambda5");
         };
         like.lambda();
 ​
     }
 }
 ​

静态代理

 package StaticProxy;
 ​
 ​
 //静态代理模式
 //    真实对象和代理对象都要实现同一个接口
 //    代理对象代理真实角色
 //    好处
 //        代理对象可以做很多真实对象做不了的事情
 //        真实对象专注做自己的事情
 public class StaticProxy {
     public static void main(String[] args) {
 ​
 ​
         new Thread(()-> System.out.println("你好")).start();
         new WeddingCompany(new You()).HappyMarry();
 ​
     }
 ​
 }
 ​
 interface Marry{
 ​
     void HappyMarry();
 }
 ​
 //真是角色
 class You implements Marry{
     @Override
     public void HappyMarry() {
         System.out.println("2021.7.21 结婚");
     }
 }
 ​
 //代理角色
 class WeddingCompany implements Marry{
 ​
 //    代理谁--> 真实目标角色
     private Marry target;
 ​
     public WeddingCompany(Marry 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("结婚之后,s收尾款");
     }
 }

线程状态

 

线程停止

  • 建议线程正常停止-->利用次数,不建议死循环

  • 建议使用标志位--->设置一个标志位

  • 不要使用stop或者destroy等过时或者JDK不建议使用的方法

 package state;

 public class TestStop implements Runnable {
 //    设置一个标志位
     private boolean flag = true;
 ​
     @Override
     public void run() {
         int i=0;
         while (flag){
             System.out.println("run-----Thread"+i++);
         }
     }
 //    设置一个公开的方法停止线程,转换标志位
     public void stop(){
         this.flag=false;
     }
 ​
     public static void main(String[] args) {
         TestStop testStop = new TestStop();
 ​
         new Thread(testStop).start();
 ​
         for (int i = 0; i < 1000; i++) {
             System.out.println("main"+i);
             if (i==900){
                 testStop.stop();
                 System.out.println("线程停止");
             }
 ​
         }
     }
 }

线程休眠

  • sleep指定当前线程阻塞的毫秒数

  • sleep存在异常InterruptedException

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

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

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

 //模拟倒计时

 public class TestSleep2 {
 ​
     public static void main(String[] args) throws InterruptedException {
 //        tenDown();
         Date startTime = new Date(System.currentTimeMillis());
 ​
         while(true){
             Thread.sleep(1000);
             System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
             startTime = new Date(System.currentTimeMillis());//更新当前时间
         }
 ​
     }
     public static void tenDown() throws InterruptedException {
         int num = 10;
 ​
         while(true){
             Thread.sleep(1000);
             System.out.println(num--);
             if (num<=0){
                 break;
             }
         }
     }
 }
 ​
 //模拟网络延时:放大问题的发生性
 public class TestSleep implements Runnable{
 ​
 //票数
     private int ticketnumber=10;
 ​
     public void run() {
         while (true){
             if (ticketnumber<=0){
                 break;
             }
 //         模拟延时
             try {
                 Thread.sleep(300);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketnumber--+"张票");
         }
     }
 ​
 ​
     public static void main(String[] args) {
 //        testThread = new TestThread3();
         TestSleep testThread3 = new TestSleep();
 ​
         new Thread(testThread3,"张三").start();
         new Thread(testThread3,"李四").start();
         new Thread(testThread3,"王五").start();
     }
 }

线程礼让

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

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

  • 让CPU重新调度,礼让不一定成功!

 //测试线程礼让
 // 礼让不一定成功,看CPU心情
 public class TestYield {
     public static void main(String[] args) {
         MyYield myYield = new MyYield();
         new Thread(myYield,"a").start();
         new Thread(myYield,"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合并线程,其它线程阻塞,待此线程执行完成后,再执行其它线程。

 
//测试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) {
         TestJoin testJoin = new TestJoin();
         Thread thread = new Thread(testJoin);
         thread.start();
 ​
         for (int i = 0; i < 1000; i++) {
             if (i==200) {
                 try {
                     Thread.sleep(100);
                     thread.join();//插队
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             System.out.println("main"+i);
         }
     }
 }

线程状态

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

  • NEW

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

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

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

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

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

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

一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

 //观察测试线程状态
 public class TestState {
     public static void main(String[] args) throws InterruptedException {
         Thread thread = new Thread(()->{
             for (int i = 0; i < 5; i++) {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             System.out.println("--------------");
         });
         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){//只要线程不终止,就一直输出状态
             Thread.sleep(100);
             state = thread.getState();
             System.out.println(state);//输出状态
         }
 //        thread.start();
 //        线程中断或者结束,一旦进入死亡状态,就不能再次启动
     }
 }

线程优先级

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

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

    • Thread.MIN_PRIORITY = 1;

    • Thread.MAX_PRIORITY = 10;

    • Thread.NORM_PRIORITY = 5;

  • 使用以下方式改变或获取优先级

    • getPriority().setPriority(int xx)

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

 
public class TestPriority {
     public static void main(String[] args) {
         //
         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
 ​
         MyPriority myPriority = new MyPriority();
 ​
         Thread thread1 = new Thread(myPriority);
         Thread thread2 = new Thread(myPriority);
         Thread thread3 = new Thread(myPriority);
         Thread thread4 = new Thread(myPriority);
         Thread thread5 = new Thread(myPriority);
         Thread thread6 = new Thread(myPriority);
 ​
 //        先设置优先级
         thread1.start();
 ​
         thread2.setPriority(1);
         thread2.start();
 ​
         thread3.setPriority(4);
         thread3.start();
 ​
         thread4.setPriority(Thread.MAX_PRIORITY);
         thread4.start();
 ​
 //        thread5.setPriority(-1);
 //        thread5.start();
 //
 //        thread6.setPriority(11);
 //        thread6.start();
     }
 }
 class MyPriority implements Runnable{
 ​
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
     }
 }

守护(daemon)线程

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

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

  • 虚拟机不用等待守护线程执行完毕; 如:后台记录操作日记,监控内存,垃圾回收等等

 //测试守护线程
 
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);//默认是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("====goodbye world==========");
     }
 }

线程同步

多个线程操作同一个资源

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

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

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

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

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

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

  1. 买票问题

 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;
     private boolean flag = true;
 ​
     @Override
     public void run() {
         while (flag){
             try {
                 Buy();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
     private void Buy() throws InterruptedException {
 ​
         if (ticketNums<=0){
             flag = false;
             return;
         }
         Thread.sleep(200);
         System.out.println(Thread.currentThread().getName()+"买到"+ticketNums--);
     }
 }

  1. 取钱问题

 public class UnsafeBank {
     public static void main(String[] args) {
         Account account = new Account(100,"工商银行");
         Drawing you = new Drawing(account,50,"you");
         Drawing girl = new Drawing(account,100,"girl");
         you.start();
         girl.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()+"钱不够,取不到");
             return;
         }
 //        sleep 可以放大问题的发生性
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         //卡内余额 = 余额 - 你取的钱
         account.money = account.money - drawingMoney;
         //你手中的钱
         nowMoney = nowMoney + drawingMoney;
         System.out.println(account.name+"余额为"+account.money);
 //        Thread.currentThread().getName() = this.getName()
 //        this 相当于 Thread
         System.out.println(this.getName()+"手里的钱:"+nowMoney);
 ​
     }
 }
 ​

  1. 不安全的集合

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

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

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

}

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

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

将上述不安全案例修改

1. 买票问题,修改买票的方法

 //synchronized 同步方法,锁的是this
     private synchronized void Buy() throws InterruptedException {
 ​
         if (ticketNums<=0){
             flag = false;
             return;
         }
         System.out.println(Thread.currentThread().getName()+"买到"+ticketNums--);
     }
  1.  

 public  void run() {
        
     //锁的对象就是变化的量,需要增删改
         synchronized (account){
             //判断
             if (account.money-drawingMoney<0){
                 System.out.println(Thread.currentThread().getName()+"钱不够,取不到");
                 return;
             }
 //        sleep 可以放大问题的发生性
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             //卡内余额 = 余额 - 你取的钱
             account.money = account.money - drawingMoney;
             //你手中的钱
             nowMoney = nowMoney + drawingMoney;
             System.out.println(account.name+"余额为"+account.money);
 //        Thread.currentThread().getName() = this.getName()
 //        this 相当于 Thread
             System.out.println(this.getName()+"手里的钱:"+nowMoney);
         }
     }
 3. 集合
public class UnsafeList {
     public static void main(String[] args) throws InterruptedException {
         List<String> list = new ArrayList<String>();
         for (int i = 0; i < 10000; i++) {
             new Thread(()->{
                 synchronized (list){
                     list.add(Thread.currentThread().getName());
                 }
             }).start();
         }
         Thread.sleep(2000);
         System.out.println(list.size());
     }
 }

同步块

  • 同步块:synchronized(Obj){}

  • Obj称之为同步监视器

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

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

  • 同步监视器的执行过程

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

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

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

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

死锁

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

产生死锁的四个必要条件

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

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

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

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

线程池

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

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

  • 好处

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

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

    • 便于线程管理

      • corePoolSize:核心池的大小

      • maximunPoolSize:最大线程数

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值