多线程 (Java)

线程和进程的区别

进程: 执行中的程序叫做进程(Process),操作系统同时执行多个进程,每个进程

独立运行。

线程: 一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同

的任务。

在这里插入图片描述

多线程

原来的执行程序为一条路径,现在加入多线程则存在多条执行路径。

三种实现方法:
  1. 继承Thread类 子类对象调用start() -----> 由于java是单继承,不推荐使用

    public class ThreadDemo01 extends Thread{
        @Override
        public void run() {
            for(int i  = 1;i<=20;i++){
                System.out.println("一遍讲课.....");
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
    
            //创建线程  子类对象
            ThreadDemo01  th = new ThreadDemo01();
    
            //方法的调用,不是线程的开启
            //th.run();
            
            //开启线程
            th.start();
    
            for(int i  = 1;i<=20;i++){
                System.out.println("一遍喝水.....");
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
        }
    }
    
    // 运行结果:
    一遍喝水.....
    一遍讲课.....
    一遍喝水.....
    一遍讲课.....
    一遍讲课.....
    一遍喝水.....
    一遍喝水.....
    
    1. 实现Runnable接口,静态代理+start() ----> 推荐使用
public class ThreadDemo02 implements Runnable{

    @Override
    public void run() {
        for(int i  = 1;i<=20;i++){
            System.out.println("一边陪女朋友.....");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        ThreadDemo02 demo = new ThreadDemo02();
        //1.创建线程
        Thread th = new Thread(demo);

        //2.开启线程
        th.start();


        for(int i  = 1;i<=20;i++){
            System.out.println("一边敲代码.....");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
// 运行结果:
一边敲代码.....
一边陪女朋友.....
一边敲代码.....
一边陪女朋友.....
一边敲代码.....
一边陪女朋友.....
一边敲代码.....
一边陪女朋友.....
一边敲代码.....
  1. juc包下Callable接口 call方法可以抛出异常,可以得到返回值。----> 使用复杂,不推荐

    public class Racer05 implements Callable<Integer> {
        //记录赢的人的名字  -->共享
        private String winner;
        /*
            定义线程体
         */
        @Override
        public Integer call() {
            //i为步数
            for(int i=1;i<=100;i++){
                if("pool-1-thread-2".equals(Thread.currentThread().getName()) &&  i%10==0){
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"正在跑第"+i+"步");
                //判断游戏是否停止
                if(check(i)){
                    return i;
                }
            }
            return -1;
        }
    
        //检查游戏是否终止
        //参数: 步数  返回值: boolean   true->终止  false->继续
        public boolean check(int steps){
            if(winner!=null){
                return true;
            }else if(steps==100){
                winner = Thread.currentThread().getName();
                return true;
            }
            return false;
        }
    
        public static void main(String[] args) throws Exception{
            Racer05 racer = new Racer05();
    
            //线程池 ->构建执行 服务
            ExecutorService server =  Executors.newFixedThreadPool(2);
    
            //提交还行
            Future<Integer> fu = server.submit(racer);
            Future<Integer> fu2 = server.submit(racer);
            //获取结果
            Integer i1 =  fu.get();
            Integer i2 =  fu2.get();
    
            System.out.println(i1 + "--->"+i2);
    
            //终止执行服务
            server.shutdown();
        }
    
        // 运行结果:
    兔子正在跑第1步
    乌龟正在跑第1步
    兔子正在跑第2步
    乌龟正在跑第2步
    乌龟正在跑第3步
    兔子正在跑第3步
    兔子正在跑第4步
    兔子正在跑第5步
    兔子正在跑第6步
    兔子正在跑第7步
    兔子正在跑第8步
    兔子正在跑第9步
    乌龟正在跑第4步
    乌龟正在跑第5步
    乌龟正在跑第6步
    乌龟正在跑第7步
    乌龟正在跑第8步
    乌龟正在跑第9步
    乌龟正在跑第10步
    乌龟正在跑第11步
    乌龟正在跑第12步
    乌龟正在跑第13步
    乌龟正在跑第14步
    乌龟正在跑第15步
    乌龟正在跑第16步
    乌龟正在跑第17步
    乌龟正在跑第18步
    乌龟正在跑第19步
    乌龟正在跑第20步
    乌龟正在跑第21步
    乌龟正在跑第22步
    乌龟正在跑第23步
    乌龟正在跑第24步
    乌龟正在跑第25步
    乌龟正在跑第26步
    乌龟正在跑第27步
    乌龟正在跑第28步
    乌龟正在跑第29步
    乌龟正在跑第30步
    乌龟正在跑第31步
    乌龟正在跑第32步
    乌龟正在跑第33步
    乌龟正在跑第34步
    乌龟正在跑第35步
    乌龟正在跑第36步
    乌龟正在跑第37步
    乌龟正在跑第38步
    乌龟正在跑第39步
    乌龟正在跑第40步
    乌龟正在跑第41步
    乌龟正在跑第42步
    乌龟正在跑第43步
    乌龟正在跑第44步
    乌龟正在跑第45步
    乌龟正在跑第46步
    乌龟正在跑第47步
    乌龟正在跑第48步
    乌龟正在跑第49步
    乌龟正在跑第50步
    乌龟正在跑第51步
    乌龟正在跑第52步
    乌龟正在跑第53步
    乌龟正在跑第54步
    乌龟正在跑第55步
    乌龟正在跑第56步
    乌龟正在跑第57步
    乌龟正在跑第58步
    乌龟正在跑第59步
    乌龟正在跑第60步
    乌龟正在跑第61步
    乌龟正在跑第62步
    乌龟正在跑第63步
    乌龟正在跑第64步
    乌龟正在跑第65步
    乌龟正在跑第66步
    乌龟正在跑第67步
    乌龟正在跑第68步
    乌龟正在跑第69步
    乌龟正在跑第70步
    乌龟正在跑第71步
    乌龟正在跑第72步
    乌龟正在跑第73步
    乌龟正在跑第74步
    乌龟正在跑第75步
    乌龟正在跑第76步
    乌龟正在跑第77步
    乌龟正在跑第78步
    乌龟正在跑第79步
    乌龟正在跑第80步
    乌龟正在跑第81步
    乌龟正在跑第82步
    乌龟正在跑第83步
    乌龟正在跑第84步
    乌龟正在跑第85步
    乌龟正在跑第86步
    乌龟正在跑第87步
    乌龟正在跑第88步
    乌龟正在跑第89步
    乌龟正在跑第90步
    乌龟正在跑第91步
    乌龟正在跑第92步
    乌龟正在跑第93步
    乌龟正在跑第94步
    乌龟正在跑第95步
    乌龟正在跑第96步
    乌龟正在跑第97步
    乌龟正在跑第98步
    乌龟正在跑第99步
    乌龟正在跑第100步
    兔子正在跑第10

使用内部类开启多线程

了解内部类:https://blog.csdn.net/weixin_45464739/article/details/109208715

public class ThreadDemo06 {

    //静态内部类
    static class Inner extends Thread{
        @Override
        public void run() {
            for(int i=1;i<=20;i++){
                System.out.println("一边打游戏....");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        // 静态内部类 
        // 如果去掉static, 变成 成员内部类
        // 可以用 new ThreadDemo06().new Inner().start(); 开启多线程 
        new ThreadDemo06.Inner().start();  

        //局部内部类
        class Inner02 implements Runnable{

            @Override
            public void run() {
                for(int i=1;i<=20;i++){
                    System.out.println("一边抽烟....");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        //创建线程+开启
        new Thread(new Inner02()).start();

        //匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=1;i<=20;i++){
                    System.out.println("一边喝啤酒->科罗娜....");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


        //lambda 表达式
        new Thread(()->{
            for(int i=1;i<=20;i++){
                System.out.println("一边吃鸭货....");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();



    }
}

线程的状态

5种状态
  1. 新生 : new就是新生

  2. 就绪 : 当一个线程调用start(),就进入到就绪队列进行等待,等待cpu的调度

    如何进入就绪状态:
    1.start 2.线程切换 3.yield 礼让线程 4.阻塞解除

    ​ yield() : 让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态。(比如此时A在运行,B是就绪,A被执行yield()后, 那么A和B都是就绪状态,然后CPU随机选择A或B分配时间片,所以下个执行可能仍然是A,也可能是B )

    public static void main(String[] args) {
        //1.新生
        Thread th = new Thread();
        //2.就绪
        th.start();
    }
    
    
  3. 运行 : 当cpu分配时间片给哪一个线程,这个线程才能运行

  4. 阻塞 : 不是一定会出现的,非正常运行了 sleep

    如何进入阻塞状态:
    1.sleep 2. wait 3. join 插队 4.IO

    sleep() : 当一个执行到sleep方法,当前线程就会休眠指定时间,在休眠过程中,让出cpu的资源

    ​ 释放cpu 不放锁 (抱着资源睡觉)

    ​ sleep() 的作用 1.放大问题的可能性 2.模拟网络延迟 倒计时

    wait() : 释放cpu 放锁 --> 唤醒 notify()

    一个线程如果进入阻塞状态,阻塞解除没有办法直接恢复到运行,会直接恢复就绪

    join(): **join() 能够让其他线程阻塞,让调用自己的线程插队,抢到cpu的使用。join()方法内放一个参数作为插队的时间。**比如线程A调用该方法,就能抢到cpu的使用权,在插队期间,其他线程必须等待线程A执行,在A插队结束后,才能有继续往后执行。如果参数为空,那么线程A可以一直插队,直到线程A运行到死亡为止。

  5. 死亡|终止 : 线程结束

    一个线程如果一旦终止,没有办法恢复

线程状态Thread.State 该类型是一个枚举类型

判断线程状态Thread.State
getState() 获取线程状态 返回值是一个枚举类型

NEW   // 新生 : 尚未启动的线程处于此状态

WAITING     // 就绪  :  正在等待另一个线程执行特定动作的线程处于此状态
TIMED_WAITING // 就绪一段时间:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

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

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

TERMINATED  // 死亡:已退出的线程处于此状态

线程优先级:

​ 每一个线程都有优先级别问题 优先执行谁, 但是 不能控制一个线程是否先执行,只是概率更大
1~10
1为最小 10为最大 默认为5
static int MAX_PRIORITY
线程可以具有的最高优先级。
static int MIN_PRIORITY
线程可以具有的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。

​ getPriority() 获取优先级
setPriority() 设置优先级

public class ThreadStateDemo10 implements Runnable{


    @Override
    public void run() {

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

    public static void main(String[] args) throws InterruptedException {
        Thread th = new Thread(new ThreadStateDemo10());
        System.out.println("线程th的状态为: "+th.getState());
        th.start();

       th.setPriority(Thread.MAX_PRIORITY);

        System.out.println("线程th的优先级为: "+th.getPriority());

    }
}

// 运行结果: 
线程th的状态为: NEW
线程th的优先级为: 10
A线程结束了
线程基本信息

调用方法: Thread.currentThread()

​ currentThread() 取得当前正在运行的线程对象,也就是取得自己本身

调用方法: Thread.currentThread().isAlivel()

​ isAlivel() 判断线程是否还“活”着,即线程是否还未终止。

调用方法: Thread.currentThread().getPriority()

​ getPriority() 获得线程的优先级数值

调用方法: Thread.currentThread().setPriority()

​ setPriority() 设置线程的优先级数值

调用方法: Thread.currentThread().setName()

​ setName() 给线程一一个名字

调用方法: Thread.currentThread().getName()

​ getName() 取得线程的名字

同步锁

线程安全问题:
当多线程共享同一份资源的时候,才有可能存在线程不安全问题

​ 同步锁: synchronized
同步方法 : 同步静态方法 同步成员方法
同步块{} : synchronized(this|类名.class|资源){
代码段…
}

​ {} -> 排队执行的代码块
this|类名.class|资源 : 线程要求的执行{}中代码的执行条件

​ 注意:
同步的代码范围如果 太大,效率低,如果太小,锁不住
锁不变的内容(this|资源…|类…)

​ 同步块: 类的class对象,相当于把真个类锁住了,效率相对较低–>推荐使用this

同步块锁类: synchronized (SynDemo11.class) { }

使用的是类的class对象,相当于把整个类锁住了,效率相对较低 --> 不如锁对象

public class SynDemo11 implements Runnable{

    int tickets  = 20;


    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            synchronized (SynDemo11.class){ // 同步块
                if(tickets<=0){
                    break;
                }
                //A B  C
                //买票
                System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } //  同步块
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo11 web = new SynDemo11();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}

// 运行结果:
张绍杰正在购买第20
张绍杰正在购买第19
张绍杰正在购买第18
张绍杰正在购买第17
阮志志正在购买第16
何志豪正在购买第15
何志豪正在购买第14
阮志志正在购买第13
阮志志正在购买第12
阮志志正在购买第11
阮志志正在购买第10
阮志志正在购买第9
阮志志正在购买第8
阮志志正在购买第7
阮志志正在购买第6
阮志志正在购买第5
阮志志正在购买第4
阮志志正在购买第3
张绍杰正在购买第2
张绍杰正在购买第1
同步块锁对象: synchronized (this) { }

this,相当于调用成员方法的对象,相当于锁住了整个对象,整个对象的所有资源都锁住了,如果只想锁住某一个资源,只锁住这个资源就可以 --> 不如锁资源

public class SynDemo12 implements Runnable{

    int tickets  = 20;
    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            synchronized (this){  //当前调用成员方法的对象
                if(tickets<=0){
                    break;
                }
                //A B  C
                //买票
                System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo12 web = new SynDemo12();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}

// 运行对象:
张绍杰正在购买第20
张绍杰正在购买第19
张绍杰正在购买第18
张绍杰正在购买第17
张绍杰正在购买第16
阮志志正在购买第15
阮志志正在购买第14
阮志志正在购买第13
何志豪正在购买第12
何志豪正在购买第11
何志豪正在购买第10
何志豪正在购买第9
何志豪正在购买第8
何志豪正在购买第7
何志豪正在购买第6
何志豪正在购买第5
何志豪正在购买第4
何志豪正在购买第3
何志豪正在购买第2
何志豪正在购买第1
同步块锁资源: synchronized (tickets) { }

同步块: 资源,一定要锁不变的东西, 而且要是引用数据类型 --> 自定义的 引用数据类型的地址

public class SynDemo13 implements Runnable{
    //资源  20张 票
    Ticket tickets  = new Ticket();

    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            synchronized (tickets){  //当前调用成员方法的对象
                if(tickets.num<=0){
                    break;
                }
                //A B  C
                //买票
                System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets.num--);
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo13 web = new SynDemo13();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}

//票
class Ticket{
    int num = 20;
}

//运行结果:
张绍杰正在购买第20
张绍杰正在购买第19
张绍杰正在购买第18
张绍杰正在购买第17
张绍杰正在购买第16
阮志志正在购买第15
阮志志正在购买第14
何志豪正在购买第13
何志豪正在购买第12
何志豪正在购买第11
何志豪正在购买第10
何志豪正在购买第9
何志豪正在购买第8
阮志志正在购买第7
阮志志正在购买第6
阮志志正在购买第5
阮志志正在购买第4
阮志志正在购买第3
阮志志正在购买第2
阮志志正在购买第1
同步块锁资源+减少范围+多加一重检查: synchronized (tickets) { }

减少锁资源的范围同时,增加一重检查,所以是双重检查 -->提高效率

减少锁资源的范围,可以精准锁定,提高效率,那为什么要多加一重检查呢?

​ 原因:如果不多加一重检查,很可能会产生一种情况: 在票数为1的时候,由于锁的范围减少了,1个人都进去,2个人 synchronized的门口进行等候,按照顺序分别买了第1和第0还有第-1张票,然后在下个while循环中,根据break跳出循环。所以就会产生买到第0和第-1张票的情况。

public class SynDemo14 implements Runnable{
    //资源  100张 票
    Ticket tickets  = new Ticket();


    @Override
    public void run() {
        //A B  C
        while(true){

            //停止循环的条件
            if(tickets.num<=0){
                break;
            }

            //A B  C
            synchronized (tickets){  //当前调用成员方法的对象
                //买票
                if(tickets.num>0){
                    System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets.num--);
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo14 web = new SynDemo14();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}
同步方法

这里synchronized不能放到run()方法上,因为如果synchronized放在run()上面的话,会锁住整个run()方法,那么只能有一个人进去把张票买完了才出来结束自己的线程,由于tickets是共享的,此时tickets已经为0,其他人也买不了票。如下所示:

//A B  C
@Override
public synchronized void run() {

    while(true){
        //停止循环的条件
            if(tickets<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);

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

    }
}
//运行结果:
张绍杰正在购买第20
张绍杰正在购买第19
张绍杰正在购买第18
张绍杰正在购买第17
张绍杰正在购买第16
张绍杰正在购买第15
张绍杰正在购买第14
张绍杰正在购买第13
张绍杰正在购买第12
张绍杰正在购买第11
张绍杰正在购买第10
张绍杰正在购买第9
张绍杰正在购买第8
张绍杰正在购买第7
张绍杰正在购买第6
张绍杰正在购买第5
张绍杰正在购买第4
张绍杰正在购买第3
张绍杰正在购买第2
张绍杰正在购买第1

为了避免上面的情况,每个人都应该是进去买一次票,然后出来,这样3个人都能买到票,同步方法如下所示:

public class SynDemo15 implements Runnable{
    int tickets  = 20;


    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            if(buyTickets()){
                break;
            }
        }
    }

    public synchronized boolean buyTickets(){
        if(tickets<=0){
            return true;
        }
        //A B  C
        //买票
        System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }



    public static void main(String[] args) {
        //资源
        SynDemo15 web = new SynDemo15();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}
// 运行结果:
张绍杰正在购买第20
张绍杰正在购买第19
阮志志正在购买第18
阮志志正在购买第17
何志豪正在购买第16
何志豪正在购买第15
何志豪正在购买第14
何志豪正在购买第13
何志豪正在购买第12
何志豪正在购买第11
何志豪正在购买第10
何志豪正在购买第9
何志豪正在购买第8
阮志志正在购买第7
阮志志正在购买第6
阮志志正在购买第5
阮志志正在购买第4
阮志志正在购买第3
阮志志正在购买第2
阮志志正在购买第1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值