进程定义:
是一个正在执行中的程序,每个程序都有一个执行顺序,该顺序是一个执行路径,或者叫做执行单元。
线程定义:
是进程中的独立控制单元,是最小的执行单元,一个进程至少有一个线程。
多线程定义:
一个进程中包含多个线程
一:为何要使用多线程?
1. 为了更好地利用cpu资源,如果只有一个线程,则第二个任务必须要等到第一个任务执行完毕以后才可以执行。但是用多线程后, 当主线程在执行任务的时候,其他线程任务也可以执行,不需要等待。
2. 进程之间不可以共享数据,但是线程之间可以。
3. 系统创建进程需要为进程分配系统资源,创建线程代价更小。
4. Java内置了多线程功能支持,编码更加方便。
二:线程的生命周期
- 新建线程: 从新建一个线程对象到执行到start()语句,都属于创建阶段。
- 就绪状态: 这个状态下有执行资格但是没有执行权,线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度;(不停的抢占cpu)
- 运行状态: 这个状态下有执行资格也有执行权,start()方法开启进程后就开始运行run()方法,进入运行代码阶段。这个时候它可以变为 就绪,阻塞,死亡三个状态。
- 等待/阻塞/睡眠状态:在一个线程执行了sleep()---- 睡眠方法后,该线程进入阻塞状态,当阻塞时间过去以后,回到就绪状态,再次开始抢占cpu。
- 死亡: run( ) 执行完或者遇到其他终止条件后,线程死亡。
三: 多线程的创建方式:
第一种:继承Thread类:
步骤:
a) 继承Thread类,
b) 重写 run()_方法,在方法里面写入自己需要运行的代码。
c) 调用start()方法:
该方法两步:开启线程 ,调用run( ) 执行代码
第二种:实现Runnable接口:
步骤:
- 定义实现Runnable接口。
- 重写 run()_方法,在方法里面写入自己需要运行的代码。
- 创建Runnable对象。
- 通过Thread创建线程。并将Runnable对象交给Thread类的构造函数。
- 调用Thread的start方法开启线程 并 执行run() 方法。
四、继承Thread类和实现Runnable接口、实现Callable接口的区别。
1.继承Thread:线程代码存放在Thread子类run方法中。
优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
劣势:已经继承了Thread类,无法再继承其他类。
2.实现Runnable:线程代码存放在接口的子类的run方法中。
优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
3.实现Callable:
优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法
建议使用实现接口的方式创建多线程。
五: 一个线程安全的案例:
没锁的时候会有线程安全问题:
线程开启之后 当
窗口一 先执行run() 方法 他会在sleep(睡眠方法)执行后进入睡眠,该时候不影响其他线程执行。窗口二也是这样。
当窗口三执行到这里的时候,窗口一醒来继续执行 ticket++,这个时候变为1号票,但是没有输出。
因为线程执行具有随机性,你不知道什么时候执行那个线程。如果这个时候窗口二 抢夺了cpu 那它继续执行 ticket++,这个时候变为2号票
如果这个时候输出了 那窗口一 和二 都出的是二号票。
解决方法: 加上锁synchronized(锁对象)
锁默认打开 一个线程进去 锁关闭 该线程执行完 锁开启
为什么要进行线程同步?
java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如对其进行增删改查操作),会导致数据不准确,而且相互之间产生冲突。所以加入同步锁以避免该线程在没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性。
六.同步方法:
代码:
Ctrl+alt+m 变成同步方法:
同步方法
作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
同步方法底层原理:
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
是同步代码块好还是同步方法好一点?
同步代码块 锁的范围更小,同步方法 锁的范围更大。
七:手动上锁(LOCK锁):
无论是同步代码块还是同步方法,虽然都锁住了对象,但是都无法直接看到在那里上了锁,在那里释放了锁。为了更加清晰的看到那里上了锁,在那里释放了锁,我们采用了Lock锁的方法对对象进行上锁。
实现方法(手动上锁):void lock(): 获得锁 void unlock(): 释放锁
注意:Lock是接口无法直接实例化,需要采用他的实现类ReentrantLock来实例化。
示例代码:
上面代码结果显示有问题(因为程序无法进行完毕):
解决方法:
将整个锁下面的执行语句放在异常里面 利用异常的 finally{ } 来把释放锁放在里面
finally{ }-----它里面的代码是一定会执行的
代码示例: