java高级04-线程

目录

一、多线程

1.进程

2.线程

3.并发

4.并行

5.多线程的实现

(1)继承Thread类

(2)实现Runnable接口

(3)利用Callable和Future接口

6.三种方式的对比

7.线程调度

(1)多线程的并发运行:

(2)调度模型

(3)后台线程/守护线程

8.类加载器

9.线程安全问题

(1)没有做线程安全

(2)问题:

(3)解决方式一同步代码块

(4)同步代码块

①同步的好处和弊端

(5)解决方式一同步方法

(6)同步代码块和同步方法的区别

(7)同步静态方法

(8)Lock锁

(9)死锁

10.生产者和消费者

(1)生产者消费者模式

(2)组成

(3)理想状态

(4)可能出现的状况

(5)等待和唤醒的方法

(6)代码实现


一、多线程

1.进程

正在运行的软件

  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位

  • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的

  • 并发性:任何进程都可以同其他进程一起并发执行

2.线程

是进程中的单个顺序控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径,则称为单线程程序

  • 多线程:一个进程如果有多条执行路径,则称为多线程程序

主要方法

方法名返回值类型说明
getName()String返回此线程的名称 线程是默认名称:Thread-编号
setName(String name)void设置线程名称 Thread类构造方法 也可,子类需要继承Thread并添加一个无参和一个String参数的构造
currentThread() 静态方法Thread返回对当前正在执行的线程对象的引用
sleep() 静态方法void让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)void设置该线程的优先级,默认是5,范围1到10
getPriority()int获取该线程优先级
setDaemon(boolean on)void设置为守护线程

3.并发

在同一时刻,有多个指令在单个CPU交替执行

4.并行

在同一时刻,有多个指令在多个CPU同时执行

5.多线程的实现
  • 继承Thread类

  • 实现Runnable接口

  • 利用Callable和Future接口

(1)继承Thread类
  • 定义一个类,继承Thread类

  • 在该类中重写run()方法(线程开启后执行的代码)

  • 创建该类对象

  • 启动线程

public class Application  extends Thread{
​
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("线程1开启");
        }
    }
}
public class ThreadDemo01 {
    public static void main(String[] args) {
        Application application01 = new Application();
        Application application02 = new Application();
​
        application01.start();
        application02.start();
    }
}

①为什么要重写run()方法?

因为run0是用来封装被线程执行的代码

②run()和start()区别?

run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程

start():启动线程;然后由JVM调用此线程的run0方法

(2)实现Runnable接口
  • 定义一个类,实现Runnable接口

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

  • 创建该类对象

  • 创建Thread类对象,把上步创建的对象作为构造方法的参数传入

  • 启动线程

Application02 application021 = new Application02();
Application02 application022 = new Application02();
​
//线程对象
Thread thread1 = new Thread(application021);
thread1.start();
Thread thread2 = new Thread(application022);
thread2.start();

(3)利用Callable和Future接口
  • 定义一个类,实现Callable接口

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

  • 创建该类对象

  • 创建Future的实现类FutureTask对象,将上步对象作为构造方法的参数传入

  • 创建Thread类对象,把上步创建的对象作为构造方法的参数传入

  • 启动线程

public class Application03 implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程启动"+i);
        }
        //返回线程运行完成后的结果
        return "运行完毕";
    }
}
        
        Application03 application03 = new Application03();
        FutureTask<String> futureTask01 = new FutureTask<>(application03);
        Thread thread01 = new Thread(futureTask01);
        thread01.start();
        //获得线程运行之后的结果
        //如果线程还没有运行结束,那么get方法会在这里死等,在没有获取到结果之前,代码不会往下执行!
        System.out.println(futureTask01.get());
​
        Application03 application04 = new Application03();
        FutureTask<String> futureTask02 = new FutureTask<>(application03);
        Thread thread02 = new Thread(futureTask02);
        thread02.start();
        System.out.println(futureTask02.get());
6.三种方式的对比
实现方式优点缺点
继承Thread类编程简单,能直接使用Thread类中的方法拓展性低,不可继承其他的类
实现Runnable接口,利用Callable和Future接口拓展性高,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法

7.线程调度
(1)多线程的并发运行:

计算机中的CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码.各个线程轮流获得CPU的使用权,分别执行各自的任务。

(2)调度模型
  1. 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程用CPU的时间片

  2. 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级一样,那么会随机选择一个,优先级高的线程获得的CPU时间片相对多一些。

java使用的是第二种

方法在线程方法里

(3)后台线程/守护线程

当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了(但不会立即停,如果最后还有CPU时间片,会使用完再结束)

8.类加载器

主要方法

方法名返回值类型说明
getSystemClassLoader() 静态方法ClassLoader获取系统类加载器
getResourceAsStream(String name)InputStream加载某一个资源文件
public class ClassLoaderDemo {
    public static void main(String[] args) throws IOException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        //注意:
        //在IO流中,相对路径是相对整个项目而言
        //在类加载器当中,路径是在src下的
        InputStream resource = classLoader.getResourceAsStream("properties.properties");
​
        Properties properties = new Properties();
        properties.load(resource);
        System.out.println(properties);
​
        resource.close();
​
    }
}

9.线程安全问题

某电影院目前正在上映电影,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

(1)没有做线程安全
package classloader;
​
public class Tickets implements Runnable{
    private int tickets = 100;
​
    public Tickets() {
    }
​
    public Tickets(int tickets) {
        this.tickets = tickets;
    }
​
    @Override
    public void run() {
        while (true){
              if (tickets<=0){
                    break;
                }else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    tickets--;
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
              }
​
        }
​
    }
}
package classloader;
​
public class Application {
    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        Thread thread01 = new Thread(tickets);
        Thread thread02 = new Thread(tickets);
        Thread thread03 = new Thread(tickets);
​
        thread01.setName("第一窗口");
        thread02.setName("第二窗口");
        thread03.setName("第三窗口");
​
        thread01.start();
        thread02.start();
        thread03.start();
​
    }
}
(2)问题:

同一个票卖三个人

负的票数

多线程操作共享数据

(3)解决方式一同步代码块

把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

使用同步代码块

@Override
public void run() {
    while (true){
        synchronized (o){
            if (tickets<=0){
                break;
            }else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                tickets--;
                String name = Thread.currentThread().getName();
                System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
            }
        }
​
        }
​
}
(4)同步代码块
synchronized (锁对象【任意对象】){//多个线程必须使用同一把锁
    //操作共享数据的代码
}

默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭

当线程执行完出来了,锁才会自动打开

①同步的好处和弊端
  • 好处:解决了多线程的数据安全问题

  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

(5)解决方式一同步方法

就是把synchronized关键字加到方法上

锁对象:this

public class Tickets02 implements Runnable{
    private int tickets = 100;
​
    @Override
    public void run() {
        while (true){
           if (("第一窗口").equals(Thread.currentThread().getName())){
                if (synchronizedMethod()){
                    break;
                }
           }
           if (("第二窗口").equals(Thread.currentThread().getName())){
               synchronized (this){
                   if (tickets == 0){
                       break;
                   }else {
                       try {
                           Thread.sleep(100);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       tickets--;
                       String name = Thread.currentThread().getName();
                       System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
                   }
​
               }
           }
        }
​
    }
​
    private synchronized boolean synchronizedMethod() {
​
        if (tickets <= 0){
            return true;
        }else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tickets--;
            String name = Thread.currentThread().getName();
            System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
            return false;
        }
    }
}
public class Application02 {
    public static void main(String[] args) {
        Tickets02 tickets02 = new Tickets02();
​
        Thread thread01 = new Thread(tickets02);
        Thread thread02 = new Thread(tickets02);
​
        thread01.setName("第一窗口");
        thread02.setName("第二窗口");
​
        thread01.start();
        thread02.start();
​
    }
}
(6)同步代码块和同步方法的区别
  • 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码

  • 同步代码块可以指定锁对象,同步方法不能指定锁对象

(7)同步静态方法

就是把synchronized关键字加到静态方法上

锁对象:类名.class

public class Tickets03 implements Runnable {
    private static int tickets = 100;
​
    @Override
    public void run() {
        while (true){
            if (("第一窗口").equals(Thread.currentThread().getName())){
                if (synchronizedMethod()){
                    break;
                }
            }
            if (("第二窗口").equals(Thread.currentThread().getName())){
                synchronized (Application02.class){
                    if (tickets == 0){
                        break;
                    }else {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        tickets--;
                        String name = Thread.currentThread().getName();
                        System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
                    }
​
                }
            }
        }
​
    }
​
    private static synchronized boolean synchronizedMethod() {
​
        if (tickets <= 0){
            return true;
        }else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tickets--;
            String name = Thread.currentThread().getName();
            System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
            return false;
        }
    }
}

(8)Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法

方法名返回值类型说明
lock()void获得锁
unlock()void释放锁

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

public class Tickets04 implements Runnable {
​
    private int tickets = 100;
    private Object o = new Object();
    private ReentrantLock lock = new ReentrantLock();
​
    public Tickets04() {
    }
​
    public Tickets04(int tickets) {
        this.tickets = tickets;
    }
​
    @Override
    public void run() {
        while (true) {
​
            try {
                lock.lock();
                if (tickets <= 0) {
                    break;
                } else {
                    Thread.sleep(100);
                    tickets--;
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "卖出一张票" + "还剩" + tickets + "张票");
                }
​
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
​
​
    }
​
​
}

(9)死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

代码实现死锁效果

public class ThreadSafe {
    public static void main(String[] args) {
        Object oA = new Object();
        Object oB = new Object();
        new Thread(() -> {
            while (true){
                synchronized (oA){
                    synchronized (oB){
                        System.out.println("A进行中");
                    }
                }
            }
        }).start();
​
        new Thread(() -> {
            while (true){
                synchronized (oB){
                    synchronized (oA){
                        System.out.println("B进行中");
                    }
                }
            }
        }).start();
​
    }
}

解决:避免锁嵌套~

10.生产者和消费者
(1)生产者消费者模式

生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻

(2)组成
  • 生产者

  • 商品架

  • 消费者

(3)理想状态
  • 消费者先抢到执行权,买走商品

  • 生产者抢到执行权,生产商品

  • 消费者再抢

  • 生产者再抢

  • 反复

(4)可能出现的状况

消费者等待:

  • 消费者先抢到执行权,但商品架无商品

  • 消费者等待商品

  • 生产者抢到执行权,生产商品并放到商品架上

  • 生产者唤醒消费者(给消费者提示消息)

  • 消费者买走商品

生产者等待:

  • 生产者先抢到执行权,判断商品架是否有商品,没有就生产商品并放到商品架上

  • 还是生产者抢到执行权,但因为有商品,不生产,生产者等待

  • 消费者抢到执行权,买走商品

  • 消费者唤醒等待的生产者

消费者步骤:

  • 判断是否有商品

  • 有就买走

  • 商品架商品消失

  • 唤醒等待的生产者继续生产

  • 需要的商品总数-1

生产者步骤:

  • 判断商品架是否有商品,如果没有就生产商品,否则等待

  • 将商品放到商品架上

  • 唤醒等待的消费者

(5)等待和唤醒的方法

为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中

方法名返回值类型说明
wait()void导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAII()方法
notify()void唤醒正在等待对象监视器的单个线程
notifyAII()void唤醒正在等待对象监视器的所有线程

(6)代码实现

消费者:

package thread.thread_practice;
​
public class Consumer extends Thread {
​
    @Override
    public void run() {
        //1.while(true)
        //2.synchronized 锁,锁对象要唯一
        //3.判断共享数据是否结束,结束
        //4.继续判断共享数据是否结束,没有结束
        while (true){
            synchronized (GoodsShelf.LOCK){
                if (GoodsShelf.count == 0){
                    break;
                }else {
                    if (GoodsShelf.flag){
                        //有
                        System.out.println("一共需要"+GoodsShelf.count+"个商品");
                        System.out.println("消费者买走商品");
                        GoodsShelf.flag = false;
                        GoodsShelf.LOCK.notifyAll();
                        GoodsShelf.count--;
                        System.out.println("还剩"+GoodsShelf.count+"个商品");
                    }else {
                        try {
                            GoodsShelf.LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

生产者:

package thread.thread_practice;
​
public class Producer extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (GoodsShelf.LOCK){
               if (GoodsShelf.count == 0){
                   break;
​
               }else{
                   if (!GoodsShelf.flag){
                       System.out.println("生产者生产商品");
                       GoodsShelf.flag = true;
                       GoodsShelf.LOCK.notifyAll();
​
                   }else {
                       try {
                           GoodsShelf.LOCK.wait();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
            }
        }
    }
}

商品架:

package thread.thread_practice;
​
public class GoodsShelf {
    /**
     * 表示为true时,表示有商品,允许消费者买走
     *  表示为false时,表示无商品,允许生产者生产
     */
    public static boolean flag = false;
    /**
     * 商品总数量
     */
    public static int count = 10;
​
    /**
     * 生产者和消费者的锁对象
     */
    public static final Object LOCK = new Object();
}

测试:

package thread.thread_practice;
​
public class Application {
    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        Producer producer = new Producer();
​
        Thread thread1 = new Thread(consumer);
        Thread thread2 = new Thread(producer);
​
        thread1.start();
        thread2.start();
​
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值