线程概述2

多线程

概念:一个程序中可以同时运行多个线程来执行不同的任务。

什么时候需要多线程?

  1. 程序中需要同时执行两个或多个任务时;

  2. 程序中需要实现需要等待的任务;例如:搜索、用户输入

多线程的优点

  1. 提高了程序的响应速度

  2. 提高了CPU的利用率

  3. 可以将复杂的任务分为多个线程同时进行,改善程序结构

多线程的缺点

  1. 线程也是程序,所以线程需要占用内存,线程越多占用的内存也就越多;

  2. 多线程需要协调和管理,所以需要CPU时间跟踪线程

    注:以上两个问题可以通过提升硬件配置解决

  3. 线程之间对共享资源的访问互相影响

    该问题出现的条件也较为苛刻:首先是在多线程的条件下,其次多线程需要操作同一个资源(变量);

    例如:卖票、取款

线程同步

并行:多个CPU同时执行多个任务;

并发:在一个时间段内依次执行操作;例如:卖票、秒杀看似同时进行,实际上是依次执行的。

多线程同步

多个线程访问同一个共享数据时,如果不加以控制,在理论上可能会出现问题

解决办法:加锁(synchronized)
  1. 继承Tread类创建线程

  2. 实现Runnable接口创建线程

    可以使用两种方式来加锁(模拟了卖票的机制):

    • 使用synchronized关键字来修饰一段代码块

    synchronized(锁对象 注:必须多个线程对应同一个对象){

    作用:

    1. 使用该对象来记录有没有线程进入到同步代码块中

    2. 使用该对象的对象头中的一块空间来记录锁的状态

    }

     static int num = 10;//static修饰后,变成了静态变量,内存中就只有一个了,所以多个线程就可以共用一个   
    static String s = new String();//用static修饰,确保多个线程对应一个对象即可
            @Override
        public void run() {
            while (true) {
                synchronized (s) {
                    if (num > 0) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                     System.out.println(Thread.currentThread().getName() + "买到了" + num--);
                    } else {
                        break;
                    }
                }
            }
        }
    • synchronized修饰静态方法,此时锁对象变为了该类的Class类对象(只有一个)

    注:当一个类用static修饰后被使用时,就会被加载到方法区中,它会为每个类创建一个class类的对象,来表示此类的信息;

    synchronized修饰非静态方法时,锁对象就是this,有可能会有多个this,无法保证多个线程对应同一个对象。

    static int num = 10;//static修饰后,变成了静态变量,内存中就只有一个了,所以多个线程就可以共用一个      
    @Override
        public void run() {
            while (true) {
                if (num <= 0) {
                    break;
                }
               this. printticket();
            }
        }
        public synchronized void printticket() {
            if (num > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "买到了" + num--);
            }
        }

同步执行的过程

  1. 第一个线程访问,锁定同步对象,执行其中代码.

  2. 第二个线程访问,发现同步对象被锁定,无法访问.

  3. 第一个线程访问完毕,解锁同步对象.

  4. 第二个线程访问,发现同步对象没有锁,然后锁定并访问.

Lock锁

除了使用synchronized关键字来加锁之外,也可以使用Lock对象来加锁;

ReentrantLock类中实现了Lock

同样采样卖票的机制来演示:

 static int num = 10;//static修饰后,变成了静态变量,内存中就只有一个了,所以多个线程就可以共用一个   
Lock lock = new ReentrantLock();     
@Override
    public void run() {
        while (true) {
           try{
           lock.lock();//加锁
                if (num > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                 System.out.println(Thread.currentThread().getName() + "买到了" + num--);
                } else {
                    break;
                }
          }finally{
               lock.unlock();//释放锁,线程执行过程中无论发生什么,都要执行此代码,来释放锁
              }
              
         }
  }

synchronized与Lock的区别

  1. 底层实现不同

    synchronized是关键字,底层实现不依赖与Java代码,依赖于底层指令

    Lock锁底层实现完全是Java代码控制的

  2. 用法不同

    synchronized既可以修饰代码块,也可以修饰方法

    Lock锁只能对某一段代码进行修饰

  3. 加锁与释放锁的方式不同

    synchronized加锁和释放锁都是隐式的;

    Lock加锁(lock.lock();//加锁)和释放锁( lock.unlock();)都是显示的。

线程死锁

多个线程分别占用对方需要的同步对象(锁对象)不放手,此时程序不会报错不会提示,相互等待对方释放锁的情况就称为死锁。

为避免程序出现死锁,在程序设计时要考虑锁的顺序,尽量减少嵌套加锁。

线程通信

线程通信指的是多个线程之间相互牵制,相互调度

所涉及到的方法:

  1. wait():在Object类中定义的,表示让线程等待,必须通过notify()进行唤醒,线程等待时,可以自动释放锁对象;

  2. notify():在Object()中定义的,唤醒等待状态的线程

  3. notifyAll():唤醒所有等待状态的线程;

注:以上3个方法,必须在同步代码块/同步方法中进行,否则会报异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值