java学习笔记——多线程

一、
什么是程序?为了完成某项特定的任务,使用某种语言,编写一组指令的集合
什么是进程?一个正在进行中的程序
什么是线程?在一个进程中执行的一套功能流程,称为线程
            在一个进程中执行的多套功能流程,称为多线程

二、为什么使用多线程?
抢占式策略系统:系统会为每个执行任务的线程分配一个很小的时间段,当该时间段用完后
                系统会强制剥夺其 cpu 执行权,交给其他线程执行任务

1.提高程序效率,提高效率的方式是尽可能的利用 cpu 的资源
2.可以增强用户体验

三、如何使用多线程?
创建执行线程的方式一:
①声明一个类继承 Thread 类
②重写 Thread 类的 run() 方法,同时编写线程执行体
③创建该子类的实例
④调用 Thread 的 start() 方法启动线程,默认执行 run() 方法

创建执行线程的方式二:
①声明一个类实现 Runnable 接口
②实现接口中的 run() 方法,同时编写线程执行体
③创建该实现类的实例
④将该实现类的实例作为参数,传递给 Thread 的构造器
⑤调用 Thread 类的 start() 方法启动线程,默认执行 run() 方法

继承 Thread 类的方式与实现 Runnable 接口的方式区别?
①实现接口的方式解决了 Java 中单继承的局限性
②当多个线程需要访问共享数据时,首选使用实现 Runnable 接口的方式

四、线程的常用方法:
currentThread() : 是静态方法,获取当前执行线程
getName() : 获取线程名称
setName(String name) : 设置线程名称
start() : 启动线程

sleep(long millis) : 是一个静态方法,使当前线程进入睡眠状态。
join()/join(long millis) : 是一个实例方法,使当前调用线程进入阻塞状态,直到实例执行结束或到达时间。
interrupt() : 是一个实例方法,中断阻塞状态的实例线程
isAlive() : 实例方法判断实例线程是否处于存活状态
yield() : 实例方法线程让步

五、线程的优先级(1-10):默认优先级为 5. 优先级高并不意味着线程一定会优先执行,只不过
                         更高概率获取 cpu 的资源。
getPriority() : 获取线程优先级
setPriority() : 设置线程优先级

MIN_PRIORITY : 1
NORM_PRIORITY : 5

MAX_PRIORITY : 10



六、线程的生命周期


七、线程同步
【例题】模拟售票程序,实现三个窗口同时售票 100 张
遇到问题:当多个线程同时访问共享数据时,出现了无序、重复、超额售票等多线程安全问题
解决办法:将多个线程需要访问的共享数据,包装起来,视为一个整体,确保一次只有一个线程执行流访问共享数据

1. 同步代码块

    synchronized(同步监视器){
        //需要访问的共享数据
    }
    同步监视器 : 俗称“锁”,可以使用任意对象充当。但是,确保多个线程持有同一把锁(同一个对象)

2. 同步方法:在方法的声明处使用 synchronized 关键字
    
例如:
public synchronized void show(){//隐式的锁:(非静态同步方法的锁为 : this,静态同步方法锁为:Class 实例

}

3. 同步锁:Lock 可以做更加复杂的操作

注意:必须保证手动通过 unlock() 释放锁

一、Lock和synchronized的区别和各自的特点

1、类型不同
Lock是一个接口,是JDK层面的实现;synchronized是Java的关键字,是JVM层面的实现,是Java的内置特性;这也造成了在锁管理上的不同。
2、释放锁的方式
Lock是在编码中进行锁的操作,需要主动调用接口中的方法释放锁,所以使用Lock时需要配合try模块,在finally中释放锁,不然一旦发生异常很可能造成死锁现象;synchronized由于是JVM层面的实现,获取锁和释放锁的操作是不可见的,当发生异常时,锁的释放由JVM实现。
3、获取锁的过程
Lock接口中提供的方法,让获取锁的过程更加灵活,编程人员可以方便的在获取锁的过程中进行多种操作,比如尝试获取锁、设置获取锁的超时时间、中断等待获取锁的线程等,应该说让锁的操作变得“丰富多彩”起来;synchronized是无法这么灵活的对锁进行操作的。
4、效率
基于读写锁接口的扩展,Lock可以提高多个线程进行读操作的效率,在大并发量的情况下,效率的提高会尤其明显。

二、Lock应用场景举例

1、解决获取锁的等待问题
如果占有锁的线程A由于各种原因导致阻塞而没有释放锁,此时其他线程B也需要获得该锁。synchronized的机制是让B持续等待,如果A一直没有释放锁,那么B将一直等待,这会很大程度影响执行的效率;而Lock中提供了中断线程等待的方法,也提供了带有超时时间的获取锁的方法,后面会讲到这些方法。
2、读写锁的分离
我们知道,多线程仅仅是执行读操作的话是没有冲突问题的,因而在读操作时的锁没必要是独占的。synchronized实现同步就会导致在读操作时只能有一个线程获得锁,其他线程只能等待锁的释放。Lock中的ReentrantReadWriteLock很好的解决了这个问题。
3、其他锁的操作
如获知当前线程是否成功获得锁等,synchronized是做不到的。

三、几种锁的概念介绍

1、可重入锁
具备可重入性的锁,即为可重入锁,比如synchronized和ReentrantLock都是。锁基于线程分配,当某个线程获得了锁去执行某个方法,方法中如果再次需要获取锁资源时,当前线程可以直接获得锁,不必重新申请。
2、可中断锁
可以响应中断的锁。synchronized不是可中断锁,但是Lock是可中断锁。
3、公平锁
尽量按照请求锁的顺序获得锁。synchronized是非公平锁,ReentrantLock和ReentrantReadWriteLock提供了设置公平锁的方法,不过默认为非公平锁。非公平锁可能导致某些线程永远获取不到锁。
4、读写锁
将对资源的锁分为两个,一个读锁和一个写锁,使得多个线程之间的读操作不会发生冲突可以并行,这极大的提高了读的效率。不过读锁和写锁、写锁和写锁之间都是互斥的。




八、线程通信(线程交互):当多个线程完成某项任务时,多个线程之间可能需要一定通信,即线程通信。

【等待唤醒机制】
在 java.lang.Object 类中
wait() : 使当前“同步监视器”上的线程进入等待状态。同时释放锁
notify() / notifyAll() : 唤醒当前“同步监视器”上一个/所有等待状态的线程。

注意:上述方法必须使用在 同步 中。



int i = 0;
Object obj = new Object();
public void run(){
        while(true){
            synchronized(obj){
                obj.notify();

            if(i <= 100){
                System.out.println(i++);
            }
            try{
                obj.wait();
            }catch(InterruptedException e){
            }
            }
        }
}

【经典案例】生产者消费者案例
添加或创建数据的线程:生产者线程
删除或销毁数据的线程:消费者线程

猜你喜欢

转载自blog.csdn.net/qq_25106373/article/details/80849137