Java学习笔记07(多线程)

多线程

一个进程可以有多个线程,这些线程之间并发执行;

Java中有三种方法实现多线程:

  • 继承 Thread 类,重写 run() 方法;
  • 实现 Runnable 接口 , 重写 run() 方法;
  • 实现 Callable 接口 , 重写 call() 方法, 使用 Future 来 获取 call() 方法的返回结果;

Thread类

java.lang 包下的线程类, 实现多线程的方法:

  1. 创建一个多线程类 继承 Thread, 重写 run() 方法;
  2. 实例化子类,通过 start() 方法 启动线程;

代码示例:

// 创建一个类 继承 Thread
public class MyThread extends Thread{
    // 创建子类的 有参构造方法;
    public MyThread(String name){
        super(name);
    }
    //重写 run()方法
    public void run(){
        int i = 0;
        while(i++ < 5) {
            System.out.println(Thread.currentThread().getName() + "的 run()方法在运行");  //currentThread()获得当前线程,getName()获得当前																				   //线程名
        }
    }
}

public class Application {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t2");
        t1.start();						//启动线程 t1
        t2.start();						//启动线程 t2
    }
}

当启动 线程 t1 t2 时 ,他们会 随机的 调用 自己 run()方法 , 不会执行完t1 在 执行 t2 ;

Runnable接口

Runnable接口 实现多线程的方法:

  1. 创建一个 Runnable接口的实现类,重写 run()方法;
  2. 创建 Runnable接口的实现类对象;
  3. 用 Thread的有参构造方法创建线程实例, 并 将 Runnable的实现类的实例作为参数传入;
  4. 调用线程实例的start()方法;
// 创建 Runnable接口的实现类
public class MyRunnable implements Runnable {
    @Override
    //重写 run()方法
    public void run() {
        int i = 0;
        while (i++<10){
            System.out.println(Thread.currentThread().getName() +  "正在执行run()方法");
        }
    }
}

public class Application {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();  //创建实现类的对象
        Thread t1 = new Thread(myRunnable ,"Thread1");      // 将Runnable 接口的实现类对象作为参数 创建 线程实例;
        Thread t2 = new Thread(myRunnable,"Thread2");
        Thread t3 = new Thread(myRunnable,"Thread3");
        // Thread 的 有参构造方法  Thread(Runnable target , String name);  target Runnable接口实现类对象, name 线程名
        t1.start();
        t2.start();
        t3.start();
    }

Callable接口

Thread 和 Runnable 中重写的 run() 都没有返回值 ,无法从多个线程中获取结果。为了解决这个问题 JDK5 开始,提供一个 Callable接口,来满足 既能实现多线程,又可以获得返回结果;

它和 Runnable接口实现多线程的方法一样,都是通过 Thread 的有参构造方法,传入 Runnable 接口类型的参数 创建线程; 不同的是 Callable 传入参数的是Runnable接口的子类 FutureTask , FutureTask 对象中封装了带有返回值的 Callable 接口实现类;

实现方法如下:

  1. 创建一个Callable接口的实现类,重写 Callable 接口中的 call() 方法;
  2. 创建Callable接口实现类对象;
  3. 通过FutureTask 线程结果处理类的有参构造方法来封装Callable接口实现类对象;
  4. 使用参数为FutureTask类对象的Thread有参构造函数方法创建Thread线程实例;
  5. 调用线程实例的 start()方法;

代码实例如下:

// 创建 Callable 接口的实现类, 该接口需要指定 类型 ,是一个泛型接口;
public class MyCallable implements Callable<Object> {
    @Override
    //重写call()方法 必须 抛异常
    public Object call() throws Exception {
        int i = 0;
        while ( i++ < 5) {
            System.out.println(Thread.currentThread().getName() + "线程的call()方法正在运行");
        }
        return i;
    }
}

public class Application {
    public static void main(String[] args) throws InterruptedException, ExecutionException {  //抛异常
        MyCallable myCallable = new MyCallable();   // 创建接口实现类对象
        FutureTask<Object> objectFutureTask1 = new FutureTask<>(myCallable); //通过 FutureTask的有参构造 封装 Callable实现类对象
        Thread thread1 = new Thread(objectFutureTask1, "Thread1");     //创建线程,参数为 FutureTask 对象
        FutureTask<Object> objectFutureTask2 = new FutureTask<>(myCallable);
        Thread thread2 = new Thread(objectFutureTask2, "Thread2");
        thread1.start();
        thread2.start();
        System.out.println("thread1返回结果:" + objectFutureTask1.get());       //通过 FutureTask对象的到线程的返回结果;
        System.out.println("thread2返回结果:" + objectFutureTask2.get());

    }
}

Runnable 和 Callable 基本类似, 只是Callable 比 Runnable的功能更强大,可以处理异常 和 返回 线程结果;

Thread 类 和 Runnable ,Callable接口; 后两个接口适合去处理共享同一资源的情况,如卖票。

线程的调度

一个线程若想要执行,必须获得CPU的使用权,Java会按照某种机制给程序中的每个线程分配CPU的使用权,这种机制就是线程的调度;

线程的优先级

优先级越高的线程,其获得CPU执行的机会越大;

线程的优先级 从 0-10 ,越大优先级越高, 除了数字表示优先级, Thread类中提供三个静态常量表示优先级:

static int MAX_PRIORITY  ; //线程的最高优先级,相当于 10;
static int MIN_PRIORITY  ; //线程的最低优先级,相当于 1;
static int NORM_PRIORITY  ; //线程的普通优先级,相当于 5;

可以通过 Thread 类的 setPriority(int newPriority) 方法 对线程的优先级进行设置; newPriority 为 1-10的整数 或者优先级常量;

线程休眠

可以人为的将正在执行的线程暂停,将CPU的使用权让给其他线程, 使用的是 静态 方法 sleep(long millis) ,该方法使正在执行的线程暂停一会儿,进入休眠等待状态; 该方法会声明抛出 InterruptedException异常。

public static void main(String[] args) throws InterruptedException, ExecutionException {
        Thread thread1 = new Thread(()->{
            for(int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + "正在输出i: "+ i);
            }
        },"优先级较低的线程");
        Thread thread2 = new Thread(()->{
            for(int j = 0; j < 10; j++){
                System.out.println(Thread.currentThread().getName() + "正在输出j: "+ j);
                try {
                    if(j==2){
                        Thread.sleep(500);   //在线程执行过程中进入睡眠状态,让其他线程先执行
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        },"优先级较高的线程");
        thread1.setPriority(Thread.MIN_PRIORITY);      //设置线程1的优先级为1
        thread2.setPriority(10);                       //设置线程2的优先级为2
        thread1.start();
        thread2.start();
    }

线程让步

线程让步是指 使正在运行的线程失去CPU使用权,可以实现线程暂停,让系统的调度器重新调度一次; 线程让步的方法为 yield();

线程插队

当某个线程调用其他线程的 join() 方法时,调用的线程将被阻塞,直到被 join()方法加入的线程执行完成后它才会继续执行;

 public static void main(String[] args) throws InterruptedException, ExecutionException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + "输出了" + i);
            }
        },"Thread");                            //创建一个线程 thread
        thread.start();                                 //开启 线程 thread
        for (int i = 0;i < 10; i++){
            if ( i == 5){
                thread.join();         // join()方法 , thread线程插队 先执行 , main方法线程先挂起,thread执行完后,main才执行
            }
            System.out.println(Thread.currentThread().getName() + "输出了" + i);
        }
    }

多线程同步

多线程的并发执行可以提供程序的效率,但是当多个线程访问同一个资源时,也会引发一些安全问题;如要统计一个班学生数量,要是学生一直进进出出,则很难统计正确;

为了解决这种问题,需要实现线程同步,即 限制某个资源在同一时刻只能被一个进程访问;

同步代码块

Java中同步代码块用来实现多线程的同步 。当多个线程同时使用同一个共享资源时,可以将处理的共享资源的代码放置在一个 使用 synchronized 关键字中来修饰的代码块中,这段代码块被称作同步代码块,格式如下:

synchronized(lock){
    //操作共享资源的代码块       
}
/*
lock 是一个锁对象, 当线程执行同步代码块时,会先判断 锁对象的标志位 是否为 1,若为1 则执行同步代码块;同时将 锁对象的标志位置为0;为0时 ,其他线程都无法执行同步代码块,当执行同步代码块的线程 执行完后,会将 锁对象的标志位在设为1;
*/

下面一个模仿售票窗口案例 用到 同步代码块:

public class TicketWindow implements Runnable {
    private  int ticketNumber = 10;		//定义十张票
    Object lock = new Object();         //定义任意一个类型的对象作为 锁对象; 
    @Override   //重写 run()方法
    public void run() {
        synchronized (lock){        //定义同步代码块
            while (ticketNumber > 0) {
                try {
                    Thread.sleep(100);          //模拟售票耗时过程
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketNumber-- + "张票");
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException, ExecutionException {
        TicketWindow ticketWindow = new TicketWindow();
        //开启四个线程 模拟售票窗口
        new  Thread(ticketWindow, "窗口1").start();
        new  Thread(ticketWindow, "窗口2").start();
        new  Thread(ticketWindow, "窗口3").start();
        new  Thread(ticketWindow, "窗口4").start();
    }

同步代码块中的锁对象可以是任意类型的对象,但是多个线程的共享的多对象必须是相同的;

同步方法

在 方法前面 使用 synchronized 关键字 来修饰 可以使方法变成 同步方法,能实现和同步代码块一样的功能;

被 synchronized 修饰的方法 在某一时刻 只允许一个线程访问;

同步方法 实现 上述售票窗口案例:

public class TicketWindow implements Runnable {
    private int ticketNumber = 10;
    @Override
    public void run() {
        while(ticketNumber > 0){
            saleTicket();           //调用同步方法
        }
    }
    public synchronized void saleTicket() {          //实现 同步方法;
        if (ticketNumber > 0) {
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketNumber-- + "车票");
        }
    }
}

同步锁

Ssynchronized 同步代码块和 和同步方法有一些限制,例如 无法中断一个正在等待获得所得线程,也无法通过轮询得到锁,如果不想等下去,也就没法得到锁;

JDK 5 提供了一个Lock 锁, 它可以让某个线程 在 持续获得同步锁失败后返回,不在继续等待;

Lock是一个接口 , 可以通过 他的 实现类 ReetranyLock 进行实例化;

ublic class TicketWindow implements Runnable {
    private int ticketNumber = 10;
    private final Lock lock = new  ReentrantLock();     // 定义一个锁对象lock
    @Override
    public void run() {
        while(ticketNumber > 0) {
            lock.lock();        //对代码进行加锁
            if (ticketNumber > 0) {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticketNumber-- + "票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();                   //执行完代码释放锁
                }
            }
        }
    }
}

多线程通信

生产者消费者问题,生产者线程负责生产商品,消费者线程负责消费产品;

为了控制多个线程按照一定得顺序轮流执行,需要让线程间进行通信,保证线程任务得协调进行; Java得 Object类中提供了 wait() ,notify() , notifyAll() 等方法 用于 解决 线程间得通信问题;

三个方法任意一个实现类对象都可以调用

void wait()          // 让当前线程放弃同步锁 并进入等待;知道其他线程进入次同步锁,并调用 notify() 和 notifyAll() 方法唤醒该线程为止
void notift()		  // 唤醒此 同步锁上 等待得 第一个 调用 wait() 方法得线程;
void notifyAll()      // 唤醒此 同步锁上调用 wait() 方法得所有线程;

注意 : 调用上述三种方法得对象 必须 是 同步锁 对象,否则 会爆出 IllegalMonitorStateException异常

方法举例:(实现生产一件上商品,消费一件商品)

public static void main(String[] args) {
        List<Object> goods = new ArrayList<>(); //创建一个集合  模拟存储生产得商品;
        long start = System.currentTimeMillis(); //获取线程开启时 时间;
        //创建一个 生产者线程
        Thread thread1 = new Thread(()->{
            int num = 0;
            while (System.currentTimeMillis() - start <= 100) {    // 生产线程 执行 100毫秒;
                synchronized (goods) {                       //使用同步代码块 同步生产和消费
                    if (goods.size() > 0) {
                        try {
                            goods.wait();                   // 若 集合 有商品,线程放弃同步锁进行等待
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }else {
                        goods.add("商品"+ ++num);     // 没有商品 , 生产一件商品 加入 集合;
                        System.out.println("生产商品" + num);
                    }
                }
            }
        },"生产者");
        //创建一个 消费者线程
        Thread thread2 = new Thread(()->{
            int num = 0;
            while (System.currentTimeMillis() -start <= 100){  //消费者线程执行100毫秒
                synchronized (goods) {
                    if (goods.size() <=0 ){       //如果 商品量不足 ,唤醒生产者线程
                        goods.notify();
                    }else{
                        goods.remove("商品" + ++num);
                        System.out.println("消费商品" + num);
                    }
                }
            }
        },"消费者");
        //同时开启生产者消费者线程
        thread1.start();
        thread2.start();
    }

线程池

在大规模得应用程序中,创建,分配和释放多线程对象会产生大量内存管理开销。为此,可以使用Java提供得线程池来创建多线程,进一步优化线程管理。

Executor接口

JDK5开始, 在java.util.concurrent包下增加了 Executor接口及其子类,允许使用线程池技术来管理线程并发问题;
Executor 接口 提供了一个常用得 ExecutorServic字接口 ,实现对线程池得管理;

通过Executor接口实现线程池管理得步骤:

  1. 创建 一个 实现 Runnable 或者 Callable 接口的实现类,重写 run() 或者 call() 方法;
  2. 创建 Runnable 或者 Callable 接口 的实现类对象;
  3. 使用Executor 线程执行器 创建线程池;
  4. 使用 ExecutorService 执行器服务类的 submit()方法将 Runnable 接口 或者 Callable 接口的实现类对象 提交到线程池进行管理;
  5. 线程任务执行完成后。使用 shutdown()方法关闭线程池;

Callable代码举例:

public class ExecutorExample implements Callable<Object> {
    @Override
    // 1 .重写 Callable中的call()方法;
    public Object call() throws Exception {
        int i = 0;
        while (i++ < 5){
            System.out.println(Thread.currentThread().getName()+ "正在执行call()方法");
        }
        return i;
    }
}


ublic class Application {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        MyCallable myCallable = new MyCallable();               //创建  Callable接口的 实现类的 对象
        ExecutorService pool = Executors.newCachedThreadPool(); //创建一个线程池;
        Future<Object> submit0 = pool.submit(myCallable);       //将线程放入线程池
        Future<Object> submit1 = pool.submit(myCallable);
        pool.shutdown();                                        //关闭线程池
        System.out.println(submit0.get());                      //得到线程的返回结果
        System.out.println(submit1.get());
    }
}

// 线程池是通过  Executors 的 newCachedThreadPoll() 方法创建的; Executor是jdk5中增加的线程执行器工具类
//Runnable 和 Callable类似 ,  使用 Runnable 放入 线程池管理 可以使用 executor()方法,此方法没有返回值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值