一、什么是线程?
这里需要理解程序、进程、线程的概念和它们之前的关系
- 程序:程序是完成特定任务、用某种语言编写的一组指令的合集。程序是静态的
- 进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有产生、存在和消亡的过程。作为系统进行资源分配单位,系统在运行时会给每个进程分配不同的内存区域。进程是程序的实体,一个程序可以有多个进程
- 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。一个进程可以有多个线程;同一进程中的多条线程将共享该进程中的全部系统资源,但同一进程中的多个线程有各自的运行栈、程序计数器;进程中的多条线程可并行执行不同的任务,但多个线程操作共享资源可能会带来安全隐患
二、实现
线程实现分为4种方式,实现Runnable接口、实现Callable接口、继承Thread类、线程池,如下代码
// 请注意,此写法在并发的情况下是线程不安全的,后面的篇幅有修正
// 继承Tread
public class SellerJerry implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println("余票充足,可出票。票号:"+ticket);
ticket--;
}else{
break;
}
}
}
}
// 实现Runnable接口
public class SellerTom extends Thread{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println("余票充足,可出票。票号:"+ticket);
ticket--;
}else{
break;
}
}
}
}
//实现Callable接口
public class SellerJam implements Callable{
@Override
public Object call(){
int sum = 0;
return sum;
}
}
// 测试
@SpringBootTest
public class ThreadTomTest {
@Test
public void test1() {
Runnable sellerJerry = new SellerJerry();
Thread jerry = new Thread(sellerJerry);//创建
jerry.start();//启动线程
Thread tom = new SellerTom();//创建
tom.start();//启动线程
SellerJam jam = new SellerJam();
}
}
三、线程的生命周期
创建 NEW、就绪、运行 RUNNABLE、阻塞 BLOCKED/WATTING/TIMED_WATTING、死亡TERMINATED
简单实现如代码,在进入就绪状态之后,等待cpu分配资源,即可进入运行状态,执行完run()方法之后,线程结束。线程的最终状态一定是死亡。线程执行的过程和各个生命周期之间可能出现的转换,如下图
四、线程安全问题
当多个线程操作共享数据且操作为非原子性操作时,就会出现线程安全问题(如经典的售票、取款等问题)。解决的办法有如下几种
1.synchronized同步代码块,语法为:synchronized(同步监视器){ //需要被同步的代码 }
- 操作共享数据的代码,即为需要被同步的代码
- 共享数据:多个线程共同操作的变量。如ticket就是共享数据
- 同步监视器:俗称锁。任何一个类的对象,都可以充当锁。多个线程必须要共有一把锁。
// 还是用上面的代码,改为线程安全的写法
// 实现Runnable接口
public class SellerJerry implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {// 当前对象为锁
if (ticket > 0) {
System.out.println("余票充足,可出票。票号:" + ticket);
ticket--;
}else {
break;
}
}
}
}
}
// 继承Thread类
public class SellerTom extends Thread{
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (SellerTom.class) {// 当前对象为锁
if (ticket > 0) {
System.out.println("余票充足,可出票。票号:" + ticket);
ticket--;
}else {
break;
}
}
}
}
}
@SpringBootTest
public class ThreadTomTest {
@Test
public void test1() {
Runnable sellerJerry = new SellerJerry();
Thread jerry1 = new Thread(sellerJerry);//创建
Thread jerry2 = new Thread(sellerJerry);//创建
Thread jerry3 = new Thread(sellerJerry);//创建
jerry1.start();//就绪
jerry2.start();//就绪
jerry3.start();//就绪
Thread tom1 = new SellerTom();//创建
Thread tom2 = new SellerTom();//创建
Thread tom3 = new SellerTom();//创建
tom1.start();//就绪
tom2.start();//就绪
tom3.start();//就绪
}
}
2.同步方法
//实现Runnable
public class SellerJerry implements Runnable{
private int ticket = 100;
@Overried
public void run(){
while(true){
sellerTicket();
}
}
// 抽取方法
private snychorized void sellerTicket(){
if(ticket > 0){
System.out.println("余票充足,可出票。票号:" + ticket);
ticket--;
}
}
}
//继承Thread
public class SellerTom implements Runnable{
private static int ticket = 100;
@Overried
public void run(){
while(true){
sellerTicket();
}
}
// 抽取方法
private static snychorized void sellerTicket(){
if(ticket > 0){
System.out.println("余票充足,可出票。票号:" + ticket);
ticket--;
}
}
}
优点:同步的方式,可以解决线程安全的问题
缺点:操作同步代码时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低
3.Lock锁方式
public class SellerJerry implements Runnable{
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(true);// 入参true表示公平
@Overried
public void run(){
while(true){
try{
lock.lock();//获取锁
if(ticket > 0){
System.out.println("余票充足,可出票。票号:" + ticket);
ticket--;
}else{
break;
}
}finally(){
lock.unlock();//释放锁
}
}
}
}
五、线程之间的通信
- wait():执行此方法,当前线程进入阻塞状态,并且释放锁
- notify():执行此方法,会唤醒被wait的一个线程。多个线程被wait,唤醒优先级高的线程
- notifyAll():执行此方法,会唤醒所有被wait的线程
- 使用synchronized,这三个方法都只能在同步代码块里面使用,并且调用者为同步监视器