1.死锁
1.1概述
/*
package Thread; /* * 死锁:就是在执行过程中,都遇到了对方进入加锁的方法中,从而导致大家都访问不了的状态 * * 原理: * 1.某一线程 执行完成 需要 先后 嵌套 锁定 执行两个对象,并且在这个过程中,先锁定第一个对象 * 2.另一个线程 执行完成 需要 先后 嵌套 锁定 执行两个对象,并且在这个过程中,先锁定第二个对象 * 3.在第一个线程执行到第二个对象的时候,发现已经被锁定,只能等待 * 4.在第二个线程执行到第一个对象的时候,发现已经被锁定,只能等待 */ public class Thread_01_DeadLock { public static void main(String[]args){ Object o1 = new Object(); Object o2 = new Object(); Thread t1 = new Thread(new Thread_01(o1,o2)); Thread t2 = new Thread(new Thread_02(o1,o2)); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } } class Thread_01 implements Runnable{ Object o1; Object o2; public Thread_01(Object o1,Object o2){ this.o1 =o1; this.o2 = o2; } @Override public void run(){ synchronized(o1){ try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } synchronized (o2){ System.out.println(Thread.currentThread().getName()+"执行完成"); } } } } class Thread_02 implements Runnable{ Object o1; Object o2; public Thread_02(Object o1,Object o2){ this.o1 = o1; this.o2 = o2; } @Override public void run(){ synchronized(o2){ try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(o1){ System.out.println(Thread.currentThread().getName()+"执行完成"); } } } }
2.线程通信
2.1概述
/*
wait:让该线程进入等待状态,会释放持有的锁
无参或者传入0 表示一直等待,不会自动唤醒,只能等着notify唤醒它
也可以传入long类型的值,类型于sleep,时间到了自己唤醒
notify:随机唤醒一个该对象中正在等待的一个线程
notifyAll:唤醒在该对象中所有等待的线程
以上方法只能用在加锁的成员方法中,
2.2使用方式
需求 : 打印奇数和偶数
1 有一个业务类,有一个打印奇数和打印偶数的方法
2 有一个变量 count 记录当前的数字
3 两个线程,分别调用打印奇数和打印偶数的方法
*/
package Thread; /* * wait:让该线程进入等待状态,会释放持有的锁 * 无参或者传入0 表示一直等待,不会自动唤醒,只能等着notify唤醒它 * 也可以传入long类型的值,类型于sleep,时间到了自己唤醒 * * notify:随机唤醒一个该对象中正在等待的一个线程 * * notifyAll:唤醒在该对象中所有等待的线程 * * 以上方法只能用在加锁的成员方法中, * * 需求 : 打印奇数和偶数 * 1 有一个业务类,有一个打印奇数和打印偶数的方法 * 2 有一个变量 count 记录当前的数字 * 3 两个线程,分别调用打印奇数和打印偶数的方法 */ public class Thread_02_wait { public static void main(String[]args){ Num num = new Num(); Thread t1 = new PrintEven(num); Thread t2 = new PrintOdd(num); t1.setName("t1"); t2.setName("t2"); t2.start(); t1.start(); } } //打印偶数 class PrintEven extends Thread{ Num num; public PrintEven(Num num){ this.num =num; } @Override public void run(){ while(true){ num.printEven(); } } } //打印偶数 class PrintOdd extends Thread{ Num num; public PrintOdd(Num num){ this.num =num; } @Override public void run(){ while(true){ num.printEven(); } } } class Num{ int count = 1; public synchronized void printOdd(){ System.out.println(Thread.currentThread().getName()+"--->"+count); count++; //唤醒其他线程,去打印偶数 this.notifyAll(); //进入等待 try{ Thread.sleep(2000); this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } public synchronized void printEven(){ System.out.println(Thread.currentThread().getName()+"--->"+count); count++; //唤醒其他线程,去打印偶数 this.notifyAll(); //进入等待 try{ Thread.sleep(2000); this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } }
2.3生产者和消费者
https://blog.csdn.net/qq_39575279/article/details/87940298
百度
降低耦合度
package Thread; import java.util.Random; /** * 类似于打印奇数和偶数一样 , 使用 wait和notifyAll * * 1 一个业务类 SynStack 其中有一个变量,用来保存已生产的元素个数 * 2 业务类中有一个char数组,用于保存生产的元素(假如只生产 a-z这些字母) * 3 业务类中需要有两个方法,一个是生产 push , 一个消费 pop * push方法 主要用于向数组中添加数据 * 个数要+1 , 还要判断是否添加满了,满了就挂起进入等待 * pop 方法 主要用于取出数组中数据 * 个数要-1 , 还要判断是否消费完了,完了就挂起进入等待 * 4 两个线程,一个负责生产,一个负责消费 */ public class test01 { public static void main(String[]args){ SynStack ss = new SynStack(); Thread t1 = new Thread(new Push(ss)); Thread t2 = new Thread(new Pop(ss)); t1.start(); t2.start(); } } //生产 class Push extends Thread{ SynStack ss; public Push(SynStack ss){ super(); this.ss =ss; } @Override public void run(){ Random random = new Random(); while(true){ char ch =(char)(random.nextInt(26)+97); ss.push(ch); } } } //消费 class Pop extends Thread{ SynStack ss; public Pop(SynStack ss){ this.ss =ss; } @Override public void run(){ Random random = new Random(); while(true){ try{ Thread.sleep(1000); }catch(Exception e){ e.printStackTrace(); } char ch =(char)(random.nextInt(26)+97); ss.pop(); } } } class SynStack{ //保存的容器 char[] b=new char[6]; //生产个数 int index = 0; //生产 public synchronized void push(char ch){ //判断是否满了 if(index == b.length){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } //到这没满,开始生产 //唤醒消费者准备消费 this.notifyAll(); b [index] = ch; index++; System.out.println("生产了"+ch+"剩余"+index+"个元素"); } public synchronized char pop(){ //判断是否为空 if(index == 0){ try{ // 这里不用唤醒生产者,因为生产者是满了在wait,都为空了,说明生产者肯定没有wait this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } //到这里说明不为空,开始消费 index--; char ch =b[index]; //唤醒消费者 System.out.println("消费了"+ch+",剩余"+index+"个元素"); return ch; } }
在这里插入图片描述
3.单例模式
饿汉模式在多线程环境下没有问题,因为不管多少线程 类只能被加载一次,所以只会被初始化一次,也就意味着只能创建一个对象
https://www.cnblogs.com/dolphin0520/p/3920373.html
package day_02; /** * 单例模式 : 让某个类只实例化一个对象 * * 构造方法私有化, 静态变量保存对象,公共的静态方法用于获取类对象 * * 饿汉模式在多线程环境下没有问题,因为不管多少线程 类只能被加载一次,所以只会被初始化一次,也就意味着只能创建一个对象 */ public class Thread_05_SingLeton { private Thread_05_SingLeton() { } /** * volatile : 为什么使用volatile呢? 防止指令重排 * * https://www.cnblogs.com/dolphin0520/p/3920373.html * */ private volatile static Thread_05_SingLeton singLeton = null; // 效率低,因为每次都需要排队 // public synchronized static Thread_05_SingLeton getInstance() { // if (singLeton == null) { // singLeton = new Thread_05_SingLeton(); // } // return singLeton; // } // 效率较高,因为只需要第一次排队 public static Thread_05_SingLeton getInstance() { if (singLeton == null) { synchronized (Thread_05_SingLeton.class) { // 1// 2 if (singLeton == null) { singLeton = new Thread_05_SingLeton(); } } } return singLeton; } }