目录
1、死锁以及如何避免死锁
死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
案例:A和B同时使用画板和画笔这两把锁,如果使用锁嵌套就会造成死锁问题。
如何避免:
1、尽量不要使用锁嵌套
2、不要使用锁,而是使用安全类。java.util.concurrent下的类
3、设置锁的超时事件。Lock锁。到达指定时间没有获取锁,则不会再等待该锁。
2、线程通信
2.1 等待:
- public final void wati()
- public final void wati(long timeout)
- 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
2.2 通知:
- public final void notify() ---唤醒队列中的其中一个,随机的。
- public final void notifyAll() ----唤醒队列中的所有等待线程。
案例:银行卡一存一取的实现
public class BankCard {
private double blance;
private boolean flag;//true代表有钱,false代表没钱
public synchronized void save(double money){
if (flag){//有钱进入等待队列
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有钱就让余额加上存的钱
blance=blance+money;
System.out.println(Thread.currentThread().getName()+"存了"+money);
flag=true;
//唤醒等待队列中的线程
notify();
}
public synchronized void take(double money){
if (flag==false){//没钱进入等待队列
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有钱就让余额减去取出的钱
blance=blance-money;
System.out.println(Thread.currentThread().getName()+"取了"+money);
flag=false;
//唤醒等待队列中的线程
notify();
}
//线程一
public class SaveRunnable implements Runnable{
private BankCard bankCard;
public SaveRunnable(BankCard card){
bankCard=card;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
bankCard.save(1000);
}
}
}
//线程二
public class TakeRunnable implements Runnable{
private BankCard bankCard;
public TakeRunnable(BankCard card){
bankCard=card;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
bankCard.take(1000);
}
}
}
//测试
public class Test {
public static void main(String[] args) {
BankCard bankCard = new BankCard();
SaveRunnable save = new SaveRunnable(bankCard);
TakeRunnable take = new TakeRunnable(bankCard);
//创建线程
Thread thread1 = new Thread(save,"A");
Thread thread2 = new Thread(take,"B");
//开启线程
thread1.start();
thread2.start();
}
}
2.3 sleep和wait的区别
1、wait需要使用notify或notifyAll唤醒,而sleep到时间自动唤醒。
2、wait来自于Object类中,sleep来自Thread类中。
3、wait会释放锁资源,sleep不会释放锁资源
4、wait必须放在同步代码块中,而sleep可以放在任意位置。
2.4 线程的状态
- NEW:新建状态。当new一个线程对象进入该状态。
- RUNNABLE:运行状态。调用完start并获取cpu时间片进入运行状态。
- BLOCKED:堵塞状态。当没有获取锁资源时。
- WAITING:当执行了wait方法时,该线程进入等待状态。
- TIMED_WAITING:当执行sleep方法时,进入该状态。
- TERMINATED:终止状态。当线程执行完毕或出现异常中断。
3、线程池
3.1 什么是线程池
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。
3.2 问题
- 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出。
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销、造成性能下降。
3.3 线程池的优点:
- 线程容器,可设定线程分配的数量上限。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
- 避免频繁的创建和销毁。
3.4 线程池的原理
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。
3.5 创建线程池
常用的线程池接口和类(所在包java.util.concurrent):
- Executor:线程池的顶级接口。
- ExecutorService:线程池接口,可通过submit(Runnable task)、 execute(Runnable command)提交任务代码。
- Executors工厂类:通过此类可以获得一个线程池。
- 通过newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
- 通过newSingleThreadExecutor();//创建单一线程池,池子中只有一个线程对象。---适合任务的有序执行。
- 通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限。
———————————————————————————————————————————
Executor:它是线程池的顶级接口。该接口中就存在一个方法。void execute(Runnable command);执行线程任务的方法。
ExecutorService:它是Executor接口中的子接口。
- shutdown():关闭线程池---当前线程池要还有任务,需要等待任务完成后才会关闭。
- shutdownNow():立刻关闭线程池,不管是否还有任务未完成。
- isShutdown():是否属于关闭状态。
- isTerminated():判断是否线程池终止了。
- submit(Callable<T> task):执行线程任务。
submit(Runnable task)
Executors:它是线程池的工厂类,该类可以获取线程池对象。
(1)通过newFixedThreadPool(int nThreads)获取固定数量的线程池。
public class Test {
public static void main(String[] args) {
//获取固定长度的线程池,----阿里巴巴不建议使用Executors创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 3; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "~~~~~~");
}
});
}
/* executorService.shutdownNow();*/
}
}
(2)通过newSingleThreadExecutor() ------创建单一线程池,池子中只有一个线程对象。---适合任务的有序执行。
public class Test {
public static void main(String[] args) {
//创建单一线程池,池子中只有一个线程对象。---适合任务的有序执行。
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "~~~~~~");
}
});
}
/* executorService.shutdownNow();*/
}
}
(3)通过newCachedThreadPool()------可变线程池
public class Test {
public static void main(String[] args) {
//可变线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "~~~~~~");
}
});
}
/* executorService.shutdownNow();*/
}
}
(4)通过newScheduledThreadPool()------延迟线程池
public class Test {
public static void main(String[] args) {
//延迟线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 2; i++) {
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("定时执行任务");
}
},10, TimeUnit.SECONDS);
}
/* executorService.shutdownNow();*/
}
}
(5)通过ExecutorService接口中的ThreadPoolExecutor实现类创建线程池------推荐使用
//建议使用自己new线程池 ----(1)没有无参构造函数 (2)构造函数私有化 // int corePoolSize, 核心线程数 // int maximumPoolSize,最大线程数 // long keepAliveTime,等待时间长 // TimeUnit unit,实践单位 // BlockingQueue<Runnable> workQueue 等待队列
public class Test2 {
public static void main(String[] args) {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
//建议使用自己new线程池 ----(1)没有无参构造函数 (2)构造函数私有化
// int corePoolSize, 核心线程数
// int maximumPoolSize,最大线程数
// long keepAliveTime,等待时间长
// TimeUnit unit,实践单位
// BlockingQueue<Runnable> workQueue 等待队列
ExecutorService executorService = new ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,blockingQueue);
for (int i = 0; i < 8; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~");
}
});
}
}
}
3.6 实现Callable接口重写run方法
线程池中submit()和execute()方法有什么区别
- execute():只能执行Runnable类型的任务
- submit():可以执行Runnable和Callable类型的任务
- Callable类型的任务可以获取执行的返回值,而Runnable执行无返回值。
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
My my = new My();
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Double> submit = executorService.submit(my);
System.out.println(submit.get());
}
}
class My implements Callable<Double>{
@Override
public Double call() throws Exception {
double sum = 0;
for (int i = 1; i <=100; i++) {
sum+=i;
}
return sum;
}
}
3.7 Lock锁
synchronized和Lock有什么区别:
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果没有使用 unLock() 去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。---tryLock()
public class TickteTask implements Runnable{
//定义100张票
private Integer tickte=100;
//定义lock锁
private Lock l=new ReentrantLock();
@Override
public void run() {//使用lock、锁
//需要用到死循环
while (true){
l.lock();//上锁
try {
if(tickte>0){
tickte--;
System.out.println(Thread.currentThread().getName()+"卖出去了一张票,还剩:"+tickte+"张");
}else {
break;
}
}finally {
l.unlock();//解锁
}
}
}
/*@Override
public void run() {
//需要用到死循环
while (true){
synchronized (this){//锁对象就是this
if(tickte>0){
tickte--;
System.out.println(Thread.currentThread().getName()+"卖出去了一张票,还剩:"+tickte+"张");
}else {
break;
}
}
}
}*/
}
4、总结
- 死锁--什么是死锁?----避免死锁?
- 线程通信---wait ---notify
- 线程状态----NEW RUNNABLE WAITING TIMED_WAITING TERMINATED
- 线程池:---有点。如何创建线程池
- 实现线程的第三种方式 ---Callable
- Lock 手动锁
如有不足之处请指出, 转载请注明出处, 谢谢~!