多线程


前言


提示:以下是本篇文章正文内容,下面案例可供参考

一、有关多线程概念

1、什么是进程

可以理解为正在运行的程序,是操作系统分配和调度的最小单元。

2、什么是线程

程序中做的事情, 线程是CPU调度最小单元。线程它是进程的一部分,不能独立存在一个进程,可以有多条线程,至少有一条线程

3、为什么要使用多线程?

可以让程序同时做不同的事情,提高程序的执行效率(例如迅雷同时下载多个文件)

4、线程类及常用方法

void setName(String name) 将此线程的名称更改为等于参数name

String getName() 返回此线程的名称

static Thread currentThread() 返回对当前正在执行的线程对象的引用

static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数

final int getPriority() 返回此线程的优先级

final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

二、实现多线程的三种方式

1.方式一:继承Thread类

  • 定义一个类MyThread继承Thread类

  • 在MyThread类中重写run()方法

  • 创建MyThread类的对象

  • 启动线程

public class MyThread extends Thread{
    @Override
    public void run() {
        //代码就是线程在开启之后执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开启了" + i);
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        //创建一个线程对象
        MyThread t1 = new MyThread();
        //创建一个线程对象
        MyThread t2 = new MyThread();
        //开启一条线程
        t1.start();
        //开启第二条线程
        t2.start();
    }
}

2.方式二:实现Runnable接口

  • 定义一个类MyRunnable实现Runnable接口

  • 在MyRunnable类中重写run()方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

  • 启动线程

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程启动后执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println("第二种方式实现多线程" + i);
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        //创建了一个参数的对象
        MyRunnable mr = new MyRunnable();
        //创建了一个线程对象,并把参数传递给这个线程.
        //在线程启动之后,执行的就是参数里面的run方法
        Thread t1 = new Thread(mr);
        //开启线程
        t1.start();

        MyRunnable mr2 = new MyRunnable();
        Thread t2 = new Thread(mr2);
        t2.start();

    }
}

3.方式三:实现Callable接口

  • 定义一个类MyCallable实现Callable接口

  • 在MyCallable类中重写call()方法

  • 创建MyCallable类的对象

  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数

  • 启动线程

  • 再调用get方法,就可以获取线程结束之后的结果

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();
        //Thread t1 = new Thread(mc);

        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);

        //创建线程对象
        Thread t1 = new Thread(ft);

        //String s = ft.get();
        //开启线程
        t1.start();

        String s = ft.get();
        System.out.println(s);
    }
}

4.三种方式的对比

1.实现Runnable、Callable接口

  • 好处: 扩展性强,实现该接口的同时还可以继承其他的类
  • 缺点: 编程相对复杂,不能直接使用Thread类中的方法

2.继承Thread类

  • 好处: 编程比较简单,可以直接使用Thread类中的方法
  • 缺点: 可以扩展性较差,不能再继承其他的类

5.案例实现(电影院卖票)

  • 案例需求
    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
  • 实现步骤
    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下
    • 判断票数大于0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数要减1
    • 票卖没了,线程停止
    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程

线程类

public class Ticket implements Runnable {
    //票的数量
    private int ticket = 100;

    @Override
    public void run() {
        while(true){
            
                if(ticket == 0){
                    //卖完了
                    break;
                }else{
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            }
        
  }
}

测试类

public class Demo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

三、线程安全问题

1.什么是线程安全问题?

所谓的线程安全问题就是多线程环境下,出现数据结果与预期不一致的情况就称为线程安全问题

2.什么情况下会出现线程安全问题

多条线程操作共享数据(可以拆分为三个条件):

  1. 多线程环境
  2. 有共享数据
  3. 多条线程操作了共享数据

其实上述电影院卖票的案例也存在线程安全的问题,我们让在每个售票员卖票的时候模拟出票的时间(让线程睡眠1秒)就会发现其中的线程安全问题了
代码演示

package com.itheima.threaddemo9;
public class Ticket implements Runnable {
    //票的数量
    private int ticket = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj){//多个线程必须使用同一把锁.
                if(ticket <= 0){
                    //卖完了
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

3.如何解决线程安全问题?

加锁,在共享数据的代码块加锁,保证每个线程访问时候不会被其他线程干扰。那么怎么加锁呢?其实有两种方式:使用synchronized关键字或者使用Lock对象。

synchronized关键字

synchronized关键字可以用来修饰代码块,也可以修饰方法。代码块前加上 synchronized关键字的代码块被称为同步代码块方法前面加上 synchronized关键字的方法被称为同步方法。synchronized锁的是括号里的对象,而不是代码。对于非静态的synchronized方法,锁的是对象本身也就是this静态的synchronized方法,锁对象是 类名.class

代码如下(线程类):

public class MyThread extends Thread {
    private static int ticketCount = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while(true){
            synchronized (obj){ //就是当前的线程对象.
                if(ticketCount <= 0){
                    //卖完了
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                }
            }
        }
    }
}

Lock对象

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

方法:
void lock()获得锁

void unlock() 释放锁

代码如下(线程类):

public class Ticket implements Runnable {
    //票的数量
    private int ticket = 100;
    private Object obj = new Object();
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //synchronized (obj){//多个线程必须使用同一把锁.
            try {
                lock.lock();
                if (ticket <= 0) {
                    //卖完了
                    break;
                } else {
                    Thread.sleep(100);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            // }
        }
    }
}

四、线程池

1、什么是线程池?

存线程的容器就是线程池

2、为什么要使用线程池?**

用少量的线程执行更多的任务,提高线程的利用率

3、.如何创建线程池对象 :

常用构造方法:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

常用方法:

Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。

void shutdown() 关闭线程池,先前提交的任务将被执行,但不会接受任何新任务

如果线程执行完没有返回值,未来的get方法将在成功完成后返回null;如果线程执行完有返回值,未来的get方法将在成功完成后返回线程执行完的返回值

代码示例:

public class MyThreadPoolDemo3 {
//     参数一:核心线程数量                       --不能小于0--
//     参数二:最大线程数                         --不能小于0,最大数量>=核心线程数量--
//     参数三:空闲线程最大存活时间                --不能小于0--
//     参数四:时间单位                           --时间单位--
//     参数五:任务队列                           --不能为null--
//     参数六:创建线程工厂                        --不能为null--
//     参数七:任务的拒绝策略                      --不能为null--
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
        2,
        5,
        2,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(10), 
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());
        
         pool.submit(new MyRunnable());
         pool.submit(new MyRunnable());

         pool.shutdown();
    }
}

总结

1.多线程的三种实现方式,在开发中最常用的是 实现Runnable接口

2…如何去写多线程程序?

  • 确定要做什么,执行什么任务

  • 要不要用多线程,要任务返回的结果,如果要实现Callable接口,如果不要实现Runnable接口

  • 实现call方法或run方法

  • 交给线程类,执行start(),开启线程干

3.什么情况下会出现线程安全问题? 多条线程操作共享数据

4…锁对象为什么要唯一? 不同线程如果锁的不是同一个对象,就解决不了线程的安全问题

5.非静态的synchronized方法,锁的是对象本身也就是this静态的synchronized方法,锁
对象是 类名.class

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值