Java 多线程

多线程

多线程

一个程序可以分为多个线程、线程是程序内部的一条执行路径;

所在包名:java.lang.Thread;

基本特点

  • 线程作为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
  • 多个线程会共享进程的相同的内存单元和堆(方法区和堆);

线程的使用

线程的创建

方式一

  1. 创建一个类,继承于 Thread 类,并重写里面的 run( ) 方法,将该线程的业务代码写在 run( ) 方法里;
  2. 创建该子类的对象,调用 start( ) 方法,启动该线程;
public class kindOfThread extends Thread {
    // 重写run
    @Override
    public void run() {
        super.run();
        // 写该线程要做的事情
        // 寻找0-100里的偶数
        for (int i = 0; i <= 100; i++) {
            if ( i % 2 == 0){
                System.out.println(i + "\t" + Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        // 创建子类对象 并调用 start 方法
        kindOfThread thread = new kindOfThread(); // 主线程
        // start(); 启动当前线程,调用当前线程的run方法
        // 不能通过直接调用 run 方法来开辟多线程
        // 不可以让已经用过的 start 的线程再去执行
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("hello"+ "\t" + Thread.currentThread().getName());
        }

        kindOfThread thread1 = new kindOfThread();
        thread1.start();
        // 也可以应用匿名子类的方法来写多线程
    }
}

方式二

  1. 创建类并实现 Runable 接口,重写其抽象方法 run ( );
  2. 创建实现类的对象,并将该对象传递到 Thread 类的构造器当中,创建Thread类的对象;
  3. 通过 Thread 类的对象调用 start( ) 方法来启动线程;

实质剖析:

  • 通过 Thread 类的对象调用 start(),调用的是 Runnable 类型里的 target 的 run() 方法;
  • 然而我们在这里重写了 run ( ) 方法,最后将实现类对象直接当做 target 对象传入构造方法;
class Window implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票成功  票号为" + ticket);
                ticket--;
            }else{
                break;
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        // 创建实现类对象
        Window window = new Window();  //共享对象  共享变量ticket
        // 将创建好的实现类对象传入Thread类的构造函数创建对象
        Thread thread = new Thread(window);
        thread.setName("窗口1:");
        // 通过该对象调用start方法
        thread.start();

        Thread thread1 = new Thread(window);
        thread1.setName("窗口2:");
        thread1.start();

        Thread thread2 = new Thread(window);
        thread2.setName("窗口3:");
        thread2.start();
        //实现卖票 总票数为100
    }
}

方法一 VS 方法二

  1. 在实际应用中,优先选择实现接口的这种方法;
  2. 实现的方式没有类的单继承性的局限性;
  3. 实现的方式更适合来处理多个线程共享数据的情况;
  4. 共同点:都需要在 run( ) 方法里重写要执行的代码;

方法三

实现 Callable 接口

  1. 创建 Callable 的实现类;
  2. 实现 call( ) 方法,将线程需要的具体操作声明在 call( ) 中,同时 call( ) 方法可以有返回值;
  3. 创建 Callable 实现类的对象;
  4. 将 Callable 实现类的对象作为参数传递到 FutureTask 的构造器,并创建 FutureTask 的对象;
  5. 将 FutureTask 的对象作为参数传递到 Thread 类的构造器 创建 Thread 类的对象,并调用 start( ) 方法;
  6. FutureTask 类有一有一个 get( ) 方法可以用于获得上述第 2 步的返回值;
public class ThreadNew implements Callable {
    @Override
    public Object call() {
        int num = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                num += i;
            }
        }
        return num;
    }

    public static void main(String[] args) {
        // 创建 Callable 的实现类的对象
        ThreadNew threadNew = new ThreadNew();
        // 创建以 Callable 实现类对象为参数的 FutureTask 对象
        FutureTask futureTask = new FutureTask(threadNew);
        // 将 FutureTask 类的对象作为参数传入 Thread 类的对象,实现线程的创建
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

方法四

线程池

  1. 利用线程池来创建线程;
  2. 提供指定数量的线程池;
  3. 执行指定的线程操作,需要提供实现 Runnable 接口或者 Callable 接口实现类的对象;
  4. 关闭连接池;

优点:

  1. 提高响应速度(减少了创建新进程的时间);
  2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建);
  3. 便于线程管理;
public class ThreadPool implements Runnable{
    @Override
    public void run() {
        int num = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                num += i;
            }
        }
    }

    public static void main(String[] args) {
        // 1.提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10); //这里的 Executors 是一个工具类,但是Alibaba规范不推荐使用这个东西,因为可能会造成 OOM
		// 推荐使用 ThreadPoolExecutory,但是还没找到正确的食用方法这里先注释掉了
        // 通过ThreadPoolExecutor创建线程
        // ThreadFactory threadFactory = Executors.defaultThreadFactory();
        // ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,25,10, TimeUnit.MINUTES,new LinkedBlockingDeque<>(10),threadFactory);
        // 2.执行指定的线程操作,需要提供Runnable接口或者Callable接口实现类的对象
        service.execute(new ThreadPool());
        // 3.关闭连接池
        service.shutdown();
    }
}

线程中的常用方法

  1. start( ) 方法 :启动当前线程,调用线程中run方法;
  2. run( ) 方法 :通过重写Thread中的该方法,将创建的线程要执行的操作声明在此方法中;
  3. currentThread( ) 方法:静态方法,返回当前代码执行的线程;
  4. getName( ) :返回当前线程的名字;
  5. setName( ) :设置当前线程的名字 -->也可以通过构造器来对线程命名;
  6. yield( ): 释放当前线程CPU的执行权;
  7. join( ): 若在线程b中 调用线程a的join()方法,那么线程b会进入阻塞状态 直到线程a执行完毕,b才会恢复状态;
  8. stop( ):已过时 不推荐使用 强制结束当前线程;
  9. sleep(long millitime):静态 让指定线程睡眠 指定的毫秒值;
  10. isAlive( ):判断当前线程是否存活 返回true或false;

线程的优先级

MAX_PRIORITY = 10 //最大
MIN_PRIORITY = 1  //最小
NORM_PRIORITY = 5 //默认

设置当前线程的优先级

 getPriority()
 setPriority()

线程的生命周期

总的来说可以分为三个主要部分,两个首末部分

  • 按照次序来说为,新建就绪阻塞执行结束(死亡);
  • 新建调用 start 方法 线程进入就绪状态、当 CPU 执行权分配到该线程上时,该线程进入执行状态;
  • 因为 时间片到、sleep( )、等待同步锁、join( )、wait( ) 等方法 可以导致线程阻塞;
  • 当上述方法执行完毕 线程继续回到就绪状态等待被分配 CPU 执行权限;
  • 当线程执行完毕、调用 stop、出现 error 或者 Exception 且没处理 都会线程走向结束(死亡);

线程的安全问题

  • 多个线程执行的不确定性引起执行结果的不稳定;
  • 多个线程对数据的共享,会造成操作的不完整性,会破坏数据;

出现问题的原因

多个线程同时操作数据;

一般的解决措施:当线程在操作数据时,进行加锁 ,另其他线程无法操作;

在操作系统中我们常用的方式是加信号灯,通过操作信号灯 以及判断信号灯来实现线程、进程的控制;

解决

在 Java 中我们通常有两种方式来解决该问题

方式一

同步代码块

如果要执行的 同步代码快在一个方法里 那么可以直接将方法声明为同步方法

public synchronized faction(){
    // 方法体;
}

说明:

  1. 操作共享数据的代码,要被放入被 synchronized 方法包含的代码块中;
  2. 同步监视器:俗称锁,是锁的一种应用体现,任何一个类的对象都可以充当一个锁,但是 要保证,多个线程必须同用一把锁;

方式二

同步方法

如果要执行的 同步代码快在一个方法里 ,那么可以直接将方法声明为同步方法;

public synchronized faction(){
    // 方法体;
}

注意:

  1. 非静态同步方法体中的同步监视器为 this,静态的同步方法中,同步监视器为当前类本身( ***.class ),类只有一个 所以为安全的;
  2. 若创建线程的方法为继承 Thread 类的,那么就需要将该同步方法体 设置为 static 的方法体;

案例

此处用卖票系统来做实例

public class safeThread {
    public static void main(String[] args) {
        window w1 = new window();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);
        t1.setName("窗口1:");
        t2.setName("窗口2:");
        t3.setName("窗口3:");
        t1.start();
        t2.start();
        t3.start();
    }
}

class window implements Runnable {
    int ticket = 100;

    // Object o = new Object();
    @Override
    public void run() {
        while (true) {

            synchronized (this) {
                if (ticket > 0) {
                    /*try { // 在使用了synchronized(同步监视器) 同步代码块后,即使加上了阻塞,也不会有其他线程来占用CPU来执行他们的线程
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                    System.out.println(Thread.currentThread().getName() + "卖票成功  票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

线程通信

实现方式:通过共享数据;

  • wait( ) 方法执行后,当前线程进入阻塞状态 并释放同步监视器;
  • notify( ) 唤醒被被wait( ) 的一个方法;
  • notifyAll ( ) 唤醒全部;

注意:

  1. 上述三个方法只能被用在 synchronized 同步方法体内或者同步代码块中
  2. 上述三个方法的调用者必须是同步代码块或者同步方法中的同步监视器否则会出现异常
  3. 上述三个方法被定义在 java.Lang.Object 中

sleep() 和 wait()方法的异同

1.相同点

  • 一旦执行,便可使当前线程进入阻塞状态;

2.不同点

  • 两个方法声明的位置不同、Thread 中定义 sleep(),Object中定义 wait();
  • 对调用者的要求不同,sleep ( ) 可以在需要的任何场景通过 Thread 调用,而 wait 只能用在同步代码块或方法里;
  • 关于释放同步监视器的问题,wait( ) 会直接释放锁,再次获得需要被唤醒 notify()、notifyAll( ),然后才有资格再次获得监视器;
  • sleep( ) 不会释放锁,wait( ) 会直接释放锁;

sleep() 是线程类 Thread 的方法;作用是导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复;调用 sleep( )不会释放对象锁。

wait( ) 是 Object 类的方法,对此对象调用 wait() 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池;

只有针对此对象发出 notify() 方法或 notifyAll() 后本线程才进入对象锁定池,准备获得对象锁进行运行状态;

案例:

public class ThreadCommunication {
    public static void main(String[] args) {
        number number = new number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

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

class number implements Runnable {
    private int num = 0;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (num <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    num++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

生产者消费者案例

/** 经典例题:生产者消费者问题
 * 生产者producer 店员clerk 消费者customer
 * 店员只能持有20个产品 当持有大于等于20的时候 会命令生产者暂时停止生产
 * 等店里有了位置放产品,会再次通知生产者 继续开始生产
 * 如果店里没有产品,那么会通知消费者等一下,等店里有了产品再通知消费者来取走产品
 * <p>
 * 问题考虑:生产者比消费者快时,消费者会漏掉一些产品没有取到,消费者比生产者快的时候,消费者会取到相同的数据
 * <p>
 * 多线程:生产者线程、消费者线程
 * 存在共享数据、产品的数量、店员(或者产品)
 * 同步机制、线程通信
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Consumer Consumer = new Consumer(clerk);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(Consumer);

        t1.setName("生产者");
        t2.setName("消费者");

        t1.start();
        t2.start();
        System.out.println("hello");
    }
}

class Clerk {
    private int productCount = 0;

    //生产产品
    public synchronized void produceProduct() {
        if (productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产产品" + productCount);
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //消费产品
    public synchronized void consumerProduct() {
        if (productCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费产品" + productCount);
            productCount--;
            try {
                Thread.sleep(800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer implements Runnable { //生产者
    private Clerk clerk;

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

    @Override
    public void run() {
        System.out.println("生产者开始生产商品!");
        while (true) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

class Consumer implements Runnable {     //消费者
    private Clerk clerk;

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

    @Override
    public void run() {
        System.out.println("消费者开始消费商品!");
        while (true) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumerProduct();
        }
    }
}
posted @ 2022-01-09 22:06  atroot  阅读(56)  评论(0编辑  收藏  举报