一,多线程的概念
线程:线程是进程中的一条执行路径(执行单元)
进程:正在运行的程序
多线程程序:开启了多条线程的程序
多线程程序的好处:使用程序中的多个任务可以并发执行
注:
- 一个进程至少有一条线程,这条线程称为主线程
- 在没有创建新线程的情况下,Java程序也有两条线程:主函数所在的主线程、垃圾回收器所在的垃圾回收线程
- 并行:多个任务同时开始执行;并发:多个任务在某一个时间段内同时运行
CPU执行权的分配方式:
- 分时调度:CPU将执行权平均的分配给不同任务
- 抢占式:多个任务抢夺CPU的执行权(随机性),Java中的多线程就是采用抢占式的
二,创建线程的方式
方式一:继承Thread类
步骤:
定义一个类继承Thread
重写Thread类的run()方法
注:run()方法中的功能就是多线程中要并发执行的任务
创建子类对象
调用Thread类中的start()方法启动线程
方式二:实现Runnable接口
步骤:
- 定义一个类实现Runnable接口
- 重写run()方法
- 创建实现类对象
- 创建Thread类的对象,并将Runnable接口的实现类对象作为参数传递进Thread类的构造函数中
- 调用Thread类的start()方法启动线程
注:
- start()方法用于开启线程,线程开启后会调用重写后的run()方法
- 同一个线程对象不能重复开启,否则会发现
IllegalThreadStateException
非法线程状态异常- 只有程序中的所有线程全部执行完毕后,JVM才会结束
实现Runnable接口比继承Thread类好在避免了单继承的局限性
以上两种方式都可以使用匿名内部类来实现:
new Thread(new Runnable(){ @Override public void run() { for(int i = 1;i<=100;i++){ System.out.println(Thread.currentThread() + "---" + i); } } }).start(); new Thread(){ @Override public void run() { for(int i = 1;i <= 100;i++){ System.out.println(Thread.currentThread() + "---" + i); } } }.start();
三,线程安全问题
什么是线程安全的:
多个线程操作同一数据,最终的结果与单线程操作这一数据的结果一致就是线程安全的
线程出现安全问题的原因:多个线程操作同一共享数据
解决办法:使用同步技术
同步的关键字:
synchronized
同步代码块:将存在线程安全问题的代码包裹在同步代码块中
synchronized(对象){ // 存在线程安全问题的代码 }
注:
- 同步代码块中的对象称为锁(对象),这个对象的类型是任意的,但是要保证这个对象的唯一的
- 如果是以继承Thread类的方式来创建线程的话,同步代码块的锁对象应该是静态的
同步函数:将存在线程安全问题的代码封装在一个函数中
public synchronized void 方法名(){ // 存在线程安全问题的代码 }
注:
- 在实现Runnable的方式中,同步函数也有锁,锁对象是this
- 在继承Thread的方式中,使用同步函数无效,因为每个线程对象都有各自的this,锁不是唯一的;此时这个同步函数需要改写成静态同步函数,静态同步函数中的锁是
类名.class
同步技术的原理:
- 所有线程共同抢夺CPU的执行权,谁抢到了执行权就要先判断锁是否存在,如果锁存在就获取锁并进入同步中;如果锁不存在就处于阻塞状态,等待获取了锁的线程在执行完功能后释放锁
- 抢夺到锁的线程在执行完同步中的所有代码后会执行释放锁的动作,再与之前处于阻塞状态的线程共同争夺CPU的执行权