【Java基础】一篇文章读懂多线程

1.多线程

1.1并发和并行

  • 并发:两个或多个事件在同一时间段发生
  • 并行:两个或多个事件在同一时刻发生

1.2线程和进程

  • 进程是程序的一次执行过程,进程是系统运行应用程序的基本单位,一个应用程序可以同时运行多个进程
  • 线程是进程中的一个执行单元,一个进程至少由一个线程,负责程序的执行

1.3线程调度的两种方式

  • 分时调度:所有线程轮流使用CPU,平均分配每个线程使用CPU的时间
  • 抢占式调度:Java让优先级高的线程执行,优先级相同则随机选择一个线程执行

1.4创建多线程的两种方式

  • 多个线程在不同的栈空间里执行,互不影响

  • 第一种实现方式:创建Thread的子类,并且重写run方法,一般不用

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行:" + i);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("自定义线程执行:" + i);
        }
    }
}
  • 第二种实现方式:实现Runnable接口,重写run方法,使用Thread(Runnable target)分配Thread对象
  • 实现Runnable接口创建多线程的好处
    • 避免单继承的局限性
    • 将设置线程任务和开启线程两个过程解耦
public class Main {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        Thread thread = new Thread(run);
        thread.start();
        new Thread(new RunnableImpl()).start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

class RunnableImpl implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

1.5获取线程名称的两种方式

  • String getName() 返回线程名称
  • static Thread currentThread() 返回当前正在执行线程的引用
public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行:" + i);
        }
        //获取线程名称方法二
        String name = Thread.currentThread().getName();
        System.out.println(name);//main
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        //获取线程名称方法一
        String name = getName();
        System.out.println(name);//Thread-0

        for (int i = 0; i < 10; i++) {
            System.out.println("自定义线程执行:" + i);
        }
    }
}

2.线程安全

2.1线程同步

  • 当使用多线程访问同一资源时,且多个线程对资源有写操作,容易出现线程安全问题,Java中使用同步机制解决

    public class Main {
        public static void main(String[] args) {
            RunnableImpl runnable = new RunnableImpl();
            //三个线程共享一个实现类对象,保证共享资源为100张票
            new Thread(runnable).start();
            new Thread(runnable).start();
            new Thread(runnable).start();
            //出现重复的票和不存在的票
        }
    }
    
    class RunnableImpl implements Runnable {
        //定义一个多线程共享的资源
        private int ticket = 100;
        
        //设置线程任务买票
        @Override
        public void run() {
            //让买票操作重复执行
            while (true) {
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //执行卖票操作
                    System.out.println(Thread.currentThread().getName() + "-->" + ticket);
                    ticket--;
                }
            }
        }
    }
    
  • 同步代码块解决线程安全问题:把可能出现安全问题的代码块用synchronized修饰

    public class Main {
        public static void main(String[] args) {
            RunnableImpl runnable = new RunnableImpl();
            //三个线程共享一个实现类对象,保证共享资源为100张票
            new Thread(runnable).start();
            new Thread(runnable).start();
            new Thread(runnable).start();
            //出现重复的票和不存在的票
        }
    }
    
    class RunnableImpl implements Runnable {
        //定义一个多线程共享的资源
        private int ticket = 100;
        //创建一个锁对象
        Object object = new Object();
    
        //设置线程任务买票
        @Override
        public void run() {
            //让买票操作重复执行
            while (true) {
                synchronized (object) {
                    //先判断票是否存在
                    if (ticket > 0) {
                        //提高安全问题出现的概率
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //执行卖票操作
                        System.out.println(Thread.currentThread().getName() + "-->" + ticket);
                        ticket--;
                    }
                }
            }
        }
    }
    
  • 同步方法解决线程安全问题:把可能出现安全问题的代码块形成一个方法并用synchronized修饰该方法

    public class Main {
        public static void main(String[] args) {
            RunnableImpl runnable = new RunnableImpl();
            //三个线程共享一个实现类对象,保证共享资源为100张票
            new Thread(runnable).start();
            new Thread(runnable).start();
            new Thread(runnable).start();
            //出现重复的票和不存在的票
        }
    }
    
    class RunnableImpl implements Runnable {
        //定义一个多线程共享的资源
        private int ticket = 100;
    
        //创建一个同步方法
        public synchronized void sellTicket() {
            //先判断票是否存在
            if (ticket > 0) {
                //提高安全问题出现的概率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //执行卖票操作
                System.out.println(Thread.currentThread().getName() + "-->" + ticket);
                ticket--;
            }
        }
    
        //设置线程任务买票
        @Override
        public void run() {
            //让买票操作重复执行
            while (true) {
                sellTicket();
            }
        }
    }
    
  • 锁机制解决线程安全问题:Lock接口实现

    public class Main {
        public static void main(String[] args) {
            RunnableImpl runnable = new RunnableImpl();
            //三个线程共享一个实现类对象,保证共享资源为100张票
            new Thread(runnable).start();
            new Thread(runnable).start();
            new Thread(runnable).start();
            //出现重复的票和不存在的票
        }
    }
    
    class RunnableImpl implements Runnable {
        //定义一个多线程共享的资源
        private int ticket = 100;
        //创建一个锁对象
        Lock lock = new ReentrantLock();
    
        //设置线程任务买票
        @Override
        public void run() {
            //让买票操作重复执行
            while (true) {
                lock.lock();//加锁
                //先判断票是否存在
                if (ticket > 0) {
                    //提高安全问题出现的概率
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //执行卖票操作
                    System.out.println(Thread.currentThread().getName() + "-->" + ticket);
                    ticket--;
                }
                lock.unlock();//解锁
            }
        }
    }
    

3.线程状态

在这里插入图片描述

3.1等待唤醒案例:线程之间的通信

  • 创建一个消费者线程,调用wait方法之后,放弃CPU进入WAITING状态(无限等待)

  • 创建一个生产者线程,调用Notify方法唤醒消费者进程

    /**
     * @ClassName WaitAndNotify
     * @Description TODO
     * @Author hulin
     * @Date 2020/1/23 21:11
     * @Version 1.0.0
     */
    /*
        进入到TimeWaiting(计时等待)有两种方式
        1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
        2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
    
        唤醒的方法:
             void notify() 唤醒在此对象监视器上等待的单个线程。
             void notifyAll() 唤醒在此对象监视器上等待的所有线程。
     */
    public class WaitAndNotify {
        public static void main(String[] args) {
            //创建锁对象,保证唯一
            Object obj = new Object();
            // 创建一个顾客线程(消费者)
            new Thread(){
                @Override
                public void run() {
                    //一直等着买包子
                    while(true){
                        //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                        synchronized (obj){
                            System.out.println("顾客1告知老板要的包子的种类和数量");
                            //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //唤醒之后执行的代码
                            System.out.println("包子已经做好了,顾客1开吃!");
                            System.out.println("---------------------------------------");
                        }
                    }
                }
            }.start();
    
            // 创建一个顾客线程(消费者)
            new Thread(){
                @Override
                public void run() {
                    //一直等着买包子
                    while(true){
                        //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                        synchronized (obj){
                            System.out.println("顾客2告知老板要的包子的种类和数量");
                            //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //唤醒之后执行的代码
                            System.out.println("包子已经做好了,顾客2开吃!");
                            System.out.println("---------------------------------------");
                        }
                    }
                }
            }.start();
    
            //创建一个老板线程(生产者)
            new Thread(){
                @Override
                public void run() {
                    //一直做包子
                    while (true){
                        //花了5秒做包子
                        try {
                            Thread.sleep(5000);//花5秒钟做包子
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                        synchronized (obj){
                            System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                            //做好包子之后,调用notify方法,唤醒顾客吃包子
                            //obj.notify();//如果有多个等待线程,随机唤醒一个
                            obj.notifyAll();//唤醒所有等待的线程
                        }
                    }
                }
            }.start();
        }
    }
    

3.2等待唤醒机制

  • 多个线程在处理同一个资源且任务不同时,需要线程通信来帮助解决,需要线程通信来帮助解决线程之间对同一个资源的使用和操作。通过等待唤醒机制使各个线程有效的利用资源。
  • wait方法和notify方法都属于Object类的方法,必须在同步代码块或者同步函数中使用,必须由同一个锁对象调用

4.线程池

4.1概念

  • 容纳多个线程的容器(ArrayList,LinkedList,HashSet),无需反复创建线程
  • 好处:降低资源消耗,提高相应速度,提高线程的可管理性

4.2线程池的实现

  1. 使用线程池的工厂类Executors的静态方法生产一个指定数量的线程池

  2. 创建一个类实现Runnable接口,重写run方法,设置线程任务

  3. 调用ExecutorService的方法submit传递线程任务(实现类),开启线程执行run方法

  4. 使用ExecutorService的方法shutdown销毁线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
    public static void main(String[] args) {
        //1. 使用线程池的工厂类Executors的静态方法生产一个指定数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //3. 调用ExecutorService的方法submit传递线程任务(实现类),开启线程执行run方法
        es.submit(new RunnableImpl());//pool-1-thread-2
        //线程池会一直开启,使用完了会把线程自动归还给线程池
        es.submit(new RunnableImpl());//pool-1-thread-2
        es.submit(new RunnableImpl());//pool-1-thread-1
        //4. 使用ExecutorService的方法shutdown销毁线程池
        es.shutdown();
        es.submit(new RunnableImpl());//出现异常
    }
}
public class RunnableImpl implements Runnable {
    //2. 创建一个类实现Runnable接口,重写run方法,设置线程任务
    @Override
    public void run() {
        //获取线程名称
        System.out.println(Thread.currentThread().getName());
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40507857/article/details/106966916