Java-线程总结(二)

Java-线程总结(一)

1.单例(Singleton)模型

单例模式:保证类中有且只有一个对象。
解决问题:一个全局使用的类频繁地创建与销毁。
主要代码:构造方法私有化(private)。
单例模式的几种实现方式:

1.1懒汉式

/**
    * 懒汉式
    */
	private static Singleton01 instance;//唯一对象

	private Singleton01() {// 构造方法私有化

	}

	public static Singleton01 getInstance() {// 用来创建(获取)唯一对象的方法
		if (instance == null) {
			instance = new Singleton01();
		}
		return instance;
	}
	//---------------------------------->
	 public static void main(String[] args) {
		Singleton01 s1 = Singleton01.getInstance();
		Singleton01 s2 = Singleton01.getInstance();
		System.out.println(s1==s2);//结果为true。说明s1与s2指向同一对象
	}

多线程情况下:

public class TestSingleton implements Runnable {
	public static void main(String[] args) {
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();

	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			Singleton01 s = Singleton01.getInstance();
			System.out.println(s.hashCode());
			try {
				Thread.sleep(200);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}
}

运行结果:
在这里插入图片描述
由此可见,懒汉式模型非线程安全。
解决方法:在getInstance()方法上加锁

public synchronized static Singleton01 getInstance() {// 用来创建(获取)唯一对象的方法
		if (instance == null) {
			instance = new Singleton01();
		}
		return instance;
	}

1.2饿汉式

public class Singleton02 {
    private static Singleton02 instance = new Singleton02();//创建唯一对象
	
	private Singleton02() {//构造方法私有化
    	
	}
	
	public static Singleton02 getInstance() {//获取唯一对象的方法
		return instance;
	}
}

多线程情况下:

public class TestSingleton implements Runnable {
	public static void main(String[] args) {
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();

	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			Singleton02 s = Singleton02.getInstance();
			System.out.println(s.hashCode());
			try {
				Thread.sleep(200);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

}

运行结果:
在这里插入图片描述

由此可见,饿汉式模型线程安全。

1.3枚举类

public enum Singleton03 {
     INSTANCE;//创建唯一对象
}

多线程情况下:

public class TestSingleton implements Runnable {
	public static void main(String[] args) {
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();
		new Thread(new TestSingleton()).start();

	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			Singleton03 s = Singleton03.INSTANCE;
			System.out.println(s.hashCode());
			try {
				Thread.sleep(200);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

}

运行结果:
在这里插入图片描述

由此可见,枚举式模型线程安全

2.生产者消费者模型

所谓的生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架满的时候,消费者可以从货架上拿走商品,生产者此时等待货架的空位,这样不断的循环。那么在这个过程中,生产者和消费者是不直接接触的,所谓的‘货架’其实就是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是仍给阻塞队列,这个阻塞队列就是来解决生产者消费者的强耦合的。

为什么要使用生产者和消费者模式?

在线程开发中,生产者就是生产数据的线程,消费者就是消费数据的线程。
在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。
如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。
为了解决这个问题于是引入了生产者和消费者模式。

例如:KFC问题。
生产者不断的制造汉堡,消费者不断的消费汉堡,而货架为其两者之间的中间容器。
汉堡类:

public class Hambager {
	private static volatile int id;// 每个汉堡唯一id
	private String name;// 汉堡名称
	private static AtomicInteger ato = new AtomicInteger(0);

	public Hambager(String name) {
		id = ato.incrementAndGet();
		this.name = name;
	}

	public static int getId() {
		return id;
	}

	public static void setId(int id) {
		Hambager.id = id;
	}

	public String getName() {
		return name;
	}

	@Override
	public String toString() {
		return "Hambager [name=" + name + "]";
	}

}

货柜类:

public class KFC {
	private Deque<Hambager> pool = new LinkedBlockingDeque<>();// 存放汉堡的货柜。相当于中间容器。
	private final int MAX_VALUE = 10;// 货柜的最大容量

	public synchronized void consume() {// 消费者消费汉堡
		while (pool.isEmpty()) {
			System.out.println("没有汉堡了,消费者休息等待生产者生产汉堡");
			try {
				wait();// 当前线程等待
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		Hambager h = pool.poll();
		System.out.println("消费一个" + h.getName() + ",还剩汉堡数:" + pool.size());
		System.out.println("唤醒生产者生产汉堡");
		notifyAll();// 唤醒所有等待中的线程
	}

	public synchronized void produce() {// 生产者生产汉堡
		while (pool.size() >= MAX_VALUE) {
			System.out.println("汉堡满了,生产者等待消费者消费汉堡");
			try {
				wait();// 当前线程等待
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		Hambager h = new Hambager("鸡肉汉堡");
		pool.offer(h);
		System.out.println("生产一个" + h.getName() + ",还剩汉堡数:" + pool.size());
		System.out.println("唤醒消费者消费汉堡");
		notifyAll();// 唤醒所有等待中的线程
	}
}

消费者线程:

public class ConsumerThread extends Thread{
     private KFC kfc;

	public ConsumerThread(KFC kfc) {

		this.kfc = kfc;
	}
     @Override
    public void run() {
    	while(true) {
    		kfc.consume();//不停消费汉堡
    		try {
				sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    }
}

生产者线程:

public class ProducerThread extends Thread{
     private KFC kfc;

	public ProducerThread(KFC kfc) {

		this.kfc = kfc;
	}
     @Override
    public void run() {
    	while(true) {
    		kfc.produce();//不停生产汉堡
    		try {
				sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    }
}

模型检测:

public static void main(String[] args) {
		KFC kfc = new KFC();
		//消费者线程
		new ConsumerThread(kfc).start();
		new ConsumerThread(kfc).start();
		new ConsumerThread(kfc).start();
		new ConsumerThread(kfc).start();
		new ConsumerThread(kfc).start();
		new ConsumerThread(kfc).start();
		
		
		//生产者线程
		new ProducerThread(kfc).start();
		new ProducerThread(kfc).start();
		new ProducerThread(kfc).start();
		new ProducerThread(kfc).start();

     }

在这里插入图片描述
在这里插入图片描述
且为无限运行,不会停止。

3线程池

1.线程池的优势

  1. 降低资源销毁:通过重复利用已经创建的线程,降低线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 防止服务器过载:形成内存溢出,或者CPU耗尽。.
  4. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。

2.线程池的主要参数

  1. corePoolSiz:线程池中的核心线程数量,在没有用的时候,也不会被回收。
  2. maximumPool:就是线程池中可以容纳的最大线程的数量
  3. keepAliveTi:线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间
  4. util:计算时间的一个单位
  5. workQueue:就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)
  6. handler:拒绝策略

3.线程池流程
在这里插入图片描述
4.拒绝策略
当任务队列满了之后,如果还有任务提交过来,会触发拒绝策略

1.AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认该方式。
2.CallerRunsPolicy:直接调用execute来执行当前任务。
3.DiscardPolicy:丢弃任务,但是不抛出异常。
4.DiscardOldestPolicy:抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。先从任务队列中弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。

5.提交方式
在这里插入图片描述

3.1创建线程池

常见的几种线程池类型:

  1. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  public static void main(String[] args) throws Exception {
    	ExecutorService pool = Executors.newCachedThreadPool();//创建线程池
    	
    	for(int i =0;i<10;i++) {
    		Thread.sleep(200);
    		pool.execute(()->{//线程池创建线程
    			for(int s =0;s<10;s++) {
    				System.out.println(Thread.currentThread().getName()+":"+s);
    				try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
    			}
    		});
    	}
	}

结果:
在这里插入图片描述

  1. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
 public static void main(String[] args) throws Exception {
    	ExecutorService pool = Executors.newFixedThreadPool(2);//创建定长为2线程池
    	
    	for(int i =0;i<10;i++) {
    		Thread.sleep(200);
    		pool.execute(()->{//线程池创建线程
    			for(int s =0;s<10;s++) {
    				System.out.println(Thread.currentThread().getName()+":"+s);
    				try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
    			}
    		});
    	}
	}

结果:
在这里插入图片描述

  1. newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
 public static void main(String[] args) throws Exception {
    	ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);//创建定长为5线程池
    	int t =0;
    	pool.scheduleAtFixedRate(()->//定时创建线程
    	 System.out.println(Thread.currentThread().getName()+":"+t)
    	, 0, 2, TimeUnit.SECONDS);
    	
	}

结果:
在这里插入图片描述

  1. newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
public static void main(String[] args) throws Exception {
    	ExecutorService pool = Executors.newSingleThreadExecutor();//创建线程池
    	
    	for(int i =0;i<10;i++) {
    		Thread.sleep(200);
    		pool.execute(()->{//线程池创建线程
    			for(int s =0;s<10;s++) {
    				System.out.println(Thread.currentThread().getName()+":"+s);
    				try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
    			}
    		});
    	}
	}

结果:
在这里插入图片描述

3.2线程池相关问题

1.线程池核心线程数的设置?
在这里插入图片描述
(1)CPU的线程数概念仅仅只针对Intel的CPU才有用,因为它是通过Intel超线程技术来实现的。
(2)如果没有超线程技术,一个CPU核心对应一个线程。 所以,对于AMD的CPU来说,只有核心数的概念,没有线程数的概念。
2.高并发、任务执行时间短的业务怎样使用线程池?
高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
3.并发不高、任务执行时间长的业务怎样使用线程池?
假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。
假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值