Java基础编程-多线程

本文全文参考视频https://www.bilibili.com/video/av48144058内容编写

1、基本概念:程序、进程、线程

  • 程序(programm):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
  • 进程(process):程序的一次执行过程,或是正在运行的一个程序
    • 说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
  • 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
    • 说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。
      Java基础编程-多线程_第1张图片

一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

  • 内存结构
    Java基础编程-多线程_第2张图片
  • 并行与并发的理解
    • 并行多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    • 并发一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

2、线程的创建和使用

  • 有两种方法来创建一个新的执行线程。
2.1.新增线程方式一: 声明一个类是类Thread的子类。这类应重写类Thread的run方法。子类的一个实例可以被分配和启动。
  • 例如,一个线程计算素数大于规定值可以写成如下:
     class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
  • 下面的代码将创建一个线程并开始运行:
     PrimeThread p = new PrimeThread(143);
     p.start();

如果直接调用run()方法,则不会创建新的线程,比如 p.run();

  • 如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().
p.start();
p.start(); // 报错 IllegalThreadStateException
2.2.新增线程方式二:创建一个线程是声明一个类实现Runnable接口的其他方式。该类实现run方法。那类的一个实例可以分配,作为一个参数传递Thread时创建,并开始。
  • 同样的例子在这个其他风格看起来像以下:
     class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

  • 下面的代码将创建一个线程并开始运行:
     PrimeRun p = new PrimeRun(143);
     new Thread(p).start();
2.3.Thread类的常用方法
1.start():启动线程并执行相应的run()方法

2.run():子线程要执行的代码放入run()方法中

3.currentThread():获取当前的线程 //静态方法

4.getName():获取子线程的名称

5.setName():设置子线程的名称

6.yield():调用此方法的线程释放当前CPU的执行权

7.join():在子线程1中调用线程2join()方法,表示当执行到此方法时,线程1停止执行(阻塞)

8.sleep(long l):显示让当前线程睡眠(毫秒 1s = 1000ms)

9.isAlive():判断当前线程是否存活

10.getPriority():获取优先级,默认是5,最小是1,最大是10

11.setPriority():改变线程的优先级

12.线程通信的方法:
wait():等待
notify():唤醒等待的线程
notifyAll():唤醒所有等待的线程

2.4.线程的优先级

Java基础编程-多线程_第3张图片

优先级高,并不会一定先执行,只是执行的概率变高。
高优先级线程–抢占–低优先级线程

例子:创建三个窗口卖票,总票数为100张
/**
 * @Author Jerry
 * @Description //例子:创建三个窗口卖票,总票数为100张
 * @Date 19:52 2020/3/21
 * @Param
 * @return
 **/
public class ThreadTest01 {
    public static void main(String[] args) {
        Window w1 = new Window("窗口一");
        Window w2 = new Window("窗口二");
        Window w3 = new Window("窗口三");

        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread {
    private static int ticket = 100;

    public Window(String name) {
        super(name);
    }

    @Override
    public void run() {

        while (true) {
            if (ticket > 0) {
                System.out.println(getName() + ":卖票,票号为:" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

Java基础编程-多线程_第4张图片

  • 该例子目前的写法存在线程安全问题。在学习线程的同步后就能解决

3、线程的生命周期

Java基础编程-多线程_第5张图片

  • 生命周期关注两个概念:状态、相应的方法
  • 关注:状态a–>状态b:哪些方法执行了(回调方法)
    • 某个方法主动调用:状态a–>状态b
  • 阻塞:临时状态,不可以作为最终状态;死亡:最终状态。

线程的6种状态

  • NEW(初始):线程被创建后尚未启动。
  • RUNNABLE(运行):包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在运行,也可能正在等待系统资源,如等待CPU为它分配时间片。
  • BLOCKED(阻塞):线程阻塞于锁。
  • WAITING(等待):线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIME_WAITING(超时等待):该状态不同于WAITING,它可以在指定的时间内自行返回。
  • TERMINATED(终止):该线程已经执行完毕。
    Java基础编程-多线程_第6张图片

4、线程的同步

案例背景

例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

4.1.方式一:同步代码块

   synchronized(同步监视器){
      //需要被同步的代码
   }
说明
  • 1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
  • 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
  • 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求
  • 多个线程必须要共用同一把锁。
public class ThreadTest01 {
    public static void main(String[] args) {
        Window w1 = new Window("窗口一");
        Window w2 = new Window("窗口二");
        Window w3 = new Window("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }
}
class Window extends Thread {
    private static int ticket = 100;
    private static Object object = new Object();
    
    public Window(String name) {
        super(name);
    }
    @Override
    public void run() {
        while (true) {
            synchronized (object){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (ticket > 0) {
                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

4.2.方式二:同步方法

    @Override
    public void run() {
        show();
    }

    private static synchronized void show() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
  2. 非静态的同步方法,同步监视器是:this --》Runnable private synchronized void show()
    静态的同步方法,同步监视器是:当前类本身 --》Thread private static synchronized void show()
面试题:使用同步机制将单例模式中的懒汉式改写为线程安全的
public class BankTest {
}
class Bank {
    public Bank() {
    }
    
    private static Bank instance = null;

    // 方式一:同步方法
//    public static synchronized Bank getInstance() {
//        if (instance == null){
//            instance = new Bank();
//        }
//        return instance;
//    }
    // 方式二:同步代码块
//    public static Bank getInstance() {
//        synchronized (Bank.class) {
//            if (instance == null) {
//                instance = new Bank();
//            }
//            return instance;
//        }
//    }
    // 方式二性能优化:
    public static Bank getInstance() {
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

4.3.方式三:Lock(锁)— JDK5.0新增

class LockWindow implements Runnable{
    private int ticket = 100;

    // 1.实例化ReentrantLock对象
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            // 2.调用锁定方法
            lock.lock();
            try {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                // 3.调用解锁方法
                lock.unlock();
            }
        }
    }
}
面试题:Java是如何解决线程安全问题的,有几种方式?并对比几种方式的不同
  • 答:同步代码块(synchronized)、同步方法(synchronized)、Lock锁
面试题:synchronized和Lock方式解决线程安全问题的对比

Java基础编程-多线程_第7张图片

练习题

Java基础编程-多线程_第8张图片

package hjl.study.thread;

public class AccountTest {
    public static void main(String[] args) {
        Account acct = new Account(0);
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName("储户一");
        c2.setName("储户二");

        c1.start();
        c2.start();
    }
}

class Customer extends Thread {
    private Account acct;

    public Customer(Account acct) {
        this.acct = acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            acct.deposit(1000);
        }


    }
}

class Account {
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    // 存钱
    public synchronized void deposit(double amt){
        if (amt > 0) {
            balance += amt;

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+":存钱成功。余额为:"+balance);
        }
    }
}

5、线程的通信

5.1.线程通信涉及到的三个方法:
  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
5.2.说明:
  • 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
  • 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常
  • 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
面试题
  • sleep() 和 wait()的异同?
    • 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
    • 不同点:
      • 1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
      • 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
      • 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
案例一:使用两个线程打印1-100
/**
 * 线程通信例子:使用两个线程打印1-100
 * 线程一、线程二交替打印
 **/
class Number implements Runnable {

    private int number = 1;

    @lombok.SneakyThrows
    @Override
    public void run() {

        while (true) {
            synchronized (this) {

                // 唤醒一个线程(优先级高的先唤醒)
                notify();

                if (number <= 100) {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    // 使得调用如下wait()的线程进入阻塞状态
                    wait();

                } else {
                    break;
                }
            }
        }

    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程一");
        t2.setName("线程二");
        t1.start();
        t2.start();

    }
}

案例二:线程通信的应用:生产者/消费者问题

Java基础编程-多线程_第9张图片

import lombok.SneakyThrows;

/**
 * 线程通信的应用:生产者/消费者问题
 *
 * 分析:
 *      1.是否有多线程问题?是,生产者线程,消费者线程
 *      2.是否有共享数据?是,店员(或产品)
 *      3.如何解决线程的安全问题?同步机制,有三种方法
 *      4.是否涉及线程内通信?是
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producter p1 = new Producter(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        
        p1.start();
        c1.start();

    }
}
/**
 * 店员
 **/
class Clerk{
    private int productCount = 0;

    // 生产产品
    @SneakyThrows
    public synchronized void produceProduct() {
        if (productCount<20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            // 生产者生产产品后,就可以唤醒消费者消费产品
            notify();
        } else {
            wait();
        }
    }

    // 消费产品
    @SneakyThrows
    public synchronized void consumeProduct() {
        if (productCount>0){
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            // 消费者消费产品后,产品减少,就可以唤醒生产者生产
            notify();
        } else {
            wait();
        }
    }
}
/**
 * 生产者
 **/
class Producter extends Thread{

    private Clerk clerk;

    public Producter(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(this.getName()+":开始生产产品...");
        while (true){
            clerk.produceProduct();
        }

    }
}
/**
 * 消费者
 **/
class Consumer extends Thread{
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(this.getName()+":开始消费产品...");
        while (true){
            clerk.consumeProduct();
        }

    }
}

6、JDK5.0新增线程创建方式

6.1.新增线程方式三:实现Callable接口

Java基础编程-多线程_第10张图片
Java基础编程-多线程_第11张图片

理解案例一:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// 1.创建一个实现Callable的实现类
class NumThread implements Callable {

    // 2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3.创建Callable实现类对象
        NumThread numThread = new NumThread();
        // 4.将此Callable实现类对象传递到FutureTask构造器中,创建FutureTask对象
        FutureTask futureTask = new FutureTask<>(numThread);
        // 5.启动线程
        //public class FutureTask implements RunnableFuture
        //public interface RunnableFuture extends Runnable, Future
        //public class Thread implements Runnable {
        new Thread(futureTask).start();
        
        // 6.获取Callable中call()方法的返回值
        // get()返回值即为FutureTask构造器参数Callable实现类重新的call的返回值
        Object sum = futureTask.get();
        System.out.println("总和为:"+sum);
    }
}

6.2.新增线程方式四:使用线程池

Java基础编程-多线程_第12张图片
Java基础编程-多线程_第13张图片

理解案例一:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ExecutorsTest {
    public static void main(String[] args) {
        // 1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 设置线程池的属性
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        service1.setCorePoolSize(15); // 核心池的大小
        service1.setMaximumPoolSize(20); // 最大线程数

        // 2.执行指定线程的操作。
        service.execute(new NumberThread()); // 适合使用于Runnable
        service.execute(new NumberThread1()); // 适合使用于Runnable
//        service.submit(); // 适合使用于Callable

        // 3.关闭连接池
        service.shutdown();
    }
}
class NumberThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
class NumberThread1 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

你可能感兴趣的:(Java,#Java基础编程,java)