如何解决线程安全问题
当多个线程共享一个资源时,则可能出现线程安全问题。 java中解决线程安全的方式有三种
第一种: 同步代码块
第二种: 同步方法
第三种: Lock
同步代码块
synchronized(锁对象){
需要同步的代码。
}
synchronized 同步的意思
锁对象可以是任何引用类型的java对象,只要是一个java对象就可以当做锁对象
锁对象一旦被确定下来要保证唯一性, 锁对象只能有一份
public class SellTicket implements Runnable {
private int tickets = 100;//票数
private StringBuffer sb = new StringBuffer();
@Override
public void run() {
while (tickets > 0) {
synchronized (this) { //this:表示当前类对象。因为外界就创建了一个该类对象
// synchronized (sb) { //sb表示StringBuffer对象,StringBuffer可变。
// synchronized (SellTicket.class) { //获取当前类的反射类 也可以
if (tickets>0) { //判断票数是否大于0
System.out.println(Thread.currentThread().getName() + "卖了一张票;剩余:" + --tickets + "张");
}
}
// }
}
}
}
同步方法
修饰符 synchronized 返回值类型 方法名 ( 方法参数 ) {
方法体;
}同步方法默认的锁对象就是:this
什么时候使用同步成员方法?
如果你发现你写的一个成员方法 , 这个方法内部所有的代码都使用同步代码块给包裹了 , 并且使用的锁对象是this的时候 . 就可以使用同步成员方法简化这个方法。
public class SellTicket2 implements Runnable {
private int tickets = 100;//票数
private StringBuffer sb = new StringBuffer();
@Override
public void run() {
while (tickets > 0) {
sell();
}
}
//同步方法
private synchronized void sell(){
if (tickets>0) { //判断票数是否大于0
System.out.println(Thread.currentThread().getName() + "卖了一张票;剩余:" + --tickets + "张");
}
}
}
Lock锁
手动锁,需要手动加锁和释放锁。实现类 ReentrantLock . 早期Lock锁效率高于synchronized,但是jdk1.6后进行synchronized优化,二者相差不大,主要看个人喜好
public class SellTicket implements Runnable {
private int tickets = 100;//票数
private Lock l=new ReentrantLock();//创建锁对象
@Override
public void run() {
while (tickets > 0) {
try {
l.lock();//上锁
if(tickets>0) {
System.out.println(Thread.currentThread().getName() + "卖了一张票;剩余:" + --tickets + "张");
}
} finally {
l.unlock();//释放锁--建议释放锁放在finally中
}
}
}
}
死锁
线程死锁是指由于两个或者多个线程相互持有对方所需要的锁资源,导致这些线程处于等待状态,无法继续执行。
例子: 画画 【画笔,画板】 线程A拥有画笔 需要画板 线程B拥有画板,需要画笔
package org.example.demo09;
public class Test01 {
private static String a="画笔";
private static String b="画板";
public static void main(String[] args) {
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
synchronized (a){
System.out.println("线程A拥有画笔");
synchronized (b){
System.out.println("线程A拥有画板");
System.out.println("线程A可以画画");
}
}
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
synchronized (b){
System.out.println("线程B拥有了画板");
synchronized (a){
System.out.println("线程B拥有了画笔");
System.out.println("线程B可以画画");
}
}
}
});
t1.start();
t2.start();
}
}
如何避免死锁
1. 使用tryLock设置获取时间。
2. 可以使用juc包下的类。--线程安全类
3. 尽量减少同步代码块的嵌套。
线程的通信
package org.example.demo12;
public class BankCard {
private double balance;//余额
private boolean flag = false;//表示卡中是否有钱 false表示没钱 true 表示有钱
//存
public synchronized void deposit(double amt) {
if (flag == false) {
balance += amt;
System.out.println(Thread.currentThread().getName() + "存款" + amt + "元,余额为:" + balance);
notify();//唤醒正在等待的线程
}
flag = true;
try {
wait();//需要进入等待队列
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//取
public synchronized void takeMoney(double amt) {
if (flag == true) {
balance -= amt;
System.out.println(Thread.currentThread().getName() + "取款" + amt + "元,余额为:" + balance);
notify();
}
flag = false;
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
package org.example.demo12;
public class SaveTask implements Runnable{
private BankCard card;
public SaveTask(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
card.deposit(10000);
}
}
}
package org.example.demo12;
public class TakeTask implements Runnable{
private BankCard card;
public TakeTask(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
card.takeMoney(10000);
}
}
}
public class Test {
public static void main(String[] args) {
//创建一个银行卡对象
BankCard card=new BankCard();
//创建两个任务对象
SaveTask task1=new SaveTask(card);
TakeTask task2=new TakeTask(card);
//创建两个线程对象
Thread t1=new Thread(task1,"李晨");
Thread t2=new Thread(task2,"范冰冰");
t1.start();
t2.start();
}
}
wait和sleep方法的区别。
wait属于Object类中的方法 而sleep属于Thread类的方法。
wait必须放在同步代码块中,sleep可以在任何位置使用。
wait会释放锁资源,而sleep不会释放锁资源。
wait需要通过notify|notifyAll唤醒,而sleep时间到了自动唤醒。
线程的状态
线程生命周期,线程对象从生到死一个过程,当线程被创建并启动以后,他既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。
NEW: 新建状态.
RUNNABLE:运行状态。【就绪和运行】
BLOCKED:堵塞状态
WAITING: 等待状态
TIME_WAITING: 有时间的等待
TERMINSTED:终止
线程池
什么是线程池
提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子 ( 容器 ) ,在该池子中存储很多个线程。一个装着多条线程对象的容器就是线程池 。
为什么使用线程池
1、提高性能:线程的创建和销毁需要消耗一定的系统资源,使用线程池可以重复利用已经创建好的线程,避免频繁地创建和销毁线程,从而提高程序的性能。
2、提高响应速度:当任务到达时,如果线程池中有空闲的线程,则可以立即执行任务,提高程序的响应速度。
3、控制并发线程数:线程池可以控制同时运行的线程数,避免由于过多的线程导致系统资源占用过多或者系统崩溃等问题。
创建线程池的核心参数
线程池的工作原理
创建线程池的方式
ThreadPoolExecutor [推荐]
package org.example.demo13;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test02 {
public static void main(String[] args) {
/**
* int corePoolSize,核心线程数
* int maximumPoolSize,最大线程数
* long keepAliveTime,非核心线程的存活时间
* TimeUnit unit 时间单位
* BlockingQueue<Runnable> workQueue: 等待队列
*/
BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(5);
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(2,5,20, TimeUnit.SECONDS,workQueue);
//15 10 10 20 15
for (int i = 0; i < 11; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了任务");
}
});
}
}
}
线程池大小如何设定.
最简回答:生产环境中,Java线程池大小的设定与硬件资源和并发需求密切相关。通常可以考虑CPU核心数、内存容量、网络带宽等硬件资源,并结合预估的并发请求量来确定线程池大小,以充分利用资源并保持合理的并发处理能力。较多的硬件资源和高并发通常需要更大的线程池来提高并发处理效率。
Executors工具【不推荐】
package org.example.demo13;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
public static void main(String[] args) {
//1.采用Executors工具类创建线程池
// ExecutorService executorService = Executors.newFixedThreadPool(5);//创建一个固定的线程池.
// ExecutorService executorService = Executors.newSingleThreadExecutor();//创建单一的线程池。保证任务的有序执行。
ExecutorService executorService = Executors.newCachedThreadPool(); //创建一个变的线程池。
//ExecutorService executorService = Executors.newCachedThreadPool();//创建一个缓存的线程池。
//2.通过线程池对象执行任务.submit()可以执行Runnable类型的任务也可以执行Callable类型的任务 execute()方法只能执行Runnable类型的任务.
for (int i = 0; i < 300000; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了任务");
}
});
}
}
}