(1)死锁以及如何避免死锁
死锁:
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
例子:
小红和小明都想画画,小红有画板,小明有画笔,为了可以画画,他们必须同时拥有画板和画笔。
运行结果:
通过运行结果得知,他们两个同时进入的堵塞的状态
如何避免:
- 尽量不要使用锁的嵌套
- 不要使用锁,而使用安全类。java.util.concurrent
- 设置锁的超时时间,Lock锁,到达指定时间没有获取锁,则不会再等待该锁。
(2)线程通信
在Java中,线程之间可以通过wait()和notify()方法进行通信。wait()方法使当前线程进入等待状态,直到其他线程调用notify()方法来唤醒它。wait()方法必须在synchronized块中使用,因为它只有在获得对象的锁时才能被调用。
等待:
public final void wait()
public final void wait(long timeout)
必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其拥有的锁标记,并且进入等待队列,只有被notify()唤醒时才会进入就绪状态进行抢夺时间片。
唤醒:
public final void notify()
public final void notifyAll()
线程之间可以通过wait()和notify()方法进行通信。notify()方法用于随机唤醒一个等待中的线程,使其从wait()方法中返回,并开始竞争时间片。而notifyAll()方法用于唤醒所有等待中的线程,使其从wait()方法中返回。并开始竞争时间片
示例:
package communication;
/**
* 银行卡
*/
public class BankCard {
//余额
private double balance;
//是否够一次转账
private boolean flag;//true表示有钱,false表示没钱
//存
public synchronized void save(double money){
if(flag){
try {
//如果有钱就进入等待队列
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没钱就去增加钱
balance=money+balance;
System.out.println("往卡里存入了1000元,卡中余额"+balance+"元");
flag=true;
//唤醒等待队列的线程
notify();
}
//取
public synchronized void take(double money){
if(flag==false){
try {
//如果没钱就进入等待队列
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有钱就去花钱
balance=balance-money;
System.out.println("从卡中取出了1000元,卡中余额"+balance+"元");
flag=false;
//唤醒等待队列的线程
notify();
}
}
package communication;
/**
* 存钱
*/
public class Save implements Runnable{
//通信中间表
private BankCard bankCard;
public Save(BankCard bank){
bankCard=bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.save(1000);
}
}
}
package communication;
/**
* 取钱
*/
public class Take implements Runnable{
private BankCard bankCard;
public Take(BankCard bank){
bankCard=bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.take(1000);
}
}
}
package communication;
public class Test {
public static void main(String[] args) {
BankCard bankCard = new BankCard();
Save save = new Save(bankCard);
Take take = new Take(bankCard);
Thread save1 = new Thread(save);
Thread take2 = new Thread(take);
save1.start();
take2.start();
}
}
wait sleep方法的区别
//1. wait需要使用notify或notifyAll唤醒,而sleep到时间自动唤醒。
//2. wait来自于Object类中,sleep来自Thread类中
//3. wait会是否锁资源,sleep不会释放锁资源。
//4. wait必须放在同步代码中,而sleep可以放在任意位置。
(3)线程的状态
NEW : 新建状态。当new一个线程对象时进入该状态;
RUNNABLE:运行状态,调用完start并获取时间片进入运行状态
BLOCKED:堵塞状态。当没有获取锁资源时,进入堵塞状态
WAITING:等待状态,当执行了wait方法时,该线程进入等待状态
TIOMED_WAITING:限时等待状态,当执行 sleep方法时,进入该状态。
TERMINATED:终止状态,当线程执行完毕获取出现异常中断的时候,进入限时等待状态
以红字状态为准
(4)线程池
为什么要使用线程池?
线程是宝贵的内存资源,单个线程约占位1MB的空间,过多分配容易造成内存溢出。
频繁的创建以及销毁线程会增加虚拟机回收频率,资源开销,造成程序性能下降
线程池的优点
线程容器,可以设定线程分配的数量上限。 将预先创建的线程对象存入池中,并重用线程池中的线程对象,避免频繁的创建和销毁。
将任务交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程。
如何创建jdk自带的线程池
-
常用的线程池接口和类(所在包java.util.concurrent);
-
Executor :线程池的顶级接口,里面只有一个execute方法;void execute(Runnable command);,执行任务的方法
-
ExecutorService: 线程池接口,可通过submit(Runnable task)提交任务代码。他是Executor的子接口,里面包含了很多方法:
-
1.shutdown():关闭线程池---当前线程池如果还有任务,则要等任务完成后才会关闭。
-
2.shutdownNow():关闭线程池---这个是直接立刻关闭线程池,无论池子中有没有任务
-
3.isShutdown():查看线程池是否处于关闭状态
-
4.isTerminated():判断线程池是否已经终止。
-
5.submit(Callable task): 执行线程任务。
submit(Runnable task):
-
Executors工厂类: 通过此类可以获得一个线程池。
1--3都使用submit执行线程任务,4-使用schedule定时执行任务
线程池的种类:
1.获取固定长度的线程池:
Executors.newFixedThreadPool(5);
2.创建单一的线程池,池子中只有一个线程的对象,适合任务有序执行:
Executors.newSingleThreadExecutor();
3.可变线程池:
Executors.newCachedThreadPool();
4.延迟线程池:
Executors.newScheduledThreadPool(5);
//延迟线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 100; i++) {
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("10秒后执行");
}
},10,TimeUnit.SECONDS);
}
如何创建自定义线程池
线程池中参数的意思:
corePoolSize---->核心线程数。
maximumPoolSize---->最大线程数。
keepAliveTime---->等待时长。
unit ---->等待时长单位.
workQueue----> 等待队列对象类型为BlockQueue,想要设置有多少等待队列的数量,需要用到BlockQueue的实现类,在实现类中接入参数,然后传入自定义线程池。(设置可以处于等待状态的对象有几个)
//自定义线程池
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(5);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, arrayBlockingQueue);
for (int i = 0; i < 16; i++) {
int e = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"跑到了"+e);
}
});
}
实现Callable创建多线程---第三种
Callable和runnable的区别 callable任务体有返回值。
callable自动抛出异常
callable是runnable的补充
//Callable创建多线程
public class MyCallable implements Callable<Double> {
@Override
public Double call() throws Exception {
double e =0;
for (int i = 0; i < 100; i++) {
e += i;
}
return e;
}
}
//测试
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
//单一线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Double> submit = executorService.submit(myCallable);
System.out.println(submit.get());
}
}
(5)手动锁和自动锁的区别
1.synchronized可以给代码块和方法上锁,而lock只能给代码块加锁。
2.synchronized自动上锁和解锁,而lock需要手动上锁和解锁。
3.lock可以知道是否获取锁资源。
获取手动锁需要自己new
Lock lock=new ReentrantLock();
lock.lock();//上锁
lock.unlock();//释放锁
lock.tryLock(10, TimeUnit.SECONDS); //时间一到,自动解锁,释放锁资源
public class MyLock implements Runnable{
private int tickets = 100;
Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//上锁
try {
if (tickets > 0) {
tickets--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩票数" + tickets);
} else {
break;
}
}finally {
lock.unlock();//释放锁
}
}
}
}
public class MyLock implements Runnable{
private int tickets = 100;
Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.tryLock(10, TimeUnit.SECONDS); //时间一到,自动解锁,释放锁资源
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
tickets--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩票数" + tickets);
} else {
break;
}
}
}
}