Bootstrap

Java多线程基础,实现多线程并发买票demo

程序 , 进程 , 线程

程序(programm):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的 代码。

进程(process)

概念:程序的一次执行过程,或是正在运行的一个程序。

说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(thread) 概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。

说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的 开销小。

每个线程,拥有自己独立的:栈、程序计数器

多个线程,共享同一个进程中的结构:方法区、堆。

线程的创建和使用

方式一: 继承 Thread类

  • 创建一个继承于 Thread 类的子类
  • 重写 Thread 类的 run() --> 将此线程执行的操作声明在 run()
  • 创建 Thread 类的子类的对象
  • 通过此对象调用 start() :①启动当前线程 ② 调用当前线程的 run()
class MyThread extends Thread { //创建一个线程,完成0-100内的偶数的输出 
    @Override //重写run()方法 
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i + "---mythread---" + Thread.currentThread().getName());
            }
        }
    }

    public class ThreadTest1 {
        public static void main(String[] args) { //通过子类对象调用start()方法开启线程 Thread             
            thread = new MyThread();
            thread.start(); //此时有两个线程 
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) {
                    System.out.println(i + "----main()----" + Thread.currentThread().getName());
                }
            }
        }
        /* 存在交互 0---mythread 0----main()---- 2----main()---- 4----main()---- 6----main()---- 2---mythread 8----main()---- 4---mythread*/
    }
}

说明两个问题:

问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。

问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象 的start().

方式二: 实现 Runnable接口

1. 创建一个实现了Runnable接口的类

2. 实现类去实现Runnable中的抽象方法:run()

3. 创建实现类的对象

4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

5. 通过Thread类的对象调用start()

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

public class ThreadTest2 {
    public static void main(String[] args) {
        MThread mThread = new MThread();
        new Thread(mThread).start();//将实现类的对象作为真实参数传到Thread类的构造器中
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

方式三 : 实现Callable 接口

类似于实现Runnable接口, 只是 Callable接口 实现类 是作为 FutureTask类 构造器 中的参数 , 然后再将 FutureTask 对象 作为 Thread 类构造器中的参数 创建Thread对象,并调用start().

1.创建一个实现Callable的实现类

2.实现call方法,将此线程需要执行的操作声明在call()中

3.创建Callable接口实现类的对象

4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建 FutureTask的对象

5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并 调用start()

6.通过 futureTask.get() 获取Callable中call方法的返回值

//1.创建一个实现Callable的实现类
class NumThread implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
//3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
说明:
        *如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
        *1.call()可以返回值的。
        *2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
        *3.Callable是支持泛型的

方式四: 使用线程池

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);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
//1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
        service.shutdown();
    }
}
说明:
        *好处:
        *1.提高响应速度(减少了创建新线程的时间)
        *2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
        *3.便于线程管理
        *corePoolSize:核心池的大小
        *maximumPoolSize:最大线程数
        *keepAliveTime:线程没任务时最多保持多长时间后会终止

Thread类中常用的方法

* 1. start():启动当前线程;调用当前线程的run()
* 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. currentThread():静态方法,返回执行当前代码的线程
* 4. getName():获取当前线程的名字
* 5. setName():设置当前线程的名字
* 6. yield():释放当前cpu的执行权 //不释放锁
* 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线
程a才结束阻塞状态。
* 8. stop():已过时。当执行此方法时,强制结束当前线程。
* 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时
间内,当前线程是阻塞状态。
* 10. isAlive():判断当前线程是否存活

线程的生命周期

当线程对象被创建时 , 它处于新建状态
当使用线程对象调用 start() , 线程进入就绪状态 .
当线程获取到 CPU 执行权后开始运行 , 在运行过程中 , 遇到 sleep(),wait(), 等待同步锁等操作时会进入阻塞状态. sleep() 结束或者获取到同步锁时 , 线程由阻塞状态进入就绪状态.
run() 执行结束或者出现异常且未处理时 , 线程进入死亡状态 .

线程的同步机制

方式一: 同步代码块

synchronized(同步监视器){
//操作共享数据的代码
        }
//说明
        共享数据:多个线程共同操作的变量.如售票系统中的ticket
        同步监视器:锁;可以是任意一个对象;但要求必须是:多个线程共享同一个锁
//补充
        在实现Runnable接口创建多线程的方式中,可以用 this 来 充当锁
        在继承Thread类创建多线程的方式中,常用 当前类(如 window.class)来充当锁
class window1 implements Runnable {//创建三个窗口买票, 总票数为100张 (线程不安全的),使用实现Runnable接口的方式
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {//锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁。this:表示 w 这个对象
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() +
                            ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

方式二: 同步方法

相当于将操作共享数据的代码封装到一个方法当中 , 然后这个方法声明为synchronized 同步 方法

同步方法依然涉及到同步监视器 : 只是不需要我们显式的声明。

// 非静态同步方法中 (实现Runnable接口创建多线程方式中常用) : 锁 :this

// 静态方法中(继承Thread类创建多线程方式中使用) : 锁 : 当前类

class window2 implements Runnable {//创建三个窗口买票, 总票数为100张 ,使用实现Runnable接口的方式
    private int ticket = 100;
    @Override
    public void run() {
        while(true) {
            show();
            if (ticket ==0) {
                return;
            }
        }
    }
    public synchronized void show() { // 同步监视器 :this
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":买票, 票号为:" + ticket);
            ticket--;
        }
    }
}

方式三: Lock锁 ( jdk5.0 新增 )

//实例化ReentrantLock类,得到lock对象
ReentrantLock lock = new ReentrantLock();
//手动加锁,手动解锁
        lock.lock();
        操作共享数据的代码
        lock.unlock();
        类似于synchronized同步代码块

1. 面试题:synchronized 与 Lock的异同?
        * 相同:二者都可以解决线程安全问题
        * 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
        * Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现
        (unlock())
class window2 implements Runnable {
    ReentrantLock lock = new ReentrantLock();

    // ReentrantLock lock = new ReentrantLock(true); // 公平的,先进先出
    @Override
    public void run() {
        while (true) {
            try {
                //手动加锁
                lock.lock();
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() +
                            ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                lock.unlock();//手动解锁
            }
        }

    }
}

线程通信

        线程通信涉及到的三个方法:

* wait():当前线程进入阻塞状态,并释放同步监视器。

* notify():唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

* notifyAll():唤醒所有被wait的线程。

1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视 器。否则,会出现IllegalMonitorStateException异常

* 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

多线程卖票demo

Window

public class Window implements Runnable {
    //    创建10个窗口卖票,总票数为100张
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) { //锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁
                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName()+ "在卖票,票号为: " + ticket);
                        ticket--;
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        break;
                    }
            }

        }

}

SaleTicket

public class SaleTicker {

    public static void main(String[] args) {
        Window w = new Window();
        new Thread(w,"窗口1").start();
        new Thread(w,"窗口2").start();
        new Thread(w,"窗口3").start();
        new Thread(w,"窗口4").start();
        new Thread(w,"窗口5").start();
        new Thread(w,"窗口6").start();
        new Thread(w,"窗口7").start();
        new Thread(w,"窗口8").start();
        new Thread(w,"窗口9").start();
        new Thread(w,"窗口10").start();
    }
}

窗口10在卖票,票号为: 16
窗口10在卖票,票号为: 15
窗口9在卖票,票号为: 14
窗口9在卖票,票号为: 13
窗口9在卖票,票号为: 12
窗口9在卖票,票号为: 11
窗口9在卖票,票号为: 10
窗口9在卖票,票号为: 9
窗口9在卖票,票号为: 8
窗口9在卖票,票号为: 7
窗口9在卖票,票号为: 6
窗口9在卖票,票号为: 5
窗口8在卖票,票号为: 4
窗口8在卖票,票号为: 3
窗口8在卖票,票号为: 2
窗口8在卖票,票号为: 1

;