多线程创建,线程安全,线程同步,线程池

目录

线程

1.多线程

创建方式一,继承Thread类

创建方式二,实现Runnable接口

创建二的匿名内部类写法:

创建方式三,实现Callable接口  ,FutureTask类实现

2.线程Thread的常用方法

3.线程安全

4.线程同步

方式一.同步代码块

方式二:同步方法

方式三:Lock锁

线程通信

案例:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上          2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。

线程池:

创建线程池

线程池处理Runnable任务 

线程池处理Callable任务

Executors工具类实现线程池

Executors 使用可能存在的陷阱

并发,并行

线程的生命周期(各种状态)


线程

Thread是一个程序内部的一条执行流程。

1.多线程

多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。

Java是通过java.lang.Thread 类的对象来代表线程的。

多线程是怎么执行的?
并发: CPU 分时轮询的执行线程。
并行:同一个时刻同时在执行

创建方式一,继承Thread类

/**
 * 1、让子类继承Thread线程类。
 */
public class MyThread extends Thread{
    // 2、必须重写Thread类的run方法
    @Override
    public void run() {
        // 描述线程的执行任务。
        for (int i = 1; i <= 5; i++) {
            System.out.println("子线程MyThread输出:" + i);
        }
    }
}


public class ThreadTest1 {
    // main方法是由一条默认的主线程负责执行。
    public static void main(String[] args) {

        // 3、创建MyThread线程类的对象代表一个线程
        Thread t = new MyThread();
        // 4、启动线程(自动执行run方法的)
        t.start();  // main线程 t线程

        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出:" + i);
        }
    }
}

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展

注意: 1 、启动线程必须是调用 start 方法, 不是调用 run 方法。 

                 直接调用 run 方法会当成普通方法执行, 此时相当于还是单线程执行。 

                 只有调用 start 方法才是启动一个新的线程执行。

             2 、 不要把主线程任务放在启动子线程之前。

                       这样主线程一直是先跑完的, 相当于是一个单线程的效果了。

创建方式二,实现Runnable接口

优点:任务类只是实现接口,可以继续继承其他类,实现其他接口,扩展性强,

缺点:需要多一个 Runnable 对象。

/**
 * 1、定义一个任务类,实现Runnable接口
 */
public class   MyRunnable implements Runnable{   //alt+回车
    // 2、重写runnable的run方法
    @Override
    public void run() {
        // 线程要执行的任务。
        for (int i = 1; i <= 5; i++) {
            System.out.println("子线程输出 ===》" + i);
        }
    }
}

/**
 * 目标:掌握多线程的创建方式二:实现Runnable接口。
 */
public class ThreadTest2 {
    public static void main(String[] args) {
        // 3、创建任务对象。
        Runnable target = new MyRunnable();

        // 4、把任务对象交给一个线程对象处理。
        //  public Thread(Runnable target)
        new Thread(target).start();

        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出 ===》" + i);
        }
    }
}

创建二的匿名内部类写法:

  1. 可以创建Runnable的匿名内部类对象。
  2. 再交给Thread线程对象。
  3. 再调用线程对象的start()启动线程。

/**
 * 目标:掌握多线程创建方式二的匿名内部类写法。
 */
public class ThreadTest2_2 {
    public static void main(String[] args) {
        // 1、直接创建Runnable接口的匿名内部类形式(任务对象)
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("子线程1输出:" + i);
                }
            }
        };
        new Thread(target).start();

        // 简化形式1:
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("子线程2输出:" + i);
                }
            }
        }).start();

        // 简化形式2:
        new Thread(() -> {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("子线程3输出:" + i);
                }
        }).start();

        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出:" + i);
        }
    }
}

创建方式三,实现Callable接口  ,FutureTask类实现

优点:返回线程执行完毕后的结果

// 未来任务对象的作用?
        // 1、是一个任务对象,实现了Runnable对象.
        // 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。

缺点:编码复杂

/**
 * 1、让这个类实现Callable接口
 */
public class MyCallable implements Callable<String> {
    private int n;  //接n的值
    public MyCallable(int n) {   //有参构造器,选第一个  把n的值接进来
        this.n = n;
    }

    // 2、重写call方法
    @Override
    public String call() throws Exception {
        // 描述线程的任务,返回线程执行返回后的结果。
        // 需求:求1-n的和返回。
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "线程求出了1-" + n + "的和是:" + sum;
    }
}

/**
 * 目标:掌握线程的创建方式三:实现Callable接口。
 */
public class ThreadTest3 {
    public static void main(String[] args) throws Exception {
        // 3、创建一个Callable的对象
        Callable<String> call = new MyCallable(100);
        // 4、把Callable的对象封装成一个FutureTask对象(任务对象)
        FutureTask<String> f1  = new FutureTask<>(call);
        // 5、把任务对象交给一个Thread对象
        new Thread(f1).start();

//又一个线程
        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2  = new FutureTask<>(call2);
        new Thread(f2).start();


        // 6、获取线程执行完毕后返回的结果。
        // 注意:如果执行到这儿,假如上面的线程还没有执行完毕
        // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。

        String rs = f1.get();
        System.out.println(rs);

        String rs2 = f2.get();
        System.out.println(rs2);
    }
}

2.线程Thread的常用方法

public class MyThread extends Thread{
    public MyThread(String name){                  //有参构造器
        super(name); // 为当前线程设置名字了
    }

    @Override
    public void run() {
        // 哪个线程执行它,它就会得到哪个线程对象。
        Thread t = Thread.currentThread();

        for (int i = 1; i <= 3; i++) {
            System.out.println(t.getName() + "输出:" + i);
        }
    }
}

/**
 * 目标:掌握Thread的常用方法。
 */
public class ThreadTest1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread("1号线程");
        // t1.setName("1号线程");
        t1.start();
        System.out.println(t1.getName()); // Thread-0

        Thread t2 = new MyThread("2号线程");
        // t2.setName("2号线程");
        t2.start();
        System.out.println(t2.getName()); // Thread-1

        // 主线程对象的名字
        // 哪个线程执行它,它就会得到哪个线程对象。
        Thread m = Thread.currentThread();

        m.setName("最牛的线程");
        System.out.println(m.getName()); // main

        for (int i = 1; i <= 5; i++) {
            System.out.println(m.getName() + "线程输出:" + i);
        }
    }
}

/**
 * 目标:掌握sleep方法,join方法的作用。
 */
public class ThreadTest2 {
    public static void main(String[] args) throws Exception {
        System.out.println(Runtime.getRuntime().availableProcessors());
        for (int i = 1; i <= 5; i++) {
            System.out.println(i);
            // 休眠5s
            if(i == 3){
                // 会让当前执行的线程暂停5秒,再继续执行
                // 项目经理让我加上这行代码,如果用户交钱了,我就注释掉!
                 Thread.sleep(5000);

            }
        }

        // join方法作用:让当前调用这个方法的线程先执行完。
        Thread t1 = new MyThread("1号线程");
        t1.start();
        t1.join();

        Thread t2 = new MyThread("2号线程");
        t2.start();
        t2.join();

        Thread t3 = new MyThread("3号线程");
        t3.start();
        t3.join();
    }
}

3.线程安全

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

案例:取钱

public class Account {
    private String cardId; // 卡号
    private double money; // 余额。

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    // 小明 小红同时过来的
    public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        // 1、判断余额是否足够
        if(this.money >= money){
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name + "来取钱:余额不足~");
        }
    }


/**
 * 目标:模拟线程安全问题。
 */
public class ThreadTest {
    public static void main(String[] args) {
         // 1、创建一个账户对象,代表两个人的共享账户。
        Account acc = new Account("ICBC-110", 100000);
        // 2、创建两个线程,分别代表小明 小红,再去同一个账户对象中取钱10万。
        new DrawThread(acc, "小明").start(); // 小明
        new DrawThread(acc, "小红").start(); // 小红
    }
}

public class DrawThread extends Thread{
    private Account acc;
    public DrawThread(Account acc, String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        // 取钱(小明,小红)
        acc.drawMoney(100000);
    }
}


4.线程同步

解决线程安全问题的方案

思想:让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

方式一.同步代码块

作用:把访问共享资源的核心代码给上锁,以此保证线程安全

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执
行。 

注意:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug

// this正好代表共享资源!      ctrl+alt+t--第9个
        synchronized (this) {
            if(this.money >= money){
                System.out.println(name + "来取钱" + money + "成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            }else {
                System.out.println(name + "来取钱:余额不足~");
            }
        }
    }

i:建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象

i:对于静态方法建议使用字节码(类名.class)对象作为锁对象。

public static void test(){
        synchronized (Account.class){

        }
    }

方式二:同步方法

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

synchronized

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执
行。

同步方法底层原理
 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
 如果方法是实例方法:同步方法默认用 this 作为的锁对象。
 如果方法是静态方法:同步方法默认用类名 .class 作为的锁对象。

是同步代码块好还是同步方法好一点?
1.范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
2.可读性:同步方法更好。 

方式三:Lock锁

1.Lock 锁是 JDK5 开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
2.Lock 是接口,不能直接实例化,可以采用它的实现类 ReentrantLock 来构建 Lock 锁

public class Account {
    private String cardId; // 卡号
    private double money; // 余额。
    // 创建了一个锁对象 ,多态写法
    private final Lock lk = new ReentrantLock();

   

public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    // 小明 小红线程同时过来的
    public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        try {
            lk.lock(); // 加锁
            // 1、判断余额是否足够
            if(this.money >= money){
                System.out.println(name + "来取钱" + money + "成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            }else {
                System.out.println(name + "来取钱:余额不足~");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            lk.unlock(); // 解锁
        }
    }

//set方法
}
 

线程通信

 当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据。
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

案例:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
          2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。

public class ThreadTest {
    public static void main(String[] args) {
        //   需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子上
        //      2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。
        Desk desk  = new Desk();

        // 创建3个生产者线程(3个厨师)
        new Thread(() -> {
            while (true) {
                desk.put();
            }
        }, "厨师1").start();

        new Thread(() -> {
            while (true) {
                desk.put();
            }
        }, "厨师2").start();

        new Thread(() -> {
            while (true) {
                desk.put();
            }
        }, "厨师3").start();

        // 创建2个消费者线程(2个吃货)
        new Thread(() -> {
            while (true) {
                desk.get();
            }
        }, "吃货1").start();

        new Thread(() -> {
            while (true) {
                desk.get();
            }
        }, "吃货2").start();
    }
}
 

public class Desk {
    private List<String> list = new ArrayList<>();

    // 放1个包子的方法
    // 厨师1 厨师2 厨师3
    public synchronized void put() {
        try {
            String name = Thread.currentThread().getName();
            // 判断是否有包子。
            if(list.size() == 0){
                list.add(name + "做的肉包子");
                System.out.println(name + "做了一个肉包子~~");
                Thread.sleep(2000);

                // 唤醒别人, 等待自己
                this.notifyAll(); 
                this.wait();

            }else {
                // 有包子了,不做了。
                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();

     //上述方法应该使用当前同步锁对象进行调用。
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 吃货1 吃货2
    public synchronized void get() {
        try {
            String name = Thread.currentThread().getName();
            if(list.size() == 1){
                // 有包子,吃了
                System.out.println(name  + "吃了:" + list.get(0));
                list.clear();
                Thread.sleep(1000);
                this.notifyAll();
                this.wait();
            }else {
                // 没有包子
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

线程池:

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

创建线程池


JDK 5.0 起提供了代表线程池的接口: ExecutorService 。
方式一:使用 ExecutorService 的实现类 ThreadPoolExecutor 自创建一个线程池对象。

方式二:使用 Executors (线程池的工具类)调用方法返回不同特点的线程池对象。 

 线程池的注意事项
1 、临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2 、什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

  // 1、通过ThreadPoolExecutor创建一个线程池对象。
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
); 

线程池处理Runnable任务 

使用 ExecutorService 的方法:
 void execute(Runnable target)

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        // 任务是干啥的?
        System.out.println(Thread.currentThread().getName() + " ==> 输出666~~");
        try {
            Thread.sleep( Integer.MAX_VALUE);//最大的整型值
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadPoolTest1 {
    public static void main(String[] args) {
        // 1、通过ThreadPoolExecutor创建一个线程池对象。
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
);

        Runnable target = new MyRunnable();  //任务对象
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        // 到了临时线程的创建时机了
        pool.execute(target);
        pool.execute(target);
        // 到了新任务的拒绝时机了!
        pool.execute(target);

        // pool.shutdown(); // 等着线程池的任务全部执行完毕后,再关闭线程池
        // pool.shutdownNow(); // 立即关闭线程池!不管任务是否执行完毕!
    }
}
 

线程池处理Callable任务

使用 ExecutorService 的方法:
 Future<T> submit(Callable<T> command)

/**
 * 1、让这个类实现Callable接口
 */
public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    // 2、重写call方法
    @Override
    public String call() throws Exception {
        // 描述线程的任务,返回线程执行返回后的结果。
        // 需求:求1-n的和返回。
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;
    }  //获取当前线程对象
}
 

public class ThreadPoolTest2 {
    public static void main(String[] args) throws Exception {
        // 1、通过ThreadPoolExecutor创建一个线程池对象。
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());

        // 2、使用线程处理Callable任务。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));   //复用前面线程

        System.out.println(f1.get());//获取线程执行的结果
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

Executors工具类实现线程池

Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

注意 :这些方法的底层,都是通过线程池的实现类 ThreadPoolExecutor 创建的线程池对象。

public class ThreadPoolTest3 {
    public static void main(String[] args) throws Exception {
        // 1、通过ThreadPoolExecutor创建一个线程池对象。
//        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
//                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
//                new ThreadPoolExecutor.CallerRunsPolicy());

        // 1-2 通过Executors创建一个线程池对象。
        ExecutorService pool = Executors.newFixedThreadPool(17);

        // 老师:核心线程数量到底配置多少呢???
        // 计算密集型的任务:核心线程数量 = CPU的核数 + 1
        // IO密集型的任务:核心线程数量 = CPU核数 * 2

        // 2、使用线程处理Callable任务。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

Executors 使用可能存在的陷阱


大型并发系统环境中使用 Executors 如果不注意可能会出现系统风险 

并发,并行

进程:

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

并发的含义
进程中的线程是由 CPU 负责调度执行的,但 CPU 能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU 会轮询为系统的每个线程服务,由于 CPU 切换的速度很快,给我们的感觉这些线程在同时执行,这就是

并行:在同一个时刻上,同时有多个线程在被 CPU 调度执行

线程的生命周期(各种状态)

也就是线程从生到死的过程中,经历的各种状态及状态转换。
理解线程这些状态有利于提升并发编程的理解能力。
Java 线程的状态
Java 总共定义了 6 种状态
6 种状态都定义在 Thread 类的内部枚举类中。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值