多线程与高并发学习笔记2

二、 线程的使用

创建线程的三种方式:

  • 方式1: 继承 Thread

    import java.util.concurrent.TimeUnit;
    
    /**
     * 通过继承方式创建线程
     *
     * @author 赵帅
     * @date 2021/1/1
     */
    public class CreateMyThreadByExtendThread extends Thread {
    
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("通过继承方式实现自定义线程,当前线程为:" + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            // 当前线程为主线程 main
            System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
    
            // 创建一个新的线程并开启线程,打印新的线程名
            CreateMyThreadByExtendThread thread = new CreateMyThreadByExtendThread();
            thread.start();
        }
    }
    
    
  • 方式2: 实现Runnable接口

    import java.util.concurrent.TimeUnit;
    
    /**
     * 通过实现Runnable接口方式
     *
     * @author 赵帅
     * @date 2021/1/1
     */
    public class CreateMyThreadByImplRunnable implements Runnable {
    
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("通过实现Runnable接口方式实现自定义线程,当前线程为:" + Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            // 当前线程为主线程 main
            System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
    
            // 创建一个新的线程并开启线程,打印新的线程名
            Thread thread = new Thread(new CreateMyThreadByImplRunnable());
            thread.start();
        }
    }
    
  • 方式3: Callable+Feature

    import java.util.concurrent.*;
    
    /**
     * 通过实现Callable接口方式
     *
     * @author 赵帅
     * @date 2021/1/1
     */
    public class CreateMyThreadByImplCallable implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("通过实现Callable接口方式实现自定义线程,当前线程为:" + Thread.currentThread().getName());
            return "hello";
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 当前线程为主线程 main
            System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
    
            // callable接口实现的任务和Future接口或线程池一起使用
            CreateMyThreadByImplCallable callable = new CreateMyThreadByImplCallable();
    
            // 和Feature一起使用,Future表示未来,也就是说我执行这个任务,未来我去取任务执行的结果
            FutureTask<String> task = new FutureTask<>(callable);
            new Thread(task).start();
            System.out.println("main线程继续运行");
    
            // 等其他线程执行完了,再来拿task的结果,
            System.out.println("task.get() = " + task.get());
    
    
            // 使用线程池方式调用callable
            Future<String> submit = Executors.newSingleThreadExecutor().submit(callable);
            System.out.println("submit.get() = " + submit.get());
        }
    }
    

三种方式的区别

通过上面三种创建线程的方式,我们对线程的使用有了基本的了解 ,下面我们来分析这三种方式有什么区别:

  1. 继承Thread: 通过继承方式创建线程,因为java单继承的特性,使用的限制就非常多了,使用不方便。
  2. 实现Runnable: 因为单继承的限制,所以出现了Runnable,接口可以多实现,因此大大提高了程序的灵活性。但是无论是继承Thread还是实现Runnable接口,线程执行的方法都是 void 返回值。
  3. 实现Callable: Callable接口就是为了解决线程没有返回值的问题,Callable接口有一个泛型类型,这个泛型就代表返回值的类型,使用Callable接口就可以开启一个线程取执行, Callable一般和Future接口同时使用,返回值为Future类型,可以通过Future接口的get方法拿执行结果。get()方法是一个阻塞的方法。

相同点:都是通过Thread类的start()方法来开启线程。

线程的方法

  • start(): 开启线程,使线程从新建进入就绪状态

  • sleep(): 睡眠,使当前线程休息, 需要指定睡眠时间,当执行sleep方法后进入阻塞状态,

  • Join():加入线程,会将调用的线程加入当前线程。等待加入的线程执行完成后才会继续执行当前线程。

    /**
     * 线程方法示例
     * @author 赵帅
     * @date 2021/1/1
     */
    public class ThreadMethodDemo {
        public static void main(String[] args) throws InterruptedException {
            // 打印当前线程 main线程的线程名 main
            System.out.println("当前主线程线程名 = " + Thread.currentThread().getName());
    
            // 创建一个新的线程
            Thread thread = new Thread(() -> {
    
                // 线程进入睡眠状态
                try {
                    Thread.sleep(1000L);
                    System.out.println("当前线程名:" + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            // 开启线程 thread
            thread.start();
    
    //        thread.join();
            System.out.println("主线程执行完毕");
        }
    }
    

    打开和关闭注释thread.join()可以发现输出结果是不一样的,使用join后会等待thread执行结束后再继续执行main方法。

  • wait(): 当前线程进入等待状态,让出CPU给其他线程,自己进入等待队列,等待被唤醒。

  • notify(): 唤醒等待队列中的一个线程,唤醒后会重新进入就绪状态,准备抢夺CPU。

  • notifyAll(): 唤醒等待队列中的所有线程,抢夺CPU。

  • yield(): 让出CPU。当前线程让出CPU给其他的线程执行,但是自己也会进入就绪状态参与CPU的抢夺,因此调用yield方法后,仍然可能继续获得CPU。

    import java.util.concurrent.TimeUnit;
    
    /**
     * 线程方法示例
     * @author 赵帅
     * @date 2021/1/1
     */
    public class ThreadMethodDemo {
        public static void main(String[] args) throws InterruptedException {
            // 打印当前线程 main线程的线程名 main
            System.out.println("当前主线程线程名 = " + Thread.currentThread().getName());
    
            Object obj = new Object();
    
            // 创建一个新的线程
            Thread thread = new Thread(() -> {
    
                // 线程进入睡眠状态
                try {
                    synchronized (obj) {
                        obj.wait();
                        System.out.println("当前线程名:" + Thread.currentThread().getName());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            // 开启线程 thread
            thread.start();
    
            System.out.println("主线程执行完毕");
            // 等待thread进入等待状态释放锁,否则会产生死锁
            TimeUnit.SECONDS.sleep(6);
            synchronized (obj) {
                obj.notify();
            }
        }
    }
    

注意:wait和notify只能在synchronized同步代码块中调用,否则会抛异常。

  • Interrupt(): 中断线程,调用此方法后,会将线程的中断标志设置为true。

线程的状态

一个完整的线程的生命周期应该包含以下几个方面:

  1. 新建(New):创建了一个线程,但是还没有调用start方法
  2. 就绪(Runnable):调用了start()方法,线程准备获取CPU运行
  3. 运行(Running):线程获取CPU成功,正在运行
  4. 等待(Waiting):等待状态,和TimeWaiting一样都是等待状态, 不同的是Waiting是没有时间限制的等,而TimeWaiting会进入一个有时间限制的等。例如调用wait()方法后就会进入一个无限制的等,等待调用notify唤醒,而调用sleep( time)就会进入一个有时间限制的等。等待结束后(被唤醒或sleep时间到期)后就会重新进入就绪队列,等待获取CPU继续向下执行。
  5. 阻塞(Blocked):多个线程再等待临界区资源时,进入阻塞状态。
  6. 销毁(Teminated): 线程执行完毕,进入销毁状态,这个状态是不可逆的,是最终状态,当进入这个状态时,就代表线程执行结束了。

以一张图来理解这几个状态:

简单介绍一个线程的生命周期:

当我们使用 new Thread()创建一个线程时,那么这个线程就处于创建状态;当我们调用start()方法后,此时线程就处于就绪状态(进入就绪状态后就不可能再进入创建状态了),但是调用start()方法后并不是说立马就会被CPU执行,而是会参与CPU的抢夺,当这个线程拿到CPU后,就会被执行。那么拿到CPU后就进入了运行状态。当调用了sleep或wait方法后,线程就进入了等待状态, 当等待状态被唤醒后,就会重新进入就绪队列等待获取CPU,当访问同步资源时或其他阻塞式操作时就会进入阻塞状态,阻塞状态结束重新进入就绪状态获取CPU。当线程运行完成后进入Teminate状态后,就代表线程执行结束了。

sleep操作不释放锁,wait操作释放锁。

原文地址:https://www.cnblogs.com/Zs-book1/p/14232806.html