1、线程之间的通信:
多个线程处理同一个资源时,处理的动作是不一样,(大概意思是有相关的先后执行顺序)
2、为什么处理线程之间的通信:
多线线程在执行时,默认是抢夺CPU的执行权,后期看结果,会有所不一样!会造成数据不是我们想要的一个结果
我们想要的是,多个线程有规则的执行。例如:一个线程给变量赋值,一个线程获取变量的值 。
3、通信分析:
一个线程执行时,另外一个线程在等待。直到一个执行完毕之后,另外一个线程才能执行。
Object类中的相关方法
进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
3.使用wait方法, 一直处于等待状态, 除非有一个线程调用notify方法,将该线程唤醒,才能醒过来
唤醒的方法: (锁.方法)
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程
4、生产者与消费者问题分析:
思路:
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait 方法,放弃cpu的执行,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
锁.wait :使本线程进入等待状态
锁.notify :唤醒其他线程
5、线程池:
每一个案例都需要创建线程,启动线程! 创建线程和启动线程,都是由系统操作的,跟CPU有关的!只要跟本地系统操作的资源 ,效率都是不高的。程序要跟CPU申请资源,每一次申请,都是需要过程(时间的),要解决这个频繁开启线程的问题,要使用线程池。
如果我们要使用线程就从线程池里拿,用完再放回线程池里边。
好处:
1. 降低资源消耗
。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2. 提高响应速度
。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性
。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
6、线程池的创建:
Executors类中创建线程池的方法:public static ExecutorService newFixedThreadPool(int nThreads)
返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)。
package com.itfxp.thread.thread02;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package com.itfxp.thread.thread02;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
// 创建线程池,该线程池中有两个线程
ExecutorService pool = Executors.newFixedThreadPool(2);
// 创建Runnable接口实现类对象
MyRunnable mr = new MyRunnable();
MyRunnable mr1 = new MyRunnable();
MyRunnable mr2 = new MyRunnable();
// new Thread(mr).start();
// 使用ExecutorService中的submit方法进行提交任务
pool.submit(mr);
pool.submit(mr1);
pool.submit(mr2);
// 消毁线程池,一般不消毁,循环使用线程池中的线程
// pool.shutdown();
pool.submit(mr);
}
}
7、voliate关键字:
volatile保证线程间变量的可见性,简单地说就是当线程A对变量X进行了修改后,在线程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:
==线程对变量进行修改之后,要立刻回写到主内存。==
==线程对变量读取的时候,要从主内存中读,而不是缓存==
/**
* volatile用于保证数据的同步,也就是可见性
*/
public class VolatileTest {
private volatile static int num = 0; //如果不加关键字,后续修改的num值是不会被看到的,即线程进入死循环
public static void main(String[] args) throws InterruptedException {
new Thread(
new Runnable(){
public void run(){
while(num==0) { //此处不要编写代码
}
}
}
) .start();
Thread.sleep(1000);
num = 1;
}
}
8、单例设计模式:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点:
1、单例类只能有一个实例。也就是只有一个对象
2、单例类必须自己创建自己的唯一实例。 写单例构造方法是要私有的
3、单例类必须给所有其他对象,提供一个方法用于获取该对象
前提条件:
1、私有构造方法
2、在本类的成员位置,创建出自己类对象
3、提供公共方法,返回创建的对象 ,该方法必须是静态的
单例模式饿汉式:
/** 单例模式饿汉式*/
public class Single {
private Single(){
}
private static final Single s = new Single();
public static Single getInstance(){
return s;
}
}
单例模式懒汉式:
/** 单例模式懒汉式*/
public class Single {
private Single(){}
private static Single s = null;
public static Single getInstance(){
if(s == null){
s = new Single();
return s;
}
}
}
懒汉式的安全问题:
public class Single {
private Single(){}
private static Single s = null;
public static Single getInstance(){
if(s == null){
synchronized(Single.class){ //单例模式懒汉式是一种对象延迟加载,在多线程情况下会有安全隐患, 要想解决此问题,我们需要加同步代码块,进行解决
if( s == null)
s = new Single();
}
}
return s;
}
}
双层if判断的原因
- 方法中加入同步锁,保证线程安全
- 第二个线程调用方法getInstance()的时候,变量s,已经不是null,被前面的线程new过
- 当已经有对象了,第二个线程没有必要再进入同步了,直接return返回对象即可