一、死锁
1.概述
- 死锁 : 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法往下执行。
- 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
- 原理 :
-
- 某个线程执行完成,需要先后嵌套锁定两个对象,在这个过程中,先锁定了第一个对象
- 另一个线程执行完成也需要先后嵌套锁定两个对象,在这个过程中,先锁定了第二个对象
- 第一个线程执行中,要执行到第二个对象的时候,发现第二个对象被锁定,进入等待状态,等待交出锁
- 第二个线程执行中,要执行到第一个对象的时候,发现第一个对象也被锁定,也进入等待状态
- 此时两个线程都在等待对方交出锁,导致死锁
2.代码实现
public class Thread_01_DeadLock {public static void main(String[] args) {Object o1=new Object();Object o2=new Object();Thread t1=new Thread(new T1(o1, o2));Thread t2=new Thread(new T2(o1, o2));t1.start();t2.start();}
}
class T1 implements Runnable{Object o1;Object o2;public T1(Object o1,Object o2){this.o1=o1;this.o2=o2;}@Overridepublic void run() {synchronized (o1) {
System.out.println(Thread.currentThread().getName()+"-->T1o1已锁定");synchronized (o2) {System.out.println(Thread.currentThread().getName()+"-->T1o2已锁定");}}System.out.println("t1执行完成");}
}
class T2 implements Runnable{Object o1;Object o2;public T2(Object o1,Object o2){this.o1=o1;this.o2=o2;}@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2) {System.out.println(Thread.currentThread().getName()+"-->T2o2已锁定");synchronized (o1) {System.out.println(Thread.currentThread().getName()+"-->T2o1已锁定");}}System.out.println("t2执行完成");}
}
二、线程通信
1.概述
- Object中的方法
- wait : 让当前线程进入等待状态(挂起),并释放锁,当被唤醒之后,接着挂起的位置继续执行,假如之前执行了1、2,到3挂起,那么被唤醒后接着执行3
- notify : 唤醒一个在该对象中挂起的任意一个线程
- notifyAll : 唤醒在该对象中挂起的所有线程
- 这几个方法必须出现在加锁的成员方法中
- wait : 如果是无参,则不会自动醒,也可以传入long类型的值,代表毫秒数,多久之后自动醒
- wait 和 sleep的区别 :
-
- sleep : 让当前线程进入睡眠状态, 是静态方法,和是否加锁没有关系,如果在加锁的方法中,也不会释放锁
- wait : 让当前线程进入挂起等待状态,必须在加锁的成员方法中,另外会释放锁
2.使用方式
public class Thread_03_Wait {public static void main(String[] args) throws InterruptedException {Num num=new Num();Thread t1=new PrintNum(num);Thread t2=new PrintNum(num);t1.start();Thread.sleep(10);t2.start();}
}
class PrintNum extends Thread{Num num;public PrintNum(Num num){this.num=num;}@Overridepublic void run() {while (true) {num.printNums();}}
}
class Num{private int count =1;public synchronized void printNums(){System.out.println(Thread.currentThread().getName()+"-->"+count);count++;this.notifyAll();try {Thread.sleep(1000);this.wait();} catch (InterruptedException e) {e.printStackTrace();}}
}
3.生产者消费者
3.1.示例
public class Thread_04_Producer {public static void main(String[] args) {SynStack ss=new SynStack();Thread producer1=new Thread(new Producer(ss));Thread producer2=new Thread(new Producer(ss));Thread consumer1=new Thread(new Consumer(ss));Thread consumer2=new Thread(new Consumer(ss));producer1.start();producer2.start();consumer1.start();consumer2.start();}
}
class Producer implements Runnable{private SynStack ss;public Producer(SynStack ss){this.ss=ss;}@Overridepublic void run() {for (int i = 0; i < 26; i++) {ss.push((char)('a'+i));}}
}
class Consumer implements Runnable{private SynStack ss;public Consumer(SynStack ss){this.ss=ss;}@Overridepublic void run() {for (int i = 0; i < 26; i++) {ss.pop();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}
}
class SynStack{int count=0;char[] data=new char[6];public synchronized void push(char ch){while(count ==data.length){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}if (count==0) {this.notifyAll();}data[count++]=ch;System.out.println(Thread.currentThread().getName()+"生产了 "+ch+" 还剩 "+count+" 个货物");}public synchronized char pop(){while(count ==0){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}if (count==data.length) {this.notifyAll();}char ch=data[--count];System.out.println(Thread.currentThread().getName()+"消费了 "+ch+" 还剩 "+count+" 个货物");return ch;}
}
三、单例模式
public class SingLeton {private SingLeton(){}private volatile static SingLeton singLeton;public static SingLeton getInstance(){if (singLeton==null) {synchronized (SingLeton.class) {if (singLeton==null) {singLeton=new SingLeton();} }}return singLeton;}
}
四、线程池
- 线程池的作用:
-
- 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;
- 少了浪费了系统资源,多了造成系统拥挤效率不高。
- 用线程池控制线程数量,其他线程排队等候。
- 一个任务执行完毕,再从队列的中取最前面的任务开始执行。
- 若队列中没有等待进程,线程池的这一资源处于等待。
- 当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了,否则进入等待队列。
- 为什么要用线程池:
-
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)