Java多线程总结

java多线程

1. 进程

当程序进入内存运行时,就启动了一个进程,即进程是处于运行过程的程序。

操作系统会给每个进程分配独立的内存等资源,即进程时操作系统资源分配,调度和管理的最小单位。

进程包含三个特性:

  1. 独立性:每个进程拥有自己的地址空间,不经过进程的允许,一个用户进程不可直接方位其他进程的地址空间。
  2. 动态性:程序只是静态指令的集合,而进程是一个正在系统运行的活动指令的集合。进程加入了时间的概念,拥有自己的生命周期,这是程序不具备的。
  3. 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响。
    1. 并行:多条指令在多个处理器同时执行。
    2. 并发:多条指令在单个处理器切换执行。
2. 线程

多进程扩展了多线程的概念,使得一个进程可以同时并发处理多个任务,线程也被称为轻量级线程。

当进程被初始化后,主线程就被创建了。对于Java来说,main线程就是主线程,我们可以在main函数创建多条顺序执行路径,这些独立执行的路径都是线程。

由于线程间的通信是在同一个地址空间上进行的,不需要额外的同通信机制,使得通信更简洁,速度快。

CPU可以在不同的进程中轮换,进程又可以在不同的线程之间轮换。故线程是CPU执行和调度的最小单元。

3. 线程的创建
3.1 继承Thread类
  1. 子类重写run方法
  2. 主线程调用start方法
public class TestThread {
    public static void main(String[] args) {
        // 在主线程 打印1-100 其余两个线程 打印 1-100 同时进行
        MyThread thread1 = new MyThread();
        thread1.setName("thread-1");
        MyThread thread2 = new MyThread();
        thread2.setName("thread-2");
        Thread.currentThread().setName("thread-3");
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName()+">>>> "+i);
        }
        thread1.start();
        thread2.start();
    }
    static class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName()+">>>> "+i);
            }
        }
    }
}
3.2 实现Runnable接口
  1. 实现Runnable接口,实现run方法

  2. 创建Thread类,Runnable接口作为参数传递 相同方法传递相同

  3. 调用start方法

public class TestRunnable {
    public static void main(String[] args) {
        Runnable runnable1 = () -> {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + ">>>>> " + i);
            }
        };
        new Thread(runnable1).start();
        new Thread(runnable1).start();
    }
}

实现只有三个窗口有权限卖票:

使用Runnable接口:

public static void main(String[] args) {
        SellTickets sellTickets = new SellTickets();
        new Thread(sellTickets).start();
        new Thread(sellTickets).start();
        new Thread(sellTickets).start();
    }
    static class SellTickets implements Runnable{
    private int ticket=100;
        @Override
        public void run() {
            while (true){
                if (ticket<=0){ // 每次卖票操作先判断 
                    System.out.println("ticket is empty~~~");
                    return;
                }
                /*卖票操作 */
                System.out.println(Thread.currentThread().getName()+" remaining "+ --ticket);
            }
        }
    }
  • Runnable 接口避免了单继承的局限性。
  • 适合处理共享资源的情况。
4. 线程的生命周期

在jdk5 之前一个完整的线程的生命周期一般经历五种状态,从操作系统层面来描述:

新建、就绪、运行、阻塞、死亡。

  • 线程生命周期流程图

线程生命周期流程图

4. 1 新建

当一个子类或其子类对象被创建时,新生的线程对象就处于新建状态,此时没有任何动态特征,和一般对象相同。

4. 2 就绪

当线程对象调用了start()方法,线程由创建转化为就绪状态,已经具备了运行的条件,随时可以调度,调度时间由JVM操控。

4. 3 运行

处于就绪状态的线程对象获取cpu,开始执行run()方法。多核cpu的并行,单核cpu并发。

4. 4 阻塞

进入阻塞的原因:

  1. 线程调用sleep() 方法,主动放弃CPU资源。
  2. 线程调用阻塞式IO方法,在方法返回之前,该线程处于阻塞。
  3. 线程试图获取同步监视器,同步监视器被其他线程持有。
  4. 同步监视器调用wait()方法,让它等待某个通知。
  5. 运行阶段遇到某个线程的加塞。
  6. 线程被调用的supend方法挂起。

阻塞结束情况:

  1. 线程sleep时间结束。
  2. 线程调用阻塞式IO已返回。
  3. 线程获取同步监视器。
  4. 线程得到通知。
  5. 加塞的线程结束。
  6. 被挂起的线程被调用了resume方法。
4. 5 死亡
  1. run方法执行结束。
  2. 抛出一个未捕获异常或错误。
  3. 调用该线程的stop方法。
  • 使用isAlive()方法进行判断是否处于就绪、运行、阻塞状态。
5. Thread类
5. 1 构造和基本方法

构造器:

  • Thread() 默认构造,线程名默认。
  • Thread(String name) 指定线程名。
  • Thread(Runnable r,String name) 指定线程目标对象,执行线程名。

方法:

  • run() 线程执行的方法体。
  • start() 启动线程。

练习: 实现Runnable接口实现打印奇数,继承Thread类打印偶数。

package indi.jihad.a7;

public class TestA { 
    public static void main(String[] args) {
    new Thread(new MyRunnable(),"奇数线程:").start();
    new MyThread("偶数线程:").start();
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 1; i <=100; i++) {
                if (i%2!=0){
                    System.out.println(Thread.currentThread().getName()+">>>> "+i);
                }
            }
        }
    }
    static class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            for (int i = 1; i <=100; i++) {
                if (i%2==0){
                    System.out.println(Thread.currentThread().getName()+">>>> "+i);
                }
            }
        }
    }
}
5. 2 线程信息
  • static Thread Thread.currentThread() 静态方法,总是返回当前线程对象。
  • boolean isAlive() 线程是否存活。
  • final int getPriority() 返回线程优先级。
  • inal int setPriority(int priority) 设置线程优先级。

练习:设置卖票窗口的优先级:

 public static void main(String[] args) {
        SellTickets sellTickets = new SellTickets();
        Thread thread1 = new Thread(sellTickets);
        thread1.setPriority(Thread.MAX_PRIORITY);
        Thread thread2 = new Thread(sellTickets);
        thread2.setPriority(Thread.NORM_PRIORITY);
        Thread thread3 = new Thread(sellTickets);
        thread3.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
        thread3.start();
    }
    static class SellTickets implements Runnable{
    private int ticket=100;
        @Override
        public void run() {

            while (true){
                if (ticket<=0){
                    System.out.println("ticket is empty~~~");
                    return;
                }
                // 卖票操作
                System.out.println(Thread.currentThread().getName()+" remaining "+ --ticket);
            }
        }
    }
5. 3 线程的控制
  • static void sleep(long millis) 在指定毫秒数让当前线程休眠。
  • static void yield() 当前执行的线程暂停,不会阻塞,将该线程转入就绪。
  • final void join() 加塞,直至该线程结束,另一线程在执行。
  • final void stop() 强迫线程停止。
  • final void respend() 挂起当前线程。
  • final void resume() 重新开始执行挂起的线程。
  • void interrupt() 中断线程。
  • static boolean interrupted() 测试当前线程是否中断,中断状态将被清除,连续两次调用返回false。
  • void setDeamon(boolean on) 设置为守护线程,在线程启动前设置。

练习1: 倒计时—sleep

 public static void main(String[] args) throws InterruptedException {
        for (int i = 5; i >0; i--) {
            System.out.println("the "+i+" s losing");
            Thread.sleep(1000);
        }
        System.out.println("end.....");
    }

练习2:线程让步—yield

yield方法 暂停一下,cpu重新调度,每个线程争夺资源。

public static void main(String[] args) throws InterruptedException {
        Runnable runnable=()->{
            for (int i=0;i<=5;i++){
                System.out.println(Thread.currentThread().getName()+": >>>> " +i);
                Thread.yield();
            }
        };
        Thread thread1 = new Thread(runnable,"hight");
        thread1.setPriority(Thread.MAX_PRIORITY);
        Thread thread2 = new Thread(runnable,"low");
        thread2.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
    }

练习3:龟兔赛跑— join

  • 当某个线程的线程体调用另一个线程的join方法,该线程将被阻塞,直到join进来的线程执行完(join不限时加塞),或阻塞一段时间(join(millis)限时加塞)后,它才能继续执行。

案例需求:

  1. 假设跑道长度为30m。
  2. 兔子的速度为10m/s,兔子每次跑完10m,休息10s。
  3. 乌龟的速度为1m/s, 乌龟每次跑完10m,休息1s。
  4. 两个线程结束,主线程公布结果。
package indi.jihad.a8;

import lombok.Data;

public class TestRace {
    public static void main(String[] args) {
        Racer racer1 = new Racer("兔子", 100, 10000, 30);
        Racer racer2 = new Racer("乌龟", 1000, 1000, 30);
        racer1.start();
        racer2.start();
        // 两线程在主线程之前执行完
        try{
            racer1.join();// 只有线程1 线程2 执行
            racer2.join();// 只有 线程2  执行
        }catch (Exception e){

        }
        // 只有主线程执行
    String winner=racer1.totalTime<racer2.getTotalTime()?racer1.getRaceName(): racer2.getRaceName();
        System.out.println("winner is "+winner);

    }
    @Data
    static class Racer extends Thread{
        private String raceName;
        private long runTime;
        private long restTime;
        private long distance;
        private long totalTime;
        private boolean finished;

        public Racer(String raceName, long runTime, long restTime, long distance) {
            this.raceName = raceName;
            this.runTime = runTime;
            this.restTime = restTime;
            this.distance = distance;
        }

        @Override
        public void run() {
            long sum=0;
            long startTime=System.currentTimeMillis();
            while (sum<distance){
                System.out.println(raceName+" is running......");
                try{
                    Thread.sleep(runTime);// 每秒跑步时间
                }catch (Exception e){
                    System.out.println(raceName+" is hurted");
                    return;
                }
                sum++;//距离+1 跑了1m
              if(sum%10==0&&sum<distance){
                  try{
                      Thread.sleep(restTime);// 休息时间
                  }catch (Exception e){
                      System.out.println(raceName+" is hurted");
                      return;
                  }
              }

            }
            long endTime=System.currentTimeMillis();
             totalTime=(endTime-startTime);
            System.out.println(raceName+" >>>>"+totalTime+" ms");
            finished=true;
        }
    }
}

练习4 :守护线程 —daemon

  • 守护线程:在后台运行,为其他线程提供服务.比如JVM的垃圾回收线程。
  • 特点:非守护线程全部死亡,守护线程跟随死亡。
  • 主线程 打印1-100 守护线程打印 i am daemon
package indi.jihad.a8;

public class TestDaemon {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
                while (true){
                    System.out.println("i am daemon");
                }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();
        for (int i = 1; i <=100 ; i++) {
            System.out.println("主线程>>>>>>>>"+ i);
        }
    }
}
  • 输出片段

输出片段

练习5: 中断线程—interrupt

interrupt调用遇上以下方法抛出异常 InterruptedExpection:

  1. Object:wait、wait(long)、wait(long,int)
  2. Thread:join、sleep。
  • 可以实现线程停止。完成龟兔赛跑:有一方到达终点即比赛就结束。
6. 线程同步
6. 1 线程安全问题

线程安全问题都是由共享变量引起的,共享变量一般都是某个类的静态变量,或者因为多个线程使用同一个对象的实例变量。方法的局部变量是不可能成为共享变量。

  • 只有读操作,没有修改操作,一般来说,该共享变量是线程安全的。
  • 如果多个线程同时执行写操作,就要考虑线程安全问题。

线程安全示例:窗口卖票:

package indi.jihad.a8;

public class TestSellTicketWithThreeWindows {
    public static void main(String[] args) {
      new SellTickets().start();
      new SellTickets().start();
      new SellTickets().start();
    }
    static class SellTickets extends Thread{
    private static int ticket=100;
        @Override
        public void run() {

            while (ticket>0){
                --ticket;//卖票
                // 通知
                System.out.println(Thread.currentThread().getName()+" remaining "+ ticket);
            }
        }
    }
}
  • 通知和卖票会错乱,出现错票、多票等线程安全问题。
    输出片段
6. 2 同步代码块
  • 6.1 解决方案

卖票线程安全问题:

  1. 通知可能会是另一个线程的结果。(同步代码块绑定)
  2. 当ticket<=0 时其他线程已进入while判断。(同步代码块加入if判断)
package indi.jihad.a8;

public class TestSellTicketWithThreeWindows {
    public static void main(String[] args) {
      new SellTickets().start();
      new SellTickets().start();
      new SellTickets().start();
    }
    static class SellTickets extends Thread{
    private static int ticket=100;
        @Override
        public void run() {
            while (ticket>0){
                // 多个线程进入
               synchronized (this.getClass()){
                 if (ticket>0){// 单个进程进入判断是否有票
                     --ticket;//卖票
                     // 通知
                     System.out.println(Thread.currentThread().getName()+" remaining "+ ticket);
                 }
               }
            }
        }
    }
}
  • 使用synchronized(锁) 让获取锁的单个线程独立执行,其余线程等待获取锁。
  • 锁要唯一,一般选取当前对象的Class对象。
6. 3 同步方法

静态方法使用的锁为:当前类的Class对象。

非静态方法使用的锁为:当前this对象。

  • 修改6. 2 代码
package indi.jihad.a8;

public class TestSellTicketWithThreeWindows {
    public static void main(String[] args) {
      new SellTickets().start();
      new SellTickets().start();
      new SellTickets().start();
    }
    static class SellTickets extends Thread{
    private static int ticket=100;
        @Override
        public void run() {
            while (ticket>0){
             sellAndAdviceTicket();
            }
        }
        public static  synchronized void sellAndAdviceTicket(){
            if (ticket>0){
                --ticket;//卖票
                // 通知
                System.out.println(Thread.currentThread().getName()+" remaining "+ ticket);
            }
        }
    }
}
6. 4 释放锁操作

释放锁的操作:

  • 当前同步代码块执行结束。
  • 遇到break (外循环) 和return。
  • 出现异常未捕获。
  • 执行锁对象的wait方法,相乘被挂起。

不会释放锁的操作:

  • 程序调用Thread.sleep、Thread.yield方法。
  • 其他线程调用了该线程的suspend将该线程挂起。
6. 5 死锁

当不同的线程分别锁住对方需要的同步监视器对象不释放时,就会形成死锁。

一旦产生死锁,程序不会报任何异常,只是所有程序处于阻塞状态。

死锁示例

package indi.jihad.a8;

public class TestDeadLock {
    public static void main(String[] args) {
        String goods="商品";
        Double money=10.0;
      new Thread(new Customer(goods,money)).start();
      new Thread(new Owner(goods,money)).start();
    }
    static class Customer implements Runnable{
        private String goods;
        private Double money;

        public Customer(String goods, Double money) {
            this.goods = goods;
            this.money = money;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (money){

                System.out.println("等待卖家发货.....");
                synchronized (goods){
                    System.out.println("顾客已付钱....");
                }
            }
        }
    }
    static class Owner implements Runnable{
        private String goods;
        private Double money;

        public Owner(String goods, Double money) {
            this.goods = goods;
            this.money = money;
        }

        @Override
        public void run() {
            synchronized (goods){

                System.out.println("等待顾客付钱.....");
                try {
                    Thread. sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (money){
                    System.out.println("商家发货....");
                }
            }
        }
    }
}
7. 等待唤醒机制
7. 1 基本方法

Object提供了三个方法(使用在同步代码块中,由锁调用):

  1. wait 释放锁 等待被唤醒
  2. notify随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
  3. notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
  • 线程:交替打印1-100
public class Test01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread,"1>>>>").start();
        new Thread(myThread,"2>>>>").start();
    }
    static class MyThread implements Runnable{
        private int num=1;
        @Override
        public void run() {
            while (num<=100){
              synchronized (this){
                  this.notify();
                if (num<=100){
                    System.out.println(Thread.currentThread().getName()+" >>>> "+num);
                    num++;
                  }
                  try {
                      this.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
            }
        }
    }
}
7.2 生产者消费者问题

生产者与消费者问题也称有限缓冲问题。是多线程同步的问题的经典案例。

生产者在缓冲区生成一定量的数据,于此同时消费者在缓冲区消耗数据。

生产者和消费者隐含两个线程问题:

  1. 线程安全问题,消费者和生产者存在共享的数据,需要进行同步机制解决。
  2. 线程协调工作问题:这个问题需要等待唤醒机制解决。
  • 关键代码
        public synchronized void produceProduct() {
            if (productionNum < 20) {
                productionNum++;
                notify();// 唤醒消费线程
                System.out.println(Thread.currentThread().getName()
                        + "is producing the " +
                        productionNum + " production");
            } else {
                try {
                    wait();// 等于20 个等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public synchronized void consumerProduct(){
            if (productionNum > 0) {
                System.out.println(Thread.currentThread().getName()
                        + "is consuming the " +
                        productionNum + " production");
                productionNum--;
                notify();

            } else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
8. JDK5 线程特性
8. 1 Callable接口

相比Runnable ,Callable优势

  1. call方法可以有返回值。
  2. 方法可以抛出异常。
  3. 支持泛型返回值。
  4. 借助FutureTask类,获取返回结果。
public class TestCallable {
    public static void main(String[] args) {
        MyCallable callable = new MyCallable();// 创建实现类
        FutureTask<Integer> task = new FutureTask<Integer>(callable);// 创建代理FutureTask
        Thread thread = new Thread(task);// 创建线程
        thread.start();// 启动线程
        try {
            Integer sum = task.get();// 获取返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    static class MyCallable implements Callable<Integer> {
     private Integer sum=0;
        @Override
        public Integer call() throws Exception {
            for (int i = 1; i <= 100; i++) {
                if (i%2==0){
                    System.out.println(Thread.currentThread().getName()+">>>> "+i);
                    sum+=i;
                }
            }
            return sum;
        }
    }
}
8. 2 线程池

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

思路:提前创建好 多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁的创建和销毁,实现重复利用。

好处:

  • 提高响应速度。
  • 降低资源消耗。
  • 便于线程管理。
public class TestThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor
            service = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);// 设置确定线程数的线程池
        service.setCorePoolSize(15);// 设置池中核心连接数
        service.execute(()->{// 执行Runnable接口函数
            for (int i=1;i<=100;i++){
                if(i%2==0) System.out.println(Thread.currentThread().getName()+">>> "+i);
            }
        });
        service.shutdown();// 关闭线程池
    }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值