多线程
1.创建线程
什么是线程?
线程就是程序内部当中的一条执行流程;
什么是多线程?线程的代表是谁?线程创建的第一种方式步骤是什么?
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行);
线程的代表是Thread类;
多线程创建方式一:
1.定义一个子类继承Thread类,重写run()方法;
2.创建子类对象代表子类的线程;
3.调用线程对象的start()方法启动线程;
优点是编码简单;缺点是由于只能单继承的限制,不能继承其他类,不利于功能的扩展;
注意事项:
1.启动线程必须调用start()方法,而不是run()方法,直接调用run()方法会被当成普通程序执行,此时相当于没有创建一个新的线程;只有调用start()才是启动一个新的线程执行程序;
2.不要把主线程任务放在启动子线程之前,会默认执行完主线程;
多线程创建方式二:实现Runnable()接口
1.定义一个线程任务类实现Runnable()接口,重写run()方法;
2.创建一个线程任务类对象;
3.将线程任务类对象交给Thread处理,封装成为线程对象;
4.调用线程对象的start()方法启动线程;
优点是任务类只是实现接口,可以继续继承其他类,实现其他接口,扩展性强;缺点是需要多一个Runnable对象;
多线程创建方式三:利用Callable()接口,FutureTask类来实现
1.创建任务对象,定义一个类实现Callable()接口,重写call(),封装要做的事情,和要返回的数据;把Callable类型的对象封装成FutureTask(线程任务对象);
2.把线程任务对象交给Thread对象;
3.调用Thread对象的start()方法启动线程;
4.线程执行完毕之后,通过FutureTask对象的get()方法去获取线程任务执行的结果;
优点是线程任务类只是实现接口,可以继续继承其他类,实现其他接口,扩展性强,还可以在线程执行完毕之后获取线程任务执行的结果;缺点是编码比较复杂;
2.线程安全
什么是线程安全问题?出现线程安全的原因?
多个线程,同时操作同一个共享资源的时候,可能出现业务安全问题;
存在多个线程同时执行,同时访问一个共享资源,存在对共享资源进行修改的操作;
2.1 线程同步
作用:线程同步是线程安全的解决方案;
思想:让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题;
常见方案:加锁;
什么是加锁? 每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能加锁进来;
2.2 加锁的常见方式
Test代码:
package synchronizedDemo1;
public class Test {
public static void main(String[] args) {
//目标:模拟线程安全问题
//小红小明同时去同一个账号取钱
//创建一个账户类,用来存放100000元
Account acc = new Account("GYID-123456",100000);
//创建小明,小红两个线程,并启动线程
Thread xm = new DrawThread("小明",acc);
xm.start();
Thread xh = new DrawThread("小红",acc);
xh.start();
}
}
账户Account代码:
package Thread;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String AccCard;
private double AccMoney;
public void DrawMoney(double Money) {
String name = Thread.currentThread().getName() ;
if (AccMoney >= Money) {
System.out.println(name + "取钱"+ Money +"成功" );
AccMoney -= Money;
System.out.println(name + "余额为:" + AccMoney);
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
package Thread;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String AccCard;
private double AccMoney;
public void DrawMoney(double Money) {
String name = Thread.currentThread().getName() ;
if (AccMoney >= Money) {
System.out.println(name + "取钱"+ Money +"成功" );
AccMoney -= Money;
System.out.println(name + "余额为:" + AccMoney);
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
1.同步代码块
作用:把访问共享资源的核心代码块上锁,以保证线程安全;
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行;
同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug;
锁对象的使用规范:建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象;对于静态方法,建议使用字节码(类名.class)对象作为锁对象;
package synchronizedDemo1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String AccCard;
private double AccMoney;
public void DrawMoney(double Money) {
String name = Thread.currentThread().getName() ;
synchronized (this) {
if (AccMoney >= Money) {
System.out.println(name + "取钱"+ Money +"成功" );
AccMoney -= Money;
System.out.println(name + "余额为:" + AccMoney);
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
}
2.同步方法
作用:把访问共享资源的核心方法给上锁,以保证线程安全;
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行;
底层原理:同步方法底层也是有隐式锁对象的,只是锁的范围是整个代码,实例方法默认使用this作为锁对象,静态方法默认使用字节码(类名.class)对象作为锁对象;
测试用例:
package synchronizedDemo2;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String AccCard;
private double AccMoney;
public synchronized void DrawMoney(double Money) {
String name = Thread.currentThread().getName() ;
if (AccMoney >= Money) {
System.out.println(name + "取钱"+ Money +"成功" );
AccMoney -= Money;
System.out.println(name + "余额为:" + AccMoney);
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
3.Lock锁
Lock锁是JDK5开始提供的一种新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活,更强大,更方便;Lock是接口,不能直接实例化,可以采用他的实现类ReentrantLock来构建Lock锁对象;
测试用例:
package synchronizedDemo3;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String AccCard;
private double AccMoney;
private final Lock lk= new ReentrantLock();
public void DrawMoney(double Money) {
String name = Thread.currentThread().getName() ;
lk.lock();
try {
if (AccMoney >= Money) {
System.out.println(name + "取钱"+ Money +"成功" );
AccMoney -= Money;
System.out.println(name + "余额为:" + AccMoney);
} else {
System.out.println(name + "取钱失败,余额不足");
}
} finally {
lk.unlock();
}
}
}
建议将锁对象加上final修饰,避免呗篡改;建议将释放锁的操作放到finally代码块中,确保锁用完了一定会被释放;
3.线程池
线程池就是一个可以复用线程的技术;
3.1 创建线程池
JDK5.0起提供了代表线程池的接口:ExecutorService;
方式一:使用ExecutorService的实现类ThreadPoolExecutor自己创建一个线程池对象;
方式二:使用Executors(线程池的工具类)调用方法返回不同的线程池对象;
3.2 线程池的注意事项
什么时候开始创建临时线程?
新任务提交时发现核心任务都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程;
什么时候会拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会拒绝任务;
3.3 通过Executors工具类创建线程池
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象;
3.4 并发/并行
进程:正在运行的程序或者软件就是一个进程;线程是属于进程的,一个进程中可以同时运行多个线程;进程中的多个线程其实是并发或者并行执行的;
并发:进程中的线程是由CPU负责调度执行的,但CPU能同时处理的线程数量有限,为了保证全部线程都能往前执行,CPU会轮询位系统的每个线程服务,由于CPU切换的速度很快,给我们感觉这些线程都在同时执行,这就是并发;
并行:在同一个时刻上,同时有多个线程在被CPU调度执行;