目录
1. 守护线程
守护线程(兜底线程)
每个程序运行时,都会有一个守护线程同步启动,用于监听我们的正常程序
当主线程执行完之后,守护线程也就没有存在的价值了,因为没有工作可做,此时,JVM就会关机.守护线程终止
我们可以通过 线程对象.setDaemon(true) 来把某个线程设置为守护线程(必须在启动之前设置)
public class Thread_01_demon {
public static void main(String[] args) {
Thread t = new Thread(new DemonThread());
t.setDaemon(true);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("main "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class DemonThread implements Runnable {
@Override
public void run() {
int i = 0;
while (true) {
System.out.println("t1" + ++i);
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2. 死锁
2.1 概述
死锁 : 死锁就是在程序执行过程中,都遇到了对方进入加锁的方法中,导致大家都访问不了
原理 :
1 某个线程执行完成,需要 先后 嵌套 锁定 执行两个对象,同时 在这个过程中,先锁定第一个对象,再锁定第二个对象
2 另外一个线程执行完成,需要 先后 嵌套 锁定 执行两个对象,同时在这个过程中,先锁定第二个对象,再锁定第一个对象
3 在第一个线程,锁定第一个对象后,要去锁定第二个对象时,发现第二个对象已经被锁定,只能等待
4 第二个线程,锁定第二个对象后要去锁定第一个对象时,发现第一个对象已经被锁定,只能等待
2.2 代码实现
public class Thread_02_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 T1(o1, o2));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class T1 implements Runnable {
Object o1;
Object o2;
public T1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "锁定o1");
try {
//睡眠让死锁肯定发生
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "锁定o2");
}
}
}
}
class T2 implements Runnable {
Object o1;
Object o2;
public T2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "锁定o2");
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "锁定o1");
}
}
}
}
此程序中t1线程中锁定o1, t2线程中锁定o2, 就会导致t1或者t2线程再运行时被对方死锁
3. 线程通信
3.1 概述
Object中的方法 :
wait() : 让该线程进入等待状态(挂起状态),当被唤醒后,进入就绪状态,然后呢再次执行时,紧接着之前挂起的地方继续执行
无参 或 传入参数0 都表示不会自动唤醒,只能被叫醒(notify,notifyAll)
也可以传入long类型的值,代表毫秒数,到指定毫秒数之后,自动唤醒
wait和sleep的区别 : sleep 不会交出锁,依然占用锁,其他线程无法进入,wait会交出锁,其他线程可以进去
notify() : 随机唤醒一个在该对象上等待的一个条线程,让别的线程去执行
notifyAll() : 唤醒所有在该对象上等待的线程
以上方法 必须用在成员方法中且,该方法必须加锁(synchronized)
3.2 使用方式
需求 交替打印奇数和偶数
思路 : 1 有个业务类,并提供打印奇数和打印偶数的方法
2 两个线程,分别去调用打印奇数和打印偶数
public class Thread_03_wait {
public static void main(String[] args) {
Num num = new Num();
Thread t1 = new PrintOdd(num);
Thread t2 = new PrintEven(num);
t1.setName("odd");
t2.setName("even");
t1.start();
try {
// 加入睡眠,保存让奇数线程先执行
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
// 打印奇数的线程
class PrintOdd extends Thread{
private Num num;
public PrintOdd(Num num) {
this.num=num;
}
@Override
public void run() {
while (true) {
num.printOdd();
}
}
}
//打印偶数的线程
class PrintEven extends Thread{
private Num num;
public PrintEven(Num num) {
this.num=num;
}
@Override
public void run() {
while (true) {
num.printEven();
}
}
}
// 业务类
class Num {
private int count =1 ;
// 打印奇数的方法
public synchronized void printOdd(){
System.out.println(Thread.currentThread().getName()+"-->"+count);
count++;
// 唤醒所有等待的线程
this.notifyAll();
try {
Thread.sleep(1000);
// 让当前线程进入等待状态,会交出锁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印偶数的方法
public synchronized void printEven(){
System.out.println(Thread.currentThread().getName()+"-->"+count);
count++;
// 唤醒所有等待的线程
this.notifyAll();
try {
Thread.sleep(1000);
// 让当前线程进入等待状态,会交出锁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3 生产者和消费者
消息队列:生产者/消费者模式_生产者消费者队列-CSDN博客
降低耦合度
/**
* 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,
* 所以生产者生产完数据之后不用等待消费者处理
* ,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
*
* 优点 : 解耦
*/
public class Thread_04_Produce {
public static void main(String[] args) {
SynStack ss = new SynStack();
Thread t1 = new Producer(ss);
Thread t2 = new Consumer(ss);
t1.start();
t2.start();
}
}
//生产者线程
class Producer extends Thread{
private SynStack ss;
public Producer(SynStack ss){
this.ss=ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
ss.push((char)('a'+i));
}
}
}
// 消费者线程
class Consumer extends Thread{
private SynStack ss;
public Consumer(SynStack ss){
this.ss=ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
ss.pop();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 业务类
class SynStack {
// 缓冲区
char[] data = new char[6];
// 保存个数
int count = 0;
// 生产者 : 库存满了就停止生产
public synchronized void push(char ch){
// 判断库存是否满了
if (count >= data.length) {
try {
// 如果满了则进入等待并交出锁
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 当库存为0时,说明消费者已经进入等待状态,那么就唤醒它
if (count == 0) {
this.notifyAll();
}
// 生产数据保存到库存
data[count] = ch;
count++;
System.out.println("生产了"+ch+",剩余"+count+"件商品");
}
// 消费者 : 没有库存了就停止消费
public synchronized void pop(){
// 判断是否还有库存
if (count == 0) {
try {
// 如果没有库存则进入等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判断生产者是否进入等待
if (count == 6) {
this.notifyAll();
}
count--;
char ch = data[count];
System.out.println("消费了"+ch+",剩余"+count+"件商品");
}
}
4. 单例模式
https://www.cnblogs.com/dolphin0520/p/3920373.html
懒汉模式 在多线程环境下可能出现问题,所以一般采用双重校验+代码块锁机制来解决处理
volatile : 防止指令重排
public class Singleton_03 {
private Singleton_03() {
}
private volatile static Singleton_03 s = null;
//一重判断可能导致多次线程不同时间进入方法中,第一个线程创建新对象时,第二个线程也进行判断从而创建新对象
//导致出现问题
public static Singleton_03 getInstance() {
if (s == null) {
synchronized (Singleton_03.class) {
if (s == null) {
s = new Singleton_03();
}
}
}
return s;
}
}
主程序
public class Test {
public static void main(String[] args) {
new A().start();
new A().start();
new A().start();
new A().start();
new A().start();
new A().start();
new A().start();
new A().start();
}
}
class A extends Thread {
@Override
public void run() {
System.out.println(Singleton_03.getInstance());
}
}