栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程基础

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程基础

什么是多线程?什么是进程 线程的简介:

线程是指一个进程中的一个执行流程,多线程就是指,同一时刻运行多个程序的能力。每一个任务称为一个线程。可以同时运行一个以上线程的程序称为多线程程序,在JVM的内部,程序的多任务是通过线程来实现的,每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。

在这个JVM环境中,所有程序代码的运行都是以线程来运行,比如在我们平时使用的main方法中运行一行 HelloWorld,就启动一个jvm的进程,JVM找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成,JVM进程也随即退出 。

对于一个进程中的多个线程来说,多个线程共享进程的内存块,当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,速度也很快。不同的进程因为处于不同的内存块,因此进程之间的通信相对困难。
比如:qq和微信运行的时候有自己的内存空间,像两个盒子放在手机大盒子里面,两个盒子互不影响,就相当于两个进程互不影响,微信里面启动多个线程,这些线程在一个,小盒子所以可以互相通信。

Java中线程是指java.lang.Thread类的一个实例或线程的执行。使用java.lang.Thread或java.lang.Runnable接口编写代码定义、实例化、启动新线程。

Java中每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行。main()方法运行在一个线程内,称为主线程。一旦创建一个新的线程,就产生一个新的调用栈。线程分为两类:用户线程和守候线程。当所有用户线程执行完毕后,JVM自动关闭。但是守候线程却不独立与JVM,守候线程一般是有操作系统或用户自己创建的。

进程简介:

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程创建方式之一
    继承Thread,实现多线程同步下载图片思路: 创建一个线程类,在创建对象时 将下载路径与文件名称传入构造方法中,构造方法优先加载相关属性的赋值之后重写线程中的run方法 ,创建下载工具类实例,给下载工具传入url和文件名字,在main主线程中同时启动三个线程实力去下载图片

因为前面每次贴图都需要上传很麻烦,是到后面发现居然可以直接截屏粘贴的,以下案例中未贴运行结果图的或者是贴错了的,可以先行分析,之后复制代码到:https://c.runoob.com/compile/10/ 进行测试查看是否和分析结果一致,图片内容和程序运行内容不一致就说明贴错了,但代码和释意是正确的,手机上也有可以直接运行java代码的链接入口小程序

public class TestThread1 extends  Thread {
   private  String url;//网络图片地址
   private  String name;//保存的文件名称
   //使用构造器加载变量
   public  TestThread1( String url,String name){
        this.url=url;
        this.name=name;
   }
   public  void run(){
       WebDownloder webDownloder=new WebDownloder();
       webDownloder.downloder(url,name);
       System.out.println("下载了文件名为:"+name);

   }
   public static void main(String[] args) {
       TestThread1 testThread1=new TestThread1("https://gimg2.baidu.com/image_search/src=https://www.mshxw.com/skin/sinaskin/image/nopic.gif","22.jpg");
       TestThread1 testThread2=new TestThread1("https://gimg2.baidu.com/image_search/src=https://www.mshxw.com/skin/sinaskin/image/nopic.gif","33.jpg");
       TestThread1 testThread3=new TestThread1("https://gimg2.baidu.com/image_search/src=https://www.mshxw.com/skin/sinaskin/image/nopic.gif","44.jpg");
       //同时执行  .run 是有顺序的
       testThread3.start();
       testThread1.start();
       testThread2.start();

   }
}
//下载器
class WebDownloder{
   //下载方法
   public void downloder(String url,String name){
       try {
           FileUtils.copyURLToFile(new URL(url),new File(name));
       } catch (IOException e) {
           e.printStackTrace();
           System.out.println("IO异常了 DownLoder 出现了问题");
       }
   }
}

以上的代码运行了2个线程

线程的创建方式二

创建线程的第二种方式,实现Runnable接口,有一个好处是这样可以体现出线程的多态传参,比较便捷与节省很多代码

public class TestThread2 implements  Runnable {
    public void run() {
        for (int i =0; i<20; i++){
            System.out.println("你吃饭了吗");
        }
    }
    public static  void main(String[] args ){
        //main线程 主线程,创建Runnable接口实现类对象
         TestThread2 testThread2=new TestThread2();
         
           //创建线程对象,通过线程对象来开启我们的线程,代理
           new Thread(testThread2).start();

        for (int i=0;i<20;i++){
            System.out.println("我在学习多线程");
        }
    }
}
线程的创建方式三

和前面的创建方式一 不同的是这样写,线程可以获取到返回值,但这种方式不常使用

public class TestThread3  implements Callable {

    private  String url;//网络图片地址
    private  String name;//保存的文件名称
    //使用构造器加载变量
    public  TestThread3( String url,String name){
        this.url=url;
        this.name=name;
    }

    public Boolean call(){
        WebDownloder1 webDownloder1=new WebDownloder1();
        webDownloder1.downloder(url,name);
        System.out.println("下载了文件名为:"+name);
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestThread3 test1=new TestThread3("https://gimg2.baidu.com/image_search/src=https://www.mshxw.com/skin/sinaskin/image/nopic.gif","22.jpg");
        TestThread3 test2=new TestThread3("https://gimg2.baidu.com/image_search/src=https://www.mshxw.com/skin/sinaskin/image/nopic.gif","33.jpg");
        TestThread3 test3=new TestThread3("https://gimg2.baidu.com/image_search/src=https://www.mshxw.com/skin/sinaskin/image/nopic.gif","44.jpg");
        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(1);
        //提交执行
        Future r1=ser.submit(test1);
        Future r2=ser.submit(test2);
        Future r3=ser.submit(test3);
        //获取结果
        boolean rs1=r1.get();
        boolean rs2=r2.get();
        boolean rs3=r3.get();
        //关闭服务
        ser.shutdownNow();
    }
}
class WebDownloder1{
    //下载方法
    public void downloder(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常了 DownLoder 出现了问题");
        }
    }
}

以上是简单的三种线程创建的方式

多个线程使用同资源的情况下,线程不安全,数据紊乱,下面的例子,同一张票会让多个人同时买到,就出现并发问题

//多个线程同时操作同一个对象
//买票的例子
//该案例发现并发问题:多个线程使用同资源的情况下,线程不安全,数据紊乱(可以用并发控制)下一个test解释
public class TestThread4 implements  Runnable {
//票数
private  int ticketNums=10;
public  void run(){
    while (true){
        if(ticketNums<=0){
            break;
        }
        try { //模拟延时
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-->拿到了第--"+ticketNums-- +"票");
    }
}
   public static void main(String[] args) {
       TestThread4  testThread4=new TestThread4();
       new Thread(testThread4,"线程名1:小明").start();
       new Thread(testThread4,"线程名1:水牛").start();
       new Thread(testThread4,"线程名1:黄牛").start();
   }
}

运行结果


Thread.sleep(200) 这个的含义在本案例中起的作用是模拟网络等其他因素造成的上一个线程还在运行中的延迟状态,就是上一个线程还在运行,下一个线程已经进来了,这个时候就会出现并发问题(后面再说)。

线程状态

就绪状态 与 运行状态之间 存在 阻塞状态

    new:Thread t=new Thread(); 线程一单创建就进入到了新生状态就绪状态:当start()方法,线程立即进入就绪状态,但不意味着立即调度执行阻塞状态:当调用sleep,wait或同步锁定,线程进入阻塞状态,就是代码不往下执行,阻塞事件接触后,重新进入就绪状态,等待cpu的调度执行运行状态:进入运行状态,线程才真正执行线程体的代码块dead:线程中断或者结束,一旦进入死亡状态,就不能再次启动
常用的线程方法
方法说明
setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在指向的线程休眠
viod join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他的线程
void interrupt()中断线程(最好不要用这个方式)
boolean isAlive()测试线程是否处于活动状态
停止线程
    不推荐jdk提供的stop()、destroy()方法。【已废弃】推荐线程自己停下来建议使用一个标志位进行终止变量当flag=false,则终止线程的运行
public class StopThreadDemo implements  Runnable {
    private  boolean flag=true;
    @Override
    public void run() {
        int i=0;
        while(flag==true) {
            System.out.println("第二个线程再运行:"+(i++));
        }
    }
    public  void stop(){
        this.flag=false;
    }
    public static void main(String[] args) {

        StopThreadDemo tStopThreadDemo=new StopThreadDemo();
        new Thread(tStopThreadDemo).start();
        for (int i = 0; i < 1000; i++) {
            if(i==900){
                tStopThreadDemo.stop();
                System.out.println("第二个线程已经停止,不在运行:");
            }
            System.out.println("主线程在运行"+(i));
        }
    }
}

运行结果

线程休眠
    sleep (时间)指定当前线程阻塞的毫秒数sleep 存在异常 InterruptedExceptionsleep时间达到后线程进入就绪状态sleep 可以模拟网络延时,倒计时等 (多个线程操作同一个对象,放大问题 ,模拟延时能发现并发问题等线程安全问题,放大问题的发生性)每一个对象都有一个锁,sleep不会释放锁(先强制记住,后面在讲解)
模拟延时
public class TestThread4 implements  Runnable {
 //票数
 private  int ticketNums=10;
 public  void run(){
     while (true){
         if(ticketNums<=0){
             break;
         }
         try { //模拟延时
             Thread.sleep(200);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+"-->拿到了第--"+ticketNums-- +"票");
     }
 }
    public static void main(String[] args) {
        TestThread4  testThread4=new TestThread4();
        new Thread(testThread4,"线程名1:小明").start();
        new Thread(testThread4,"线程名1:水牛").start();
        new Thread(testThread4,"线程名1:黄牛").start();
    }
}

运行结果(有并发问题相同票数)


如果将模拟延时 去除,在运行则基本发现不了并发问题。 并发(就是你会看见,小明拿了第一张票,但是水牛也拿了第一张票,但是一共10张 一人拿了第一张就会变为12张,是不符合该案例的逻辑的)

模拟倒计时

案例1:

public class TestSleep2 {
    public static void tenDown() throws  InterruptedException{
        int num=10; //每一秒走一次 走10秒钟线程结束
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
              break;
            }
        }

    }

    public static void main(String[] args) {
        try {
            tenDown();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

案例2:

public static void main(String[] args) {
    //打印当前系统时间
    Date starTime = new Date(System.currentTimeMillis());
    while (true) {
        try {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(starTime));

            starTime = new Date(System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

线程礼让

举例: 现在有两个线程,一个是A 一个是B 当 A 已经进入CPU A线程调用礼让就会出来,就于B 同时进行重新进入CPU ,也就是说,礼让线程,让当前在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态,让cpu重新调度,但礼让不一定说就能让成功!看CPU的心情

public class TestHaiYanYield {
    public static void main(String[] args) {
        HaiYanYield tMyYield=new HaiYanYield();
        new  Thread(tMyYield,"a").start();
        new  Thread(tMyYield,"b").start();
    }
}
class HaiYanYield implements  Runnable{
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程结束执行");
    }
}

运行结果


如果将上述的案例中的礼让去除,则很大的几率是a线程先跑完才会跑b线程,如果加了并礼让成功,在a执行完第一个线程开始执行的程序时,B 就已经进入了,B 进入后走到 Thread.yield(); 又让A 这个时候A可以走完后续的程序,未礼让成功就和没有加礼让方法一样了,这个不是100%肯定能礼让成功的

线程强制执行 join
    join合并线程,待此线程执行完毕后,在执行其他线程,其他线程阻塞可以想象成插队
public class HaiYanTestJoin implements  Runnable {

   String name;
   public  HaiYanTestJoin(String name){
       this.name=name;
   }

   @Override
   public void run() {
       for (int i = 0; i < 1000; i++) {
           System.out.println(name+i );
       }
   }
   public static void main(String[] args) throws InterruptedException {
       //启动线程
       HaiYanTestJoin tTestJoin=new HaiYanTestJoin("VIP线程来了");//先获取要调用线程的对象
       //获取对象调用启动的"vip线程"的对象
       Thread thread=  new Thread(tTestJoin);
       thread.start();

       //主线程
       for (int i = 0; i < 100; i++) {
           if(i==50){
               thread.join(); //让线程vip强制优先执行,强制执行完毕后,其他线程在继续执行,少用这种方式,容易让线程导致阻塞
           }
           System.out.println("main" + i);
       }
   }
}

运行结果:

线程状态观测

枚举类型 :线程可以处于以下状态之一 【查看JDK帮助文档】

    NEW:尚未启动的线程处于此状态 (新生)RUNNABLE: 在jaba虚拟机中执行的线程处于此状态 (运行)BLOCKED: 被阻塞等待监视器锁定的线程处于此状态 (阻塞)WAITING: 正在等待另一个线程执行特定动作的线程处于此状态 (阻塞)TIMED_WAITING: 正在等待另一个线程执行动作达到指令等待时间的线程处于此状态 (阻塞)TERMINATED: 已退的线程处于此状态 (死亡)
public class HaiYanTestThreadState {
    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("//");
        });
        //观察状态,新生,因为还没有调用start
        Thread.State state=thread.getState();
        System.out.println(state);
        //观察启动后
        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(); 会报错,因为 thread 这个对象线程已经结束死亡,不能再次启动
    }
}

运行结果

线程的优先级

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

    线程的优先级用数字表示,范围从1——10Thread.MIN_PRIORITY=1;Thread.MAX_PRIORITY=10;Thread.NORM_PRIORITY=5;使用以下方式改变获取优先级getPriority().setPriority(int XXX)线程优先级越高,先跑优先级的几率就越大,先设置优先级在启动优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了和一定是最后,这都是看cpu的调度。 简称随缘,但优先级高的线程先执行的缘分概率比较大 (性能倒置)一般默认都是5,属于公平竞争
public class HaiYanTestThreadPriority  {
    public static void main(String[] args) {
        //主线程默认优先级 5
        System.out.println(Thread.currentThread().getName()+"——>"+Thread.currentThread().getPriority());
        MyPriority tMyPriority =new MyPriority();

        //只是将线程放入了但是还未启动线程
        Thread  thread1 =new Thread(tMyPriority,"thread1");
        Thread  thread2 =new Thread(tMyPriority,"thread2");
        Thread  thread3 =new Thread(tMyPriority,"thread3");
        Thread  thread4 =new Thread(tMyPriority,"thread4");
      
        thread1.start();

        thread2.setPriority(1); //很慢
        thread2.start();

        thread3.setPriority(6);//比上一个thread2相对要优先
        thread3.start();

        thread4.setPriority(Thread.MAX_PRIORITY); //默认的最大优先级
        thread4.start();
        //报错 优先级 只能设置1到10之间(含)
       
    }
}
class  MyPriority implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"——>"+Thread.currentThread().getPriority());
    }
}

运行结果

守护线程
    线程分为用户线程和守护线程虚拟机必须确保用户线程执行完毕虚拟机不用等待守护线程执行完毕如,后台记录操作日志,监控内存,垃圾回收等待…thread.setDaemon(true) 默认是false表示的都是用户线程,正常的线程都是用户线程 thread.start();守护线程启动,这个守护线程没有写停止,当thread1死亡的时候 守护线程就会结束(虚拟机需要一定的时间停止,所以当thread对象死亡时,thread1依然进行了一会儿),就类似于一种服务线程比如jvm垃圾回收线程
//测试守护线程
public class HaiYanTestDaemon {
    public static void main(String[] args) {
         Thread thread=new Thread(new God());
         thread.setDaemon(true); 
        Thread thread1=new Thread(new You());
        thread1.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");
    }
}

运行结果,当用户线程结束后,上帝保佑你 随后也结束了

线程同步 使用场景

多个线程操作同一个资源(并发),现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队一个一个的来,或者说上厕所,每个厕所都只有一个坑如果第二个人想要上厕所,那么也是需要排队的处理线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待计值,多个需要同时访问此对象的线程进入这个对象的等待池形成对列,等待前面线程使用完毕,下一个线程再使用先来举几个例子

案例1:

比如小明和小红共同有一张银行卡,一共是500万,小红再柜台查看余额有100万,因此取钱50万, 小明再取款机查看有100万因此取钱300万,再同时操作时,如果没有线程同步处理,那么小明和小红就会有可能取出 100万,运行代码那么银行就会负50万,而且数据很乱,各跑各的

public class BankThread {
    public static void main(String[] args) {
        //账户
        Account account=new Account(100,"买房子基金");

        Drawing you=new Drawing(account,50,"小明");
        Drawing tDrawing1=new Drawing(account,100,"小红");
        you.start();
        tDrawing1.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;
     }
    //取钱
    public  void run(){
        //判断有没有钱
        if(account.money-DrawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
        }
        try {
            Thread.sleep(100); //模拟延时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //卡内余额=余额-你取的钱
        account.money=account.money-DrawingMoney;
        //你手里的钱
        nowMoney=nowMoney+DrawingMoney;
        System.out.println(account.name+"余额为:"+ account.money);
        System.out.println(this.getName()+"手里的钱:"+nowMoney);
    }
}

运行结果

案例2:
比如春节过节了,我们都要买票回家,此时买票的人有10000个,但是到达目的地的票只有100张,如果每个人再同时进行抢票行为那么就会变成负9900张票,让它们每人拿一张,但实际情况是这样吗? 在我们生活中如果只有100张票,前面100个人购买完成后,后面提示的是,手慢已抢完。 在我们程序中就是这100张票再被拿完之后后续在进的线程就不能在分配这个票的资源,并且这100张票还需要排队购买否则就会造成 你买到了第一张 我也买到了第一张的问题

//不安全的买票
public class MaiPiao {
    public static void main(String[] args) {
        BuyTicket tBuyTicket =new BuyTicket();
        new Thread(tBuyTicket,"小红").start();
        new Thread(tBuyTicket,"小明").start();
        new Thread(tBuyTicket,"黄牛党").start();
    }
}
class  BuyTicket implements  Runnable{
//票
    private  int ticketBums=10;
    boolean flag=true;
    @Override
    public void run() {
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void buy() throws InterruptedException {
        //判断是否有票
        if(ticketBums<=0){
            flag=false;
            return;
        }
        //模拟延时,放大问题
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketBums--);
    }
}

运行结果

案例3: 不安全的 线程集合,期待值是10000万但是并没有

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

            }).start();
        }
        //期待值是10000万
        System.out.println(list.size());
    }
}

运行结果

对列和锁

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

    一个线程持有锁会导致其他所有需要此锁的线程挂起在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    重复说明一下这里的性能问题:线程同步的条件是 对列 + 锁 (保证线程的安全性)和生活中上厕所的案例类似:1是排队,2是锁门,但是如果用到线程同步就会损失性能,比如一个优先级比较高的线程等待一个优先级低的线程释放锁就会出现性能倒置的问题。

这里解释一下【性能倒置】:比如有些人上厕所2分钟解决,但是你上厕所需要2小时,这个时候2分钟的那个人一直都在等待你优先解决

来重新写一下上面的三个案例

集合线程:在run启动里,可以将同时需要操作的对象锁定,并将操作操作该对象的代码放入锁代码块中,等待第一个线程操作完毕后,第二个才再次操作

//安全的 线程集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List list =new ArrayList();
        for (int i = 0; i <10000; i++) {
            new Thread(()->{
                synchronized (list){ //将list对象锁住,第一个若是没有操作完毕不允许另一个进入
                    list.add(Thread.currentThread().getName());
                }
            }).start();

        }
        Thread.sleep(100);
        //期待值是10000万
        System.out.println(list.size());
    }
}

运行结果


将上面银行的案例经过处理加锁,在you取走50块钱后,tDrawing1想在取100,就取不到了,输出tDrawing1钱不够了取不到

在run线程启动的代码块中将共同要操作的对象—>账户锁定,线程都需要排队来进行操作,跟上面的集合类似,锁定对象,将操作该对象的内容放至锁定块中

public class BankThread {
    public static void main(String[] args) {
        //账户
        Account account=new Account(100,"买房子基金");

        Drawing you=new Drawing(account,50,"you");
        Drawing tDrawing1=new Drawing(account,100,"tDrawing1");
        you.start();
        tDrawing1.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;
     }
    //取钱
    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();
            }
            //卡内余额=余额-你取的钱
            account.money = account.money - DrawingMoney;
            //你手里的钱
            nowMoney = nowMoney + DrawingMoney;
            System.out.println(account.name + "余额为:" + account.money);
            System.out.println(this.getName() + "手里的钱:" + nowMoney);
        }
    }
}

将上面的案例修改为安全的买票,这个案例因为不是一个对象,则在方法前面加锁,哪一个线程调用这个方法则需要排队 等上一个线程执行完毕后再次进入。

public class MaiPiao {
    public static void main(String[] args) {
        BuyTicket tBuyTicket =new BuyTicket();
        new Thread(tBuyTicket,"小红").start();
        new Thread(tBuyTicket,"小明").start();
        new Thread(tBuyTicket,"黄牛党").start();
    }
}
class  BuyTicket implements  Runnable{
//票
    private  int ticketBums=10;
    boolean flag=true;
    @Override
    public void run() {
        while (flag){
            try {

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9462d523cf574960b950533fe727b182~tplv-k3u1fbpfcp-watermark.image?)
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1b3b727b75bd4459b7e2413de1d15f78~tplv-k3u1fbpfcp-watermark.image?)
    private  synchronized    void buy() throws InterruptedException {
        //判断是否有票
        if(ticketBums<=0){
            flag=false;
            return;
        }
        //模拟延时,放大问题
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketBums--);
    }
}

运行结果

总结:锁的对象就是变化的量,需要增删改查的对象或者是方法

死锁

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

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        Makeup tMakeup=new Makeup(0,"小红");
        Makeup tMakeup1=new Makeup(1,"小白");
        tMakeup.start();
        tMakeup1.start();
    }
}
//口红
class Lipstick{

}
//镜子
class Mirror{
    
}
class  Makeup  extends Thread {
    //需要的资源只有一份,用static来保证只有一份
    static Lipstick tLipstick=new Lipstick ();
    static Mirror tMirror=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 (tLipstick){
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
                synchronized (tMirror){
                    System.out.println(this.girlName+"获得镜子的锁");
                }

            }

        }else{
            synchronized (tMirror){
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(2000);
                synchronized (tLipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                }

            }
        }
    }
}

运行结果 小白始终在等口喊 小红始终在等待镜子,但镜子一直被小白拿着不放,而小红一直拿着口红不释放


怎么解决上面的问题呢

将自己已经用完的空红或者是镜子释放出来让其他线程使用,或者是不使用if else 用上面的一个分支将镜子和口红一起锁,每一个线程使用完镜子和口红 一起使用完毕在释放给第二个线程(但是有损效率) ,具体是需要根据业务情况而定。

private  void makeup() throws InterruptedException {
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    if(choice==0){
        synchronized (tLipstick){
            System.out.println(this.girlName+"获得口红的锁");
            Thread.sleep(1000);
        }
        synchronized (tMirror){
            System.out.println(this.girlName+"获得镜子的锁");
        }
    }else{
        synchronized (tMirror){
            System.out.println(this.girlName+"获得镜子的锁");
            Thread.sleep(2000);
        }
        synchronized (tLipstick){
            System.out.println(this.girlName+"获得口红的锁");
        }
    }
}

修改后的运行结果


死锁避免的方法

    互斥条件:一个资源每次只能被一个进程使用。请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放不剥夺田间:进程已获得的资源,在未使用完之前,不得强行剥夺循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
Lock(锁)
    从JDK5.0开始,java提供了更强大的线程同步机制–通过显示定义同步锁对象来是仙女同步,同步锁使用Lock对象充当java.util.concurrent.lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。与synchronized不同的可以理解未Lock是可以看得见的,加锁和释放锁都需要代码控制而synchronized是隐式的
public class TestLock {
    public static void main(String[] args) {
        TestLock2 tTestLock2=new TestLock2();
         new Thread(tTestLock2,"haihai").start();
         new Thread(tTestLock2,"yanyan").start();
         new Thread(tTestLock2,"wenwen").start();
    }
}
class TestLock2 implements  Runnable {

    int ticketNums=10;
    //定义lock锁
    private final ReentrantLock Lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                Lock.lock();
                if(ticketNums>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"名, 票:"+ticketNums--);

                }else{
                    System.out.println(Thread.currentThread().getName()+"名, 票没有了");
                    break;
                }
            }finally {
                Lock.unlock();
            }
        }
    }
}

运行结果

    synchronized与Lock的对比Lock是显示锁(手动开启和关闭锁,别忘记关机锁噢)synchronized是隐式锁,出了作用域就自动释放Lock只有代码块锁,synchronized 有代码块锁和方法锁使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
线程协作

生产者消费者模式???(问题)

线程通信

    假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费如果仓库中没有产品,则生产者将产品否则停止生产并等待,直到仓库中的产品被消费者取走位置如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中在此放入产品为止

线程通信-分析

    这是一个线程同步问题,生产者和消费者共享一个资源并且生产者和消费者之间相互依赖,互为条件对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费

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

    synchronized可组织并发更新同一个共享资源,实现了同步synchronized不能用来实现不同线程之间的消息传递(通信)
//测试:生产者消费者模型-->利用缓冲区解决:管程法
public class TestPC {
    public static void main(String[] args) {
        //消费者和生产者都需要操作缓冲区
        SynContainer tSynContainer=new SynContainer();
        //生产者
        new Productor(tSynContainer).start();
        //消费者
        new Consumer(tSynContainer).start();
    }
}
//生产者
class  Productor   extends Thread{
    SynContainer container;
    public  Productor(SynContainer container){
        this.container=container;
    }
    public  void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i+"只鸡");
            container.push(new Chicken(i));
        }
    }
}
//消费者
class Consumer extends  Thread{
    SynContainer container;
    public  Consumer(SynContainer container){
        this.container=container;
    }
    public  void run(){
        for (int i = 0; i < 100; i++) {
            //container类中的pop()的返回值是 Chicken 对象所以可以.id获取是第几只
            System.out.println("消费了"+container.pop().id+"只鸡");
        }
    }
}
//产品
class  Chicken{
    int id;
    public Chicken(int id){
        this.id=id;
    }
}
//缓冲区
class SynContainer{
    //容器大小,需要一个容器大侠
    Chicken[] tchickens=new Chicken[10];
    //容器计数器
    int count=0;
    //生产者放入产品
    public  synchronized  void push(Chicken chickens) {
        //如果容器满了就需要等待消费者消费
        if(count==tchickens.length){
            //通知消费者消费,生产等待
            try{
            this.wait();}catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要丢入产品
        tchickens[count]=chickens;
        count++;
        this.notifyAll();
    }
    //消费者消费产品
    public  synchronized  Chicken pop(){
        //判断能否消费
        if(count==0){
            //等待消费者生产,消费者等待
            try{
                this.wait();}catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken= tchickens[count];
        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

运行结果:以上代码 也可以使用标志位判断通信

标志位判断通信方式

public class BiaoYan {
    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;
    }
    public  void run(){
        for (int i = 0; i < 20; i++) {
            if(i%2==0){
                this.tv.play("抖音:记录美好生活");
            }else{
                this.tv.play("快乐大本营");
            }
        }
    }
}

class  watcher extends  Thread{
    Tv tv;
    public watcher(Tv tv){
        this.tv=tv;
    }
    public  void run(){
        for (int i = 0; i < 20; i++) {
                this.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; // 取反 平常写法是 this.flag=false
    }

    //观看
    public synchronized  void watch(){
        if(flag){ //为真说明演员还没有表演
            try {
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("观看了:"+voice);
        //通知演员表演
        this.notifyAll();//通知唤醒其他线程
        this.flag=!this.flag; // 取反 平常写法是 this.flag=false
    }
}

线程池

后面有时间再写吧 这是线程中的最后一个部分了,,,,

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/704523.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号