多线程内容

线程(thread)

        一个程序内部的一条执行路径。main方法的执行就是一条单独的执行路径。

多线程:

        是指从软硬件上实现多条执行流程的技术。

 1.多线程的创建:

        方法一:继承Thread类

实现步骤:

  • 先写一个Thread的子类,重写run方法。
  • public class MyThread extends Thread{
        @Override
        public void run() {
            //run方法中写线程要执行的事情。
            for (int i = 1; i <= 100; i++) {
                System.out.println(getName()+"已经下载了..."+i+"%");
            }
        }
    }
  • 创建Thread的子类对象。
  • 调用start方法启动线程(自动执行run方法,不可以直接调用run方法。只能调用start方法开启线程。)
  • /**
     * 线程的第一种创建方式步骤:
     *      1.先写一个Thread的子类,重写run方法。
     *      2.创建Thread的子类对象。
     *      3,调用start方法启动线程(自动执行run方法)
     *      (不可以直接调用run方法,只能调用start方法开启线程)
     */
    public class Demo {
        public static void main(String[] args) throws InterruptedException {
            //创建线程对象
            MyThread t1=new MyThread();
            //设置线程名称
            t1.setName("海贼王");
            //启动线程
            t1.start();
    
            MyThread t2=new MyThread();
            t2.setName("火影忍者");
            t2.start();
    
            //这里的for循环被主线程执行:主线程的名称是main。
            for (int i = 0; i < 100; i++) {
                //线程睡眠
                Thread.sleep(1000);
                Thread thread=Thread.currentThread();
                System.out.println(thread.getName()+"已经下载了..."+i+"%");
            }
        }
    }

        方法二:实现Runnable接口

实现步骤:

  • 定义一个Runable接口的实现类,作为线程的任务类,需要重写run方法。
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //获取当前线程的对象:哪一个线程执行run方法,这里获取的就是哪一个线程对象
            Thread thread=Thread.currentThread();
            //获取线程名
            String name=thread.getName();
            System.out.println(name+"正在下载..."+i+"%");
        }
    }
}
  • 创建线程任务的对象。
  • 把线程任务的对象,交给Thread类的对象。
  • 让Thread的对象调用start方法,启动线程。(会自动执行任务类中的run方法)
/**
 * 线程第二种创建方式的步骤:
 *      1.写一个Runnable接口的实现类,作为线程的任务类,需要重写run方法
 *      2.创建线程任务的对象
 *      3.把线程任务的对象,交给Thread类的对象
 *      4.让Thread的对象调用start方法,启动线程(会自动执行任务类中的run方法)
 *
 * 优点:
 */
public class Demo {
    public static void main(String[] args) {
        //创建线程任务类的对象
        MyRunnable mr=new MyRunnable();

        //创建线程对象,把任务交给线程
        Thread t1=new Thread(mr);

        //启动线程
        t1.start();

        /**
         *实现Runnable接口(匿名内部类形式)
         */
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i++) {
                    //获取当前线程的对象:哪一个线程执行run方法,这里获取的就是哪一个线程对象
                    Thread thread = Thread.currentThread();
                    //获取线程名
                    String name = thread.getName();
                    System.out.println(name + "正在下载..." + i + "%");
                }
            }
        });
        t2.start();
    }
}

优点:线程任务类只是实现了Runnable接口可以继续继承和实现。

        方法三:实现Callable接口

优点:可以得到线程执行的返回值。

        实现步骤

  • 写一个Callable接口的实现类,重写call方法
/**
 * Callable接口的实现类:用来封装线程要执行的事情,重写call方法,方法执行完毕之后有返回值
 *      想要返回什么类型的数据,泛型就写什么类型
 */
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 10;
    }
}
  • 创建Callable接口的实现类对象
  • 创建一个FutureTask类的对象,把Callable实现类对象封装进来
  • 创建Thread对象,把FutureTask对象封装进来。
  • 调用Thread对象的start方法。
/**
 * 线程的第三种创建方式的步骤:
 *      1.写一个Callable接口的实现类,重写call方法
 *      2.创建Callable接口的实现类的对象
 *      3.创建一个FutureTask类的对象,把Callable实现类对象封装进来
 *      4.创建Thread对象,把FutureTask对象封装进来
 *      5.调用Thread对象的start方法。
 */
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建Callable接口的实现类的对象
        MyCallable mc=new MyCallable();

        //创建一个FutureTask类的对象,把Callable实现类对象封装进来
        FutureTask<Integer> ft=new FutureTask<>(mc);

        //创建Thread对象,把FutureTask对象封装进来
        Thread t1=new Thread(ft);

        //调用Thread对象的start方法。
        t1.start();

        //等线程的任务代码执行完毕了(call方法执行完了),就要返回结果,结果会封装到FutureTask对象里面
        //通过FutureTask对象获取返回值。
        Integer result=ft.get();
        System.out.println("线程执行的结果是:"+result);
    }
}

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

        可以在线程执行完毕后去获取线程执行的结果。

缺点: 编码复杂一点

 

 

 2.Thread的常用方法:

getName():获取当前线程的名称。

setName():设置当前线程的名称。

Thread currentThread():返回当前正在执行的线程对象的引用。

        1.此方法是Thread类的静态方法,可以直接调用Thread类调用。

        2.这个方法在哪个线程执行中调用,就会得到哪个线程对象。

sleep(long time): 指定休眠时间。单位为毫秒。

 3.线程安全:

多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。

/**
 * 账户类:用于封装账户的金额信息
 */
public class Account {
    private int money=10000;//余额

    //取钱的方法:其实就是对账户的余额进行更新操作。
    public void drawMoney(int money){
        String name = Thread.currentThread().getName();
        //判断余额是否足够
        if (this.money>=money){
            try {
                Thread.sleep(1000);//线程1,线程2
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.money-=money;
            System.out.println(name+"过来取钱了,取了"+money+"元,还剩下"+this.money+"元");
        }else {
            System.out.println(name+"过来取钱了,可是没钱了");
        }
    }
}
public class DrawThread extends Thread{
    private Account account;

    public DrawThread(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        account.drawMoney(10000);
    }
}
/**
 * 线程安全问题发生的原因:多个线程同时访问同一个共享资源且存在修改该资源。
 */
public class Demo {
    public static void main(String[] args) {
        Account account=new Account();
        DrawThread dt1=new DrawThread(account);
        DrawThread dt2=new DrawThread(account);

        dt1.start();
        dt2.start();
    }
}

 

 4.线程同步:

核心思想:加锁(把访问共享资源的代码封住,只有获取锁的线程可以执行,没有获得锁就不能执行。)

        1.同步代码块:

作用:把出现线程安全问题的核心代码给上锁。

 

        锁对象规范要求:

  •         规范上:建议使用共享资源作为锁对象。
  •         对于实例方法建议使用this作为锁对象。
  •         对于静态方法建议谁用字节码(类名.class)对象作为锁对象。
  • /**
     * 同步代码块
     */
            synchronized (Aaccount.class){
                //判断余额是否足够
                if(Aaccount.money>=money){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //可以取钱
                    Aaccount.money-=money;
                    System.out.println(name+"过来取钱,取了"+money+"元,还剩"+Aaccount.money);
                }else {
                    System.out.println(name+"过来取钱,余额不足");
                }
            }

        2.同步方法:

 

/**
     * 同步方法
     * 如果是实例方法:锁对象是this
     * 如果是静态方法:锁对象是类名.class
     */
    public synchronized void drawMoeny(int money){
        //获取线程名称,方便后面使用
        String name = Thread.currentThread().getName();

        //判断余额是否足够
        if(Aaccount.money>=money){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //可以取钱
            Aaccount.money-=money;
            System.out.println(name+"过来取钱,取了"+money+"元,还剩"+Aaccount.money);
        }else {
            System.out.println(name+"过来取钱,余额不足");
        }
    }

        3.Lock锁:

为了更清晰的表达如何加锁和释放锁,Lock更加灵活,方便。Lock是接口,一般采用实现类ReentrantLock来表示锁对象。

public  void drawMoeny(int money){
        //获取线程名称,方便后面使用
        String name = Thread.currentThread().getName();

        lock.lock();//上锁
        //判断余额是否足够
        try {
            if(this.money>=money){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //可以取钱
                this.money-=money;
                System.out.println(name+"过来取钱,取了"+money+"元,还剩"+this.money);
            }else {
                System.out.println(name+"过来取钱,余额不足");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }

 5.线程通信:

所谓线程通信就是线程之间相互发送指令,从而改变线程执行状态。

        如何实现:

  • 多个线程之间需要有共性数据
  • 线程根据共享数据的情况决定自己该怎么做,是让线程等待,还是让线程唤醒。

        实际应用场景:

  • 生产者和消费者模型:生产者负责生产数据,消费者负责消费生产者产生的数据。
  • 要求:生产者线程生产完毕数据后唤醒消费者,然后等待自己,消费者消费玩该数据后唤醒生产者,然后等待自己。

        线程通信的前提:

                线程通信通常是在多个线程操作同一个共享资源的时候进行通信,且保证线程安全。

/*
账户类: 封装账户的余额信息,还有存钱和取钱的方法
 */
public class Acount {
    //账户的余额, 默认是10万元
    private int money = 100000;

    /**
     * 取钱的方法
     * @param money 要取钱的金额
     */
    //同步方法的锁对象:this
    public synchronized void drawMoney(int money){
        String name=Thread.currentThread().getName();
        //1.判断余额是否足够
        if (this.money>=money){
            //2.如果余额足够,开始取钱,取完钱后,唤醒其他等待的线程
            this.money-=money;
            System.out.println(name+"过来取了"+money+"元,还剩"+this.money+"元");
            this.notifyAll();
        }else {
            //3.如果余额不足,不能取钱,让当前线程等待
            try {
                this.wait();//释放锁等待。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }

    /**
     * 存钱的方法
     * @param money 要存钱的金额
     */
    //同步方法的锁对象时: this
    public synchronized void saveMoney(int money){
        String name=Thread.currentThread().getName();
        //1.判断余额是否足够
        if (this.money==0){
            //2.如果余额不足,开始存钱,存完钱后,唤醒其他等待的线程
            this.money+=money;
            System.out.println(name+"过来存了"+money+"元,还有"+this.money+"元");
            this.notifyAll();
        }else {
            //3.如果余额足够,不能存钱,让当前线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class DrawThread extends Thread{
    private Acount acount;

    public DrawThread(Acount acount){
        this.acount=acount;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            acount.drawMoney(100000);
        }
    }
}
public class SaveThread extends Thread{
    private Acount acount;

    public SaveThread(Acount acount){
        this.acount=acount;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            acount.saveMoney(100000);
        }

    }
}
public class Demo {
    public static void main(String[] args) {
        //创建三个存钱线程、两个取钱线程、这五个线程共享同一个账户。
        Acount acount=new Acount();
        SaveThread t1=new SaveThread(acount);
        t1.setName("爸爸");
        SaveThread t2=new SaveThread(acount);
        t2.setName("妈妈");
        SaveThread t3=new SaveThread(acount);
        t3.setName("哥哥");

        DrawThread t4=new DrawThread(acount);
        t4.setName("小弟");
        DrawThread t5=new DrawThread(acount);
        t5.setName("小妹");
        
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            t5.start();

    }
}

wait():当前线程等待,直到另一个线程调用notify()或notifyAll()唤醒自己

notify():唤醒正在等待对象监视器(锁对象)的单个线程

notifyAll():唤醒正在等待对象监视器(锁对象)的所有线程

6.线程池:

线程池就是一个可以复用线程的技术。

(如果用户每发起一个请求,后台就创建一个新的线程来处理,下次新任务又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。)

实现线程池:(JDk5起提供了代表线程池的接口:ExecutorService)

 

 

public class Demo1 {
    public static void main(String[] args) {
        //创建线程池对象
        ThreadPoolExecutor pool=new ThreadPoolExecutor(
                3,                          //核心线程数
                5,                      //最大线程数
                2,                         //临时线程存活时间
                TimeUnit.SECONDS,                       //指定存活时间的单位
                new ArrayBlockingQueue<>(10),   //指定任务队列
                Executors.defaultThreadFactory(),       //指定用哪个线程工厂创建队列
                new ThreadPoolExecutor.AbortPolicy()    //指定拒绝线程策略
        );
        //

        for (int i = 1; i <= 17; i++) {
            int j=i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"线程执行了.."+j+"号任务");
                }
            });
        }

        //关闭线程池
        pool.shutdown();

    }
}

 

public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor pool=new ThreadPoolExecutor(
                3,
                5,
                4,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        Future<Object> submit = pool.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                int sum = 0;
                for (int i = 1; i <= 100; i++) {
                    sum += i;
                }
                return sum;
            }
        });

        System.out.println(submit.get());
        pool.shutdownNow();
    }
}

 

 

线程池常见面试题:

临时线程什么时候创键?

        核心线程都在忙+任务队列满了,就会创建临时线程。

什么时候会开始拒绝任务?

        核心线程都在忙+临时线程也在忙+任务队列也满了,多余的任务就会触发拒绝策略

        

7.并发,并行

正在运行的程序(软件)就是一个独立的进程,线程是 属于进程的,多个线程其实是并发与并行同时进行的。

并发:

        CPU同时处理的线程数量有限。

        CPU会轮询为系统的每个线程服务,由于CPU切换速度很快,给我们的感觉这些线程在同时执行,这就是并发。

 

并行:

        在同一时刻上,同时有多个线程在被CPU处理并执行。

 

8.线程的生命周期:

线程的生命周期指的是,线程从创建到销毁的过程可能存在不同的状态。

线程的状态

        线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态转换。

        Java总共定义了6种状态

        6种状态都定义在Thread类的内部枚举类中。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值