多线程(偏向应用)

Java基础知识多线程

1、多线程的介绍

  1. 什么是程序?

程序(Program)是一个静态的概念,一般对应于操作系统中的一个可执行文件。

  1. 什么是进程?

    执行中的程序就是进程,是一个动态的概念。其实进程就是在一个内存中独立运行的程序空间。

    image-20230412193937463

  2. 什么是线程?

线程(Thread)是操作系统能够进行运算调度的最小单位。他被包含在进程之中,是进程中的实际运作单位,线程在进程中是共享内存空间的,即是共享进程中的资源。

image-20230412194345660

  1. 什么是并发?

并发是指在一段时间内同时做多个事情,当有多个线程在运行时,如果只有一个CPU,在这种情况下操作系统会采用并发技术实现并发运行,具体做法是采用时间片轮询算法,在一个时间段内的线程代码运行,其他线程处于就绪状态,这就是并发。

image-20230412195046310

image-20230412195152549

  1. 线程的执行特点

方法的执行特点

image-20230412195306496

线程的执行特点

image-20230412195433051

  1. 什么是主线程以及子线程?

image-20230412195601638

主线程:当Java程序启动时,一个线程就会立刻运行,该线程通常叫做主线程(main thread),即main方法对应的线程,它是程序开始时就执行的。

主线程特点:产生其它子线程的线程;它不一定是最后完成执行的线程,子线程可能在它结束之后还在运行。

子线程:在主线程中创建并启动,一般称为子线程。

注意:只要线程一启动,不论主次都是并行运行的!!!

2、线程的创建

  • 继承Thread类实现多线程

    • 在Java中与多线程操作想关的都是java.lang.Thread类。

    image-20230412200650835

    • 可以通过创建Thread类的实例来创建线程。
    • 每一个线程都是通过特定的Thread对象所对应的run()方法来完成线程的操作的,run方法称为线程体。
    • 调用Thread类的start()方法来移动一个线程。
public class TestThread extends Thread{
    //run为线程体
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //由于继承Thread类 因此可以直接调用获取线程名得方法
            System.out.println(this.getName()+ "  "+ i);
        }
    }
}
class test{
    public static void main(String[] args) {
        //创建线程对象
        TestThread testThread = new TestThread();
        //设置线程名称 必须在线程启动前
        testThread.setName("Thhh");
        //启动线程
        testThread.start();
    }
}
  • 通过实现Runnable接口实现多线程
    • 新建一个类,实现Runnable接口,实现run方法
    • 启动线程,使用Thread类包装,上述定义的类,调用start()启动线程
public class TestThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+" "+ i);
        }
    }
}

class test2 {
    public static void main(String[] args) {
        Thread t2 = new Thread(new TestThread2());
        t2.setName("Runnable");
        t2.start();
    }
}

切记!!一个线程对象只能启动一次,否者就会报错!若想启动新的线程,必须在new 一个对象!

images202304122023792

在实际开发中这种实现方式最常用。
从Thread类中的源码角度看,Thread类也实现了Runnable接口。

image-20230412202922196

而在Runnable中接口只有一个抽象方法

image-20230412203016462

两种方式比较看,实现Runnable接口的方式要通用一些。

该方式克服了继承Thread类的缺点,即在实现Runnable接口的同时还可以继承某个类。

  • 线程的执行流程

image-20230412203803325

  • 线程状态和声明周期

一个线程在其生命周期内,需要经历一下5个状态。

新生状态(NEW):用new关键字建立一个线程对象后,这样的线程就是新生状态。处于新生状态的线程有这自己的内存空间,只有通过调用start()方法后才进入下一个状态就绪状态Runnable。

就绪状态(Runnable):处于该阶段(状态)的线程已经具备了运行条件,但是还没有分配到CPU(即是无权执行),就一直处于“线程就绪队列”中等待,等待操作系统为其分配CPU执行权限。就绪状态(Runnable) 不是执行状态(Running),而是当操作系统在“线程就绪队列”中选定一个等待的线程对象后,他就会进入执行状态。一旦获取到cpu,线程进入了运行状态(Running),就会调用自己的Run()方法。

线程进入就绪状态的原因:

image-20230412205357467

运行状态(Running):执行自己线程体里面的代码,知道调用其他方法而终止或等待某资源而阻塞或者完成任务而死亡。如果时间片到了,该线程还没有执行结束,那么该线程就会被换下来,进入就绪状态。也有可能由于某些阻塞的事件导致该线程直接进入阻塞状态。

导致阻塞的原因:

image-20230412210248658

死亡状态(Terminated):

死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。

当一个线程进入死亡状态以后,就不能再回到其它状态了。

image-20230412210446972

3、线程的使用

  • 典型的终止线程的方式

终止线程我们一般不使用JDK提供的stop()/destroy()方法(它们本身也被JDK弃用了)

通常的做法是提供一个boolean变量用于控制线程体是否继续执行。

public class TestThread3 implements Runnable{
    private boolean flag = true;
    public TestThread3() {
    }

    public TestThread3(boolean flag) {
        this.flag = flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }


    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始");
        int i= 0;
        while (flag){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+" "+i++);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
        System.out.println(Thread.currentThread().getName()+"结束");
    }
}

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

        System.out.println("主线程开始");
        TestThread3 t3 = new TestThread3();
        Thread thread = new Thread(t3);

        thread.start();
        try {
            System.in.read();
            
            //停止子线程
            t3.setFlag(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("主线程结束!");
    }
}
  • 线程休眠

sleep()方法:可以让当前正在运行的线程进入阻塞状态,直到休眠的时间结束。sleep方法的参数为休眠的毫秒数。

public class TestThread4 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始了");
        for (int i = 0; i < 30; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+ " "+ i);
        }

        System.out.println(Thread.currentThread().getName()+"结束了");
    }
}

class test4 {
    public static void main(String[] args) {
        System.out.println("主线程开始");
        TestThread4 testThread4 = new TestThread4();
        Thread thread = new Thread(testThread4,"myTreadSleep");
        thread.start();

        System.out.println("主线程结束");
    }
}

image-20230412215232260

  • 线程让步(礼让)

yield()让当前正在运行的线程回到就绪状态,让其相同优先级的线程。使用yield()的目的是让具有相同优先级的线程之间能够适当的乱换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

  • ​ yield()是一个静态方法;
  • 调用yield()告诉当前线程把运行机会交给具有相同优先级的线程。
  • yield() 不能保证,当前线程迅速从运行状态转换到就绪状态。
  • yield只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。
  • 案例

public class TestyieldThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            if (Thread.currentThread().getName().equals("线程一")) {
                if (i == 10 ) {
                    System.out.println("让步");
                    Thread.yield();
                }
            }
            System.out.println(Thread.currentThread().getName()+"\t"+ (i+1));
        }
    }
}
class test5{
    public static void main(String[] args) {
        TestyieldThread ty1 = new TestyieldThread();
        TestyieldThread ty2 = new TestyieldThread();

        new Thread(ty1,"线程一").start();
        new Thread(ty2,"线程二").start();
    }
}


  • 线程联合

image-20230415115419465

当前要求联合的线程必须等待被联合的线程先执行完才能继续执行,线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。

  • 案例
package Create;

import sun.awt.windows.ThemeReader;

class A implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"  "+ i);
        }
    }
}

class B implements Runnable {
    private Thread a;

    public B(Thread a) {
        this.a = a;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i == 4){
                try {
                    System.out.println("B中现在i =  "+ i +",线程 "+"B已经联合了线程A,B必须等待A执行完成");
                    a.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"  "+i);
        }
    }
}

public class TestJoinThread {
    public static void main(String[] args) {
        A a = new A();
        Thread thread = new Thread(a,"A");


        B b1 = new B(thread);
        Thread thread1 = new Thread(b1,"B");

        thread.start();
        thread1.start();

        for (int i = 0; i < 20; i++) {
            if (i == 5){
                try {
                    System.out.println("main中现在i =  "+ i +",线程 "+ Thread.currentThread().getName() + "已经联合了线程B,main必须等待B执行完成");
                    thread1.join();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" "+ i);

        }


    }
}

在main中i等于5之前,在B中i小于4之前,没有进行线程联合,3个线程可以并行执行。main中i = 5时联合了B,所以之后main必须等待B完成,在B中i = 4的时候联合了A,B必须等待A的完成。

  • 线程联合案例:实现爸爸让儿子买烟
package Create;

public class TestJoinDemo {
    public static void main(String[] args) {
        new Thread(new FartherThread()).start();
    }

}
/**
 * 儿子线程
 */
class SonThread implements Runnable{

    @Override
    public void run() {
        System.out.println("儿子拿到了钱,出去买烟了!");
        System.out.println("儿子买烟需要10分钟!");
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("第"+ i +" 分钟");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("儿子买烟回来了");
    }
}

/**
 * 爸爸抽烟线程
 */
class FartherThread implements Runnable{

    @Override
    public void run() {
        System.out.println("爸爸想抽烟,烟没了");
        System.out.println("让儿子去买烟");
        Thread son = new Thread(new SonThread(), "儿子买烟中……");
        son.start();
        System.out.println("需要等10分钟");
        try {
            son.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("儿子丢了,烟没买掉 ");
            System.exit(1);
        }
        System.out.println("烟已到手,开抽,把零钱给了儿子!");

    }
}

  • 获取线程名字

    • 方式一

      this.getName()获取线程名称,该方法适用于继承Thread实现多线程方式。

      class GetName1 extends Thread{
        @Override
        public void run() {
          System.out.println(this.getName());
         }
      }
      
      
    • 方式二

      hread.currentThread().getName()获取线程名称,该方法适用于实现Runnable接口实现多线程方式。

      class GetName2 implements Runnable{
      
      
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName());
         }
      }
      
      
  • 修改线程名

    • 方式一:通过构造方法设置线程名称。

      package Create;
      
      public class SetName1 extends Thread {
      
          public SetName1(String name) {
              super(name);
          }
      
          @Override
          public void run() {
              System.out.println(this.getName());
          }
      
      
      }
      
      class SetName{
          public static void main(String[] args) {
              new SetName1("setName").start();
          }
      }
      
    • 方式二:通过setName()方法设置线程名称。

      class SetName2 implements Runnable{
      
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName());
          }
      }
      
      class SetName{
          public static void main(String[] args) {
      //        new SetName1("setName").start();
      
              SetName2 setName2 = new SetName2();
              Thread thread = new Thread(setName2);
              thread.setName("SetNamw2");
              thread.start();
          }
      }
      
  • 判断线程是否存活

isAlive()方法: 判断当前的线程是否处于活动状态。

活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。

活动状态只包括:就绪,运行,阻塞状态

package Create;

public class Alive implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(Thread.currentThread().getName()+" "+ i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class TestAliveThread {
    public static void main(String[] args) {
        Thread thread = new Thread(new Alive());

        thread.setName("Alive");

        thread.start();

        System.out.println(thread.getName()+" "+thread.isAlive());

        try {

            Thread.sleep(4000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println(thread.getName()+" "+thread.isAlive());
    }
}

4、线程的优先级

image-20230415161029972

image-20230415161125514

  • 线程优先级的使用

int getPriority();

void setPriority(int newProority);

优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。

package Create;

public class Priority implements Runnable{
    private int num = 0;
    private boolean flag = true;
    @Override
    public void run() {
       while (flag){
           System.out.println(Thread.currentThread().getName()+" "+ (++num));
       }
    }

    public void stop(){
        this.flag = false;
    }
}

class PriorityThread {
    public static void main(String[] args) {
        Priority p1 = new Priority();

        Priority p2 = new Priority();

        Thread t1 = new Thread(p1,"线程1");

        Thread t2 = new Thread(p2,"线程2");

        System.out.println(t1.getPriority());

        //Thread.MAX_PRIORITY = 10

        t1.setPriority(Thread.MAX_PRIORITY);

        //Thread.MAX_PRIORITY = 1

        t2.setPriority(Thread.MIN_PRIORITY);

        t1.start();

        t2.start();

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

        p1.stop();

        p2.stop();
    }
}

5、守护线程

  • 什么是守护线程?

在Java中有两类线程

​ User Thread(用户线程):就是程序里面的自定义线程

​ Daemon Thraed(守护线程):比如垃圾回收机制的线程,就是典型的守护线程。

守护线程是一个服务线程,使用于服务其他的线程。

  • 守护线程的特点:
    • 守护线程会随着用户线程的死亡而消亡。用户线程只有两种情况会死掉,run中异常终止,正常把run执行完毕,线程死亡。
package Create;

class UsersThread implements Runnable{

    @Override
    public void run() {
        Daemon daemon = new Daemon();
        Thread t = new Thread(daemon, "Daemon");
        t.setDaemon(true);
        t.start();

        for(int i=0;i<5;i++){

            System.out.println(Thread.currentThread().getName()+" "+i);

            try {

                Thread.sleep(500);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }
    }
}
class Daemon  implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<20;i++){

            System.out.println(Thread.currentThread().getName()+" "+i);

            try {

                Thread.sleep(2000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }
    }
}
public class DaemonThread {
    public static void main(String[] args) {
        Thread t = new Thread(new UsersThread(),"UsersThread");

        t.start();

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

        System.out.println("主线程结束");
    }
}

6、线程同步

  • 什么是线程同步?

线程冲突现象:

image-20230415163712353

现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。

  • 线程同步的概念

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候,就需要用到线程同步,否则拿到的对象数据可能不正确。线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程在进行操作,这就是线程同步。

  • 线程冲突案例:银行取钱问题,如果不进行线程同步操作,导致金额可能会不正确。

    • 取钱的步骤基本可以分为几个步骤
      • 用户输入账户密码,系统判断用户的账户密码是否正确。
      • 用户输入取款金额
      • 系统判断账户余额是否大于或等于取款金额
      • 如果余额大于或等于取款金额,则成功,否则失败。
    package Create;
    
    class Accunt {
        private String accountNo;
        private double balance;
    
        public Accunt() {
        }
    
        public Accunt(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
    
        public String getAccountNo() {
            return accountNo;
        }
    
        public void setAccountNo(String accountNo) {
            this.accountNo = accountNo;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    }
    
    /**
     * 取钱线程
     */
    class DrawThread implements Runnable{
        private Accunt accunt;
        private double needMony;
    
        public DrawThread(Accunt accunt,double needMony) {
            this.accunt = accunt;
            this.needMony = needMony;
        }
    
        public double getNeedMony() {
            return needMony;
        }
    
        public void setNeedMony(double needMony) {
            this.needMony = needMony;
        }
    
        @Override
        public void run() {
            //判断金额是否大于等于当前用户的余额
            if (this.accunt.getBalance() >= this.needMony){
                System.out.println(Thread.currentThread().getName()+" 取钱成功!"+this.needMony+"元");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新用户余额
                this.accunt.setBalance(this.accunt.getBalance() - this.needMony);
    
                System.out.println("该账户余额为:"+this.accunt.getBalance());
            }else {
                System.out.println("余额不足,取款失败");
            }
        }
    }
    
    public class TestDrawMoneyThread {
        public static void main(String[] args) {
            Accunt accunt = new Accunt("1234", 1000);
    
            new Thread(new DrawThread(accunt,800),"王晓丽").start();
            new Thread(new DrawThread(accunt,600),"张菊").start();
    
        }
    }
    
    

image-20230415165814868

很显然这是不对的!,原因是在张菊取钱走600时,还没来得设置余额为400,当前余额还是1000,当王晓丽来了之后一看,诶还有一千!可以取800,那就取走!此时张菊已经更新余额为400了。而王晓丽也在取800,然后更新账户就出现了-400.这明显不对,产生的原因就是线程冲突!

  • 实现线程同步

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。这套机制就是synchronized关键字。

synchronized语法结构:

synchronized(锁对象){ 同步代码 }

image-20230415170241804

image-20230415170308037

  • 线程冲突案例解决冲突:锁定当前账户
package Create;

class Accunt {
    private String accountNo;
    private double balance;

    public Accunt() {
    }

    public Accunt(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

/**
 * 取钱线程
 */
class DrawThread implements Runnable {
    private Accunt accunt;
    private double needMony;

    public DrawThread(Accunt accunt, double needMony) {
        this.accunt = accunt;
        this.needMony = needMony;
    }

    public double getNeedMony() {
        return needMony;
    }

    public void setNeedMony(double needMony) {
        this.needMony = needMony;
    }

    @Override
    public void run() {
        synchronized (this.accunt) {
            System.out.println(Thread.currentThread().getName()+" 正在取钱……");
            //判断金额是否大于等于当前用户的余额
            if (this.accunt.getBalance() >= this.needMony) {
                System.out.println(Thread.currentThread().getName() + " 取钱成功!" + this.needMony + "元");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新用户余额
                this.accunt.setBalance(this.accunt.getBalance() - this.needMony);

                System.out.println("该账户余额为:" + this.accunt.getBalance());

            } else {
                System.out.println(Thread.currentThread().getName()+" 余额不足,取款失败");
            }
        }
    }
}

public class TestDrawMoneyThread {
    public static void main(String[] args) {
        Accunt accunt = new Accunt("1234", 1000);

        new Thread(new DrawThread(accunt, 800), "王晓丽").start();
        new Thread(new DrawThread(accunt, 600), "张菊").start();

    }
}

image-20230415171635833

  • 线程同步的使用

    • 使用this作为对象锁(在方法上就是锁当前类对象的实例)

      image-20230415172051536

    • 语法结构

        synchronized (对象锁){//简单理解为一种标识,互斥的线程的标识
                     //代码块 
        }
      
      //或
      public synchronized void accessVal(int newVal){
      	//同步代码
      }
      
      
      • 案例
      package Create;
      
      /**
       * 定义程序员类
       */
      class Programmer{
          private String name;
      
          public Programmer(String name){
              this.name = name;
          }
      
          //打开电脑
        synchronized   public void  openCompurter(){
              try {
                  System.out.println(this.name+"插上电源");
                  Thread.sleep(500);
                  System.out.println(this.name+"按电源键");
                  Thread.sleep(500);
                  System.out.println(this.name+"系统启动中");
                  Thread.sleep(500);
                  System.out.println(this.name+"系统启动成功");
                  Thread.sleep(500);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          //编码
         synchronized public void encoding(){
              try {
                  System.out.println(this.name+"点击桌面IDE");
                  Thread.sleep(500);
                  System.out.println(this.name+"IDE启动中");
                  Thread.sleep(500);
                  System.out.println(this.name+"IDE启动成功");
                  Thread.sleep(500);
                  System.out.println(this.name+"开始写代码");
                  Thread.sleep(500);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
      
      /**
       * 打开电脑工作线程
       */
      class Working1 extends Thread{
      
          private Programmer programmer;
      
          public Working1(Programmer programmer) {
              this.programmer = programmer;
          }
      
          @Override
          public void run() {
              this.programmer.openCompurter();
          }
      }
      
      /**
       * 编码工作线程
       */
      class Working2 extends Thread{
      
          private Programmer programmer;
      
          public Working2(Programmer programmer) {
              this.programmer = programmer;
          }
      
          @Override
          public void run() {
              this.programmer.encoding();
          }
      }
      
      public class TestSyncThread {
          public static void main(String[] args) {
              Programmer p = new Programmer("张三");
              new Working1(p).start();
              new Working2(p).start();
      
          }
      }
      
      
  • 使用字符串作为线程对象锁

synchronized(“字符串”){ 
    //同步代码 
   }

在程序员类中加

  /**

   * 去卫生间

   */

  public void wc(){

    synchronized ("suibian") {

      try {

        System.out.println(this.name + " 打开卫生间门");

        Thread.sleep(500);

        System.out.println(this.name + " 开始排泄");

        Thread.sleep(500);

        System.out.println(this.name + " 冲水");

        Thread.sleep(500);

        System.out.println(this.name + " 离开卫生间");

       } catch (InterruptedException e) {

        e.printStackTrace();

       }

     }

   }


public class TestSyncThread {

  public static void main(String[] args) {

    Programmer p = new Programmer("张三");

    Programmer p1 = new Programmer("李四");

    Programmer p2 = new Programmer("王五");

    new WC(p).start();

    new WC(p1).start();

    new WC(p2).start();

   }
  • 使用Class作为线程对象锁

    • 语法模板

      synchronized(XX.class){ 
      
      //同步代码 
      }
      
      //或者
      synchronized public static void accessVal()
      

该对象锁的是以当前类为模板的所有实例对象

  • 自定义对象做为对象锁,也就是这个类为为自己定义的类。

  • 什么是线程死锁?

image-20230415194337661

“死锁”指的是:多个线程之间彼此拥有所需的资源,并且相互等待其他线程所占有的资源才能进行。导致两个或多个线程都在等待对方四方资源,都停止执行的情形。

image-20230415194657363

  • ​ 死锁案例
/**
 * 口红类
 */
class Lipstick{


}


/**
 * 镜子类
 */
class Mirror{


}


/**
 * 化妆线程类
 */
class Makeup extends Thread{
  private int flag; //flag=0:拿着口红。flag!=0:拿着镜子
  private String girlName;
  static Lipstick lipstick = new Lipstick();
  static Mirror mirror = new Mirror();


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


  @Override
  public void run() {
    this.doMakeup();
   }
  /**
   * 开始化妆
   */
  public void doMakeup(){
    if(flag == 0){
      synchronized (lipstick){
        System.out.println(this.girlName+" 拿着口红");
        try {
          Thread.sleep(1000);
         } catch (InterruptedException e) {
          e.printStackTrace();
         }
        synchronized (mirror){
          System.out.println(this.girlName+" 拿着镜子");
         }
       }
     }else{
      synchronized (mirror){
        System.out.println(this.girlName+" 拿着镜子");
        try {
          Thread.sleep(2000);
         } catch (InterruptedException e) {
          e.printStackTrace();
         }
        synchronized (lipstick){
          System.out.println(this.girlName+" 拿着口红");
         }
       }
     }
   }
}


public class DeadLockThread {
  public static void main(String[] args) {
    new Makeup(0,"大丫").start();
    new Makeup(1,"小丫").start();
   }
}

上述死锁解决,同步块不能嵌套,改为并行。

死锁问题的解决

死锁是由于 “同步块需要同时持有多个对象锁造成”的,要解决这个问题,思路很简单,就是:同一个代码块,不要同时持有两个对象锁。

7、线程并发协作_生产者消费者模式

image-20230415195221302

多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。

image-20230415195342607

package Create;
class ManTou{
    private int id;

    public ManTou(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }
}

/**
 * 定义缓冲区类
 */
class SyncStack{
    private ManTou[] mt = new ManTou[10];

    private int index;

    //放馒头
    public synchronized void push(ManTou manTou){
        while (this.index == this.mt.length){
            /**
             * 语法:wait(),该方法必须要在synchronized块中调用。
             * wait执行后,线程会将持有的对象锁释放,并进入阻塞状态,
             * 其他需要该对象锁的线程就可以继续运行了。
             */
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //唤醒取馒头的线程

        /**
         * 语法:该方法必须要在synchronized块中调用。
         * 该方法会唤醒处于等待状态队列中的一个线程。
         */
        this.notify();
        this.mt[index++] = manTou;
    }

    /**
     * 取馒头
     */
    public synchronized ManTou pop(){
        //没有馒头我就通知生产者生产
        while (this.index == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        this.index--;
        return mt[index];
    }
}

class ShengChan extends Thread{

    private SyncStack syss;

    public ShengChan(SyncStack syss) {
        this.syss = syss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("生产馒头"+ i);
            this.syss.push(new ManTou(i));
        }

    }
}


/**

 * 定义消费者线程类

 */

class XiaoFei extends Thread{

    private SyncStack ss;

    public XiaoFei(SyncStack ss){

        this.ss = ss;

    }

    @Override

    public void run() {
        for(int i=0;i<10;i++){
            ManTou manTou = this.ss.pop();
            System.out.println("消费馒头:"+i);
        }

    }

}
public class TestProduceThread {
    public static void main(String[] args) {
        SyncStack ss = new SyncStack();

        new ShengChan(ss).start();

        new XiaoFei(ss).start();


    }
}

8、线程并发协作总结

线程并发协作(也叫线程通信)

生产者消费者模式:

  1. 生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
  2. 对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
  3. 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
  4. 在生产者消费者问题中,仅有synchronized是不够的。synchronized可阻止并发更新同一个共享资源,实现了同步但是synchronized不能用来实现不同线程之间的消息传递(通信)。
  5. 那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
方法名作 用
final void wait()表示线程一直等待,直到得到其它线程通知
void wait(long timeout)线程等待指定毫秒参数的时间
final void wait(long timeout,int nanos)线程等待指定毫秒、微秒的时间
final void notify()唤醒一个处于等待状态的线程
final void notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行

以上方法均是java.lang.Object类的方法;都只能在同步方法或者同步代码块中使用,否则会抛出异常。
id run() {
for(int i=0;i<10;i++){
ManTou manTou = this.ss.pop();
System.out.println(“消费馒头:”+i);
}

}

}
public class TestProduceThread {
public static void main(String[] args) {
SyncStack ss = new SyncStack();

    new ShengChan(ss).start();

    new XiaoFei(ss).start();


}

}

总结

这是一篇多线程基础,包含一些定义,以及一些偏向应用的多线程!接下来将会从面向面试问题的角度对多线程的讲解。到此点个赞吧!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明天码上有钱啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值