第一章 等待唤醒
1.1 线程等待唤醒方法
方法定义在java.lang.Object类中
public final void wait(); 当前线程等待,当前线程必须拥有此对象监视器.
public final void notify(); 唤醒在此对象监视器上等待的单个线程;
public final void notifyAll(); 唤醒在此对象监视器上等待的所有线程;
1.2 sleep()和wait()方法的区别
sleep()是Thread类静态方法 不需要对象锁
wait()方法是Object类的方法 被对象锁调用 而且只能出现在同步中.
执行sleep()方法的线程不会释放同步锁
执行wait()方法的线程要释放同步锁, 被唤醒后还需获取锁才能执行
代码演示:
包子铺类:
import java.util.List;
public class BaoZiPu extends Thread{
private List<String> list;
public BaoZiPu(String name, List<String> list) {
super(name);
this.list = list;
}
@Override
public void run() {
int i = 0;
while(true){
//list作为锁对象
synchronized (list){
if (list.size()>0){
//存元素的线程进入到等待状态
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果线程没有进入到等待状态 说明集合中没有元素
//向集合中添加元素
list.add("包子");
System.out.println(list);
//集合中已经有元素了 唤醒获取元素的线程
list.notify();
}
}
}
}
吃货类:
import java.util.List;
public class ChiHuo extends Thread{
private List<String> list;
public ChiHuo(String name, List<String> list) {
super(name);
this.list = list;
}
@Override
public void run() {
while(true){
//由于使用的是同一个集合 list作为锁对象
synchronized (list){
//如果集合中没有元素 获取元素的线程进入到等待状态
if (list.size() == 0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果集合中有元素 则获取元素的线程获取元素(删除)
list.remove(0);
System.out.println(list);
//集合中已经没有元素 则唤醒添加元素的线程 向集合中添加元素
list.notify();
}
}
}
}
测试类:
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
//等待唤醒案例
List<String> list = new ArrayList<>();
//创建线程对象
BaoZiPu bzp = new BaoZiPu("包子铺",list);
ChiHuo ch = new ChiHuo("吃货",list);
bzp.start();
ch.start();
}
}
第二章 Condition 接口
2.1 Condition接口常用方法
public void await(); 线程等待
public void signal(); 唤醒一个等待的线程
public void singalAll(); 唤醒所有等待的线程
2.2 Condition接口方法和Object类方法比较
(1) Condition可以和任意的Lock组合,也就是实现了线程的分组管理
一个线程的案例中,可以使用多个Lock锁,每个Lock锁上可以结合Condition对象
synchronized同步中做不到线程分组管理
(2) Object类wait()和notify()都要和操作系统交互,并通知CPU挂起线程,唤醒线程,效率低。
(3) Condition接口方法await()不和操作系统交互,而是让线程释放锁,并存放到线程队列容器中,当被signal()唤醒后,从队列中出来,从新获取锁后在执行。
(4) 因此使用Lock和Condition的效率比Object要快很多
(生产者与消费者)案例改进:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test02 {
public static void main(String[] args) {
//定义一个集合用来装包子 数据
List<String> list = new ArrayList<>();
Lock l = new ReentrantLock(); //对象监视器
Condition product = l.newCondition(); //生产线程 对象监视器
Condition customer = l.newCondition(); //消费线程 对象监视器
//生产的线程
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
l.lock(); //获取锁
if (list.size() > 0){
try {
product.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("肉包子");
System.out.println("包子铺生产了一个包子-->"+list);
//唤醒消费线程
customer.signal();
l.unlock(); //释放锁
}
}
}).start();
//消费的线程
new Thread(new Runnable() {
@Override
public void run() {
while(true){
l.lock(); //获取锁
if (list.size() == 0){
try {
//消费线程等待
customer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove(0);
System.out.println("吃货吃掉了一个包子-->"+list);
//唤醒生产线程
product.signal();
l.unlock(); //释放锁
}
}
}).start();
}
}
第三章 JMM模型
第四章 java并发编程的三大特性
在JAVA并发编程,如果要保证程序的线程安全,就要保证代码的原子性、可见性、有序性。
原子性
原子性,即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行。
(提供了互斥访问,在同一时刻只有一个线程进行访问.
JAVA提供了原子性的技术保障有如下:
1、synchronized (互斥锁)
2、Lock(互斥锁)
3、原子类(CAS)
上面两个都是通过互斥锁实现,即同一时刻只允许一个线程操作该变量,保障了原子性,这里不再过多介绍.
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程应该能够立即看得到修改的值.
那么如何保证可见性?
1.加锁,比如使用synchronized
2.使用volatile关键字保证可见性
有序性
一般来说,程序的执行顺序按照代码的先后顺序执行.但是处理器为了提高程序的效率,可能会对代码的执行顺序进行优化,
它不保证程序中各个语句的执行先后顺序一致,但是保证程序的最终结果和代码顺序执行的结果一致.
volatile关键字有两个作用 1.保证可见性 2.禁止重排序
注意:锁也可以保证有序性,因为在代码块中,一次只有一个线程执行,系统重排对单线程执行是没有任何影响的.
DCL单例是否需要使用volatile关键字?
单例设计模式
单例代表单个实例,保证一个类的对象永远只有一个!
饿汉式:
代码演示:
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static、final修饰
private static final Singleton instance = new Singleton();
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
return instance;
}
}
饿汉模式的优缺点:
优点:实现起来简单,没有多线程同步问题。
缺点:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,
从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),
当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存
懒汉式(延迟加载):
延迟加载就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。
不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。
代码演示:
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
“懒汉模式”的优缺点:
优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance没有创建对象,
当getInstance方法第一次被调用时,才创建对象为其赋值,因此在某些特定条件下会节约了内存。
缺点:在多线程环境中,这种实现方法是完全错误的,根本不能保证单例的状态。
DCL双检查锁机制单例(DCL:double checked locking) (DCL双检查锁机制单例,效率高,线程安全,多线程操作原子性。)
代码演示:
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static修饰
private static /*volatile*/ Singleton instance;
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
// 第一次检查instance是否被实例化出来,如果没有进入if块
if(instance == null) {
synchronized (Singleton.class) {
// 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才 最终实例出对象
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}