Java笔记(死锁、线程通信、单例模式)

一、死锁

1.概述

  • 死锁 : 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法往下执行。
  • 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
  • 原理 :
    1. 某个线程执行完成,需要先后嵌套锁定两个对象,在这个过程中,先锁定了第一个对象
    2. 另一个线程执行完成也需要先后嵌套锁定两个对象,在这个过程中,先锁定了第二个对象
    3. 第一个线程执行中,要执行到第二个对象的时候,发现第二个对象被锁定,进入等待状态,等待交出锁
    4. 第二个线程执行中,要执行到第一个对象的时候,发现第一个对象也被锁定,也进入等待状态
    5. 此时两个线程都在等待对方交出锁,导致死锁

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;
	}
	@Override
	public void run() {
		synchronized (o1) {
//			try {//加上睡眠一定死锁
//				Thread.sleep(1000);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			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;
	}
	@Override
	public 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);//保证t1先执行
		t2.start();
	}
}
class PrintNum extends Thread{
	Num num;
	public PrintNum(Num num){
		this.num=num;
	}
	@Override
	public 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.生产者消费者

Java笔记(死锁、线程通信、单例模式)_第1张图片

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;
	}
	@Override
	public 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;
	}
	@Override
	public 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(){
		
	}
	// volatile 防止指令重排
	private volatile static SingLeton singLeton;
	public static SingLeton getInstance(){
        // 多线程可能同时进入
		if (singLeton==null) {
            // 一个线程进入
			synchronized (SingLeton.class) {
				if (singLeton==null) {
                    // 一个线程进入后 对象就不再是null,其他的线程将无法进入
					singLeton=new SingLeton();
				}	
			}
		}
		return singLeton;
	}
}

四、线程池

  • 线程池的作用:
    • 线程池作用就是限制系统中执行线程的数量。
  • 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;
  • 少了浪费了系统资源,多了造成系统拥挤效率不高。
  • 用线程池控制线程数量,其他线程排队等候。
  • 一个任务执行完毕,再从队列的中取最前面的任务开始执行。
  • 若队列中没有等待进程,线程池的这一资源处于等待。
  • 当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了,否则进入等待队列。
  • 为什么要用线程池:
    1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
    2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

你可能感兴趣的:(java,笔记)