Thread基本认识

一、直接调用run() 方法与直接调用start()方法的区别

import java.util.concurrent.TimeUnit;

public class WhatIsThread {
    private static class T1 extends Thread{
        @Override
        public void run(){
            for (int i = 0; i < 5; i++) {
                try {
                    TimeUnit.MICROSECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T1");
            }
        }
    }

    public static void main(String[] args){
        // new T1().run(); //方法调用(动态)
        new T1().start(); //启动线程(另启一个线程运行你在run()方法里写的内容)
        for (int i = 0; i < 5; i++) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Main");
        }
    }
}

	执行结果:
	1. 直接调用new T1().run()
	T1
	T1
	T1
	T1
	T1
	Main
	Main
	Main
	Main
	Main
	2. 直接调用new T1().start()
	Main
	T1
	Main
	T1
	T1
	Main
	T1
	Main
	T1
	Main

二、线程的创建


import java.util.concurrent.*;

public class WhatIsThread {
    // 1. Thread 也是继承Runnable 接口的(见上↑↑↑)
    
    // 2. Runnable 接口只有run 方法(见上↑↑↑)

    //3. 通过继承Callable 重写call() 方法,将其封装到FutureTask中并中Thread 来启动一个线程
    public static class MyTask implements Callable{
        private String task1;
        private String task2;
        public MyTask(String task01, String task02){
            this.task1 = task01;
            this.task2 = task02;
        }
        //执行任务动作
        @Override
        public Object call() throws Exception {
            for (int i = 0; i < 5; i++) {
                TimeUnit.MICROSECONDS.sleep(1);
                System.out.println(task1 + ":" + task2);
            }
            return true;
        }
    }

    public static void main(String[] args) {
        /** 方式一  继承Thread 类*/
        new MyThread().start();

        /** 方式二  实现Runnable 接口*/
        new Thread(new MyRunnable()).start();
        new Thread(()->{
            System.out.println("hello lambda");
        }).start();

        /** 方式三  Callable配合Future Task*/
        MyTask myTask = new MyTask("name1", "name2");
        FutureTask futureTask = new FutureTask(myTask);
        //FutureTask 最终继承了Runnable 接口,采用Thread 来开启多线程
        new Thread(futureTask).start();
        try {
            boolean result = (boolean) futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            System.out.println("守护线程阻塞被打断...");
            e.printStackTrace();
        } catch (ExecutionException e) {
            System.out.println("执行任务时出错...");
            e.printStackTrace();
        } catch (CancellationException e) {
            //如果线程已经cancel了,再执行get操作会抛出这个异常
            System.out.println("future已经cancel了...");
            e.printStackTrace();
        }
        
	 	/** 方式四   线程池*/
        //自定义线程池!!!
        ExecutorService executorService = new ThreadPoolExecutor(
                5,5,10,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
        for (int i = 0; i < 11; i++) {
            executorService.execute(()-> System.out.println("开始执行线程池中的任务"));
        }

        //创建单线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            singleThreadExecutor.execute(new RunnableTest());
        }
        //创建固定数量的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            fixedThreadPool.execute(new RunnableTest());
        }
        //还有其它线程池就不写了
		..................
    }

    public static class RunnableTest implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

1.ThreadPoolExecutor 参数介绍

	public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
        this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
        this.mainLock = new ReentrantLock();
        this.workers = new HashSet();
        this.termination = this.mainLock.newCondition();
        if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
            if (var6 != null && var7 != null && var8 != null) {
                this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
                this.corePoolSize = var1;
                this.maximumPoolSize = var2;
                this.workQueue = var6;
                this.keepAliveTime = var5.toNanos(var3);
                this.threadFactory = var7;
                this.handler = var8;
            } else {
                throw new NullPointerException();
            }
        } else {
            throw new IllegalArgumentException();
        }
    }
    
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit var5, 
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);

corePoolSize :核心线程数
maximumPoolSize :最大线程数
keepAliveTime :线程存活时间
TimeUnit :时间类型
workQueue :任务队列
threadFactory :创建新的线程Factory
handler :拒绝策略

当有新任务添加时:

  1. 核心线程数 和 线程池当前线程数比较
    任务线程数 < 核心线程数 通过threadFactory 创建新的线程来执行任务;
    任务线程数 >= 核心线程数 且 workQueue 未满,任务进入workQueue;
    任务线程数 >= 核心线程数 且 workQueue 已满,任务线程数 < 最大线程数 ,创建新的线程执行任务;
    任务线程数 >= 核心线程数 且 workQueue 已满,任务线程数 >= 最大线程数,调用拒接策略handler 来处理任务。

2. 线程池常用队列介绍:

  1. ArrayBlockQueue:
    有界缓冲队列,可以指定缓存队列的大小。
    当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,
    当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。
    推荐博文:https://blog.csdn.net/zhangyong01245/article/details/102390093

  2. LinkedBlockQueue:
    无界缓存等待队列。
    当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。
    生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
    推荐博文:https://blog.csdn.net/zhangyong01245/article/details/102396551

  3. SynchronousQueue:
    没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素
    推荐博文:https://blog.csdn.net/zhangyong01245/article/details/102483284

3. 线程方法介绍

  1. join
	static void testJoin(){
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("test t1's join method");
                try {
                    TimeUnit.MICROSECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(()->{
            try {
                System.out.println("t2 is started");
                t1.join();
                System.out.println("t2 is end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();
    }

    public static void main(String[] args){
        testJoin();
    }

输出结果:
t2 is started
test t1's join method
test t1's join method
test t1's join method
test t1's join method
test t1's join method
test t1's join method
test t1's join method
test t1's join method
test t1's join method
t2 is end

如何保证三个线程按照顺序执行:
1.1 在main 方法中分别调用thread1、thread2、thread3
1.2 在thread1 中调用thread2 的join 方法,在thread2 中调用thead3 的join 方法


三、为什么说只有一种实现线程的方法

  1. 线程池、定时器、Callable 还是 FutureTask,它们都仅仅是在new Thread() 外层做了一层封装,打开封装后它们最终都是基于Runnable 接口或者继承Thread 类实现的。
  2. 基于Runnable 和继承Thread 的本质是一样的?启动线程需要调用start()方法,start()方法最后还是会调用run() 方法。它们的不同点仅仅在于**实现线程运行内容的不同**。
  3. 运行的内容要么来自target(target 实际上就是一个 Runnable),要么来自于重写的 run() 方法。
    即:本质上实现线程只有一种方式,而实现线程执行内容却有两种方式(1. 实现Runnable 接口;2. 继承Thread类重写run() 方法实现)。
public class Thread implements Runnable {
	private Runnable target;
	
	public void run() {
	   if (this.target != null) {
	         this.target.run();
	     }
	 }
}

四、为什么说实现Runnable 接口比继承Thread 线程好

1. 从代码的架构考虑

  1. Runnable() 里面只有一个run() 方法,它定义了需要执行的内容,这种情况下实现了Runnable 和Thread 类的解耦,Thread 类负责线程启动和属性设置等,权责分明。

2. 某些情况下可以提高性能

  1. 使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁(比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多)。
  2. 使用实现 Runnable 接口的方式,可以把任务直接传入线程池。

3. Java 语言不支持双继承

  1. 限制了代码未来的可拓展性。

五、线程状态之间的转换

1. 线程的六种状态

New(新建)、Runnable(可运行)、Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待)、Terminated(被终止)。

2. 状态之间的转换

在这里插入图片描述

  • 线程没有调用start() 方法也就没有开始执行run() 方法。线程一旦调用了start() 方法就会由New 状态转换成Runnable 状态。
  • Runnable 状态可能是正在执行也可能是没有正在执行,它分别对应操作系统的两种状态Running 和Ready。当任务运行到一半儿时CPU 被调度去做其他事儿,状态依然是Runnable。
  • 三个状态(Blocked、Waiting、Timed Waiting)
    • Blocked:Runnable进入到Blocked 状态只有一种可能,即Synchronized 保护的代码块没有抢到monitor 锁,无论是Synchronized 保护的代码块还是方法。Blocked 仅仅针对 synchronized monitor 锁。
    • Waiting:不会超时(即不会自动恢复),Runnable 进入到Waiting 状态有这三种可能,即Object.wait()和 Thrread.join()没有设置时间参数,LockSupport.park()方法

      Blocked 和Waiting 的区别在于Blocked 是等待其他线程释放锁,Waiting 则是等待某个
      条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。
    

    • Timed Waiting:会等待超时,由系统自动唤醒,或者超时前被唤醒信号唤醒。
      Object.wait(long timeout);
      Thread.join(long millis) ;
      Thread.sleep(long millis) ;
      LockSupport.parkNanos(long nanos);
      LockSupport.parkUntil(long deadline) ;

      唤醒Waiting 线程的线程如果调用notify() 或notifyAll() 方法,得必须先获得monitor 锁,
      这使得处于Waiting 状态的线程被唤醒时拿不到该锁,就会进入Block 状态,直到唤醒
      它的线程执行完notify()/notifyAll() 并释放monitor 锁,才可以轮到它去抢这把锁。
    

  • Terminated:1. run() 执行完毕,线程正常退出;2. 出现了一个没有捕获的异常,终止了run() 方法,最终导致意外终止。

3. wait/notify/notifyAll 方法的使用

  1. 为什么wait 必须在synchronized 保护的同步代码中使用?
public class Producer implements Runnable {
    /**
     * 产品容器
     */
    private final List<Integer> container;

    public Producer(List<Integer> container) {
        this.container = container;
    }

    /**
     * 生产者生产方法
     *
     * @throws InterruptedException
     */
    private void produce() throws InterruptedException {
        //产品容器容量
        int capacity = 5;
        synchronized (container) {
            //当容器已满,暂停生产
            while (container.size() == capacity) {
                System.out.println("...容器已经满了,暂停生产...");
                container.wait();
            }
            Random random = new Random();
            int p = random.nextInt(5);
            //模拟1秒生产一个产品
            TimeUnit.MILLISECONDS.sleep(1000);
            System.out.println("生产产品:" + p);
            container.add(p);
            container.notifyAll();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("produce error");
            }
        }
    }
}
public class Consumer implements Runnable{

    /**
     * 产品容器
     */
    private final List<Integer> container;

    public Consumer(List<Integer> container) {
        this.container = container;
    }

    /**
     * 消费者消费产品
     */
    private void consume() throws InterruptedException {
        synchronized (container){
            while (container.isEmpty()){
                System.out.println("...容器是空的,暂停消费...");
                container.wait();
            }
            Integer p = container.remove(0);
            //模拟1秒消费一个产品
            TimeUnit.MILLISECONDS.sleep(1000);
            System.out.println("消费产品:" + p);
            container.notifyAll();
        }
    }
    @Override
    public void run() {
        while (true){
            try {
                consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("consume error");
            }
        }
    }
}
 public static void main(String[] args) {
   	 List<Integer> container = new ArrayList<>();
     Thread producer = new Thread(new Producer(container));
     Thread consumer = new Thread(new Consumer(container));
     producer.start();
     consumer.start();
 }
  • 使用Synchronized 的原因: 如果生产者和消费者中没有Synchronized ,在消费者执行while 循环且还未执行wait 方法时,线程被调度器暂停了,此时生产者开始执行并往list 中添加数据并执行了notifyAll 方法,但此时的notifyAll 方法并没有任何效果,因为消费者并没有执行wait 方法,所以没有线程被等待唤醒。此时刚才被调度器暂停的消费者线程回来继续执行wait 方法并进入等待。
    假设这时没有更多的生产者进行生产,消费者便可能陷入无穷无尽的等待中,因为错过了生产者内部的notifyAll 的唤醒。
  • 使用while 的原因: 多线程情况下,线程可能在即没有被notify/notifyAll,也没有被中断或者超时的情况下被唤醒。因为if只会执行一次,执行完会接着向下执行if()外边的
    而while不会,直到条件满足才会向下执行while()外边的。
    可参考文章了解1.
    可参考文章了解2.
  1. wait/notify/notifyAll 被定义在 Object 类中, sleep 定义在 Thread 类中的原因?
  • Java 中每个对象都有一把称之为monitor 的监视器锁,这个锁是对象级别的而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在Object 中最合适,因为Object 是所有类的父类。
  • 局限性比较大,比如一个线程可能持有多把锁,以便实现相互配合的复杂,如果wait 定义在Thread 类中,如何实现一个线程持有多把锁?又如何明确线程等待的是哪把锁?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。
  1. wait/notify 和 sleep 方法的异同?
    相同点:1. 都可以让线程阻塞;2. 都可以响应中断。
    不同点:
    • wait 方法必须放在synchronized 块中,sleep 方法没有这个要求;
    • 同步代码块中wait 方法会主动释放monitor 锁,sleep 方法不会主动释放monitor 锁;
    • wait 方法会永久等待,直到被中断或被唤醒才能恢复,并不会主动恢复,而sleep 必须定义一个时间,时间到期后会主动恢复;
    • wait 是Object 的方法,sleep 是Thread 的方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值