线程安全
- 单线程程序不会出现线程安全 问题
- 多线程程序没有访问共享数据不会出现线程安全问题
- 多线程访问共享数据会产生线程安全问题
- 当某一线程睡眠时,它即失去了cpu的执行权

public class ThreadSafe implements Runnable{
//定义一个多线程的票源
private int ticket = 100;
/**
* 线程安全问题:买票
* */
//设置线程任务:卖票
@Override
public void run() {
while (true) {
//判断是否存在票源
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
public class DemoTicket {
/**
* 创建三个线程,模拟卖票程序
* 因为要实现共享数据,所以要创建一个类
* */
public static void main(String[] args) {
//创建Runnable接口的实现类对象
ThreadSafe run = new ThreadSafe();
//创建Thread类对象,构造方法中传递Runnable接口实现类对象
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
//启动线程任务
t1.start();
t2.start();
t3.start();
}
}

同步方法来解决线程安全问题
同步对象原理
- 使用了对象锁,也叫同步锁,也叫做对象监视器
- 当多个线程抢夺cpu资源时,谁先抢夺到谁执行run方法,当遇到synchronized代码块时,会判断有无锁对象,当有时,就会获取到锁对象,执行synchronized代码块中内容,当执行完毕之后,会归还锁对象给同步代码块。当另一个线程也抢夺到cpu资源,会执行run方法,直到遇到synchronized代码块,判断有无锁对象,当没有时cpu会进入阻塞状态,直到某一个线程归还锁对象,此时这个线程才会执行,代码块保证保证统一时刻仅有一个线程执行。(当某一线程执行完同步代码块中内容才会释放对象锁)
- 因为每次都要判断有无synchronized关键字,因为效率会变低
1、同步代码块
- 同步代码块中锁对象可以是任意对象
- 必须保证多个线程使用的锁对象是同一个
- 作用:把同步代码块锁住,只让一个线程在同步代码块中执行
public class ThreadSafe implements Runnable{
//定义一个多线程的票源
private int ticket = 100;
/**
* 线程安全问题:买票
* */
/**
* 格式:
* synchronized(锁对象) {
* 可能会出现线程安全问题的代码
* }
* */
/**
* 同步代码块中锁对象可以是任意对象
* 但必须保证多个线程使用的锁对象是同一个
* 锁对象的作用:
* 把同步代码块锁住,只让一个线程在同步代码块中执行
* */
//创建一个锁对象
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
while (true) {
synchronized (obj) {
//判断是否存在票源
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
}

2、同步方法
- 同步方法也会把方法内部的代码锁住,只让一个线程执行
- 同步方法锁对象是实现类对象
3、静态同步方法
- 静态同步方法的锁对象是本类的class属性,即class文件对象(反射)
public class FunctionThread implements Runnable{
//定义一个多线程的票源
private static int ticket = 100;
/**
* 同步方法解决线程安全问题
* 格式:synchronized 返回值类型 函数名() {
* 可能会出现线程安全问题的代码块
* }
*
* */
/**
* 线程安全问题:买票
* */
//设置线程任务:卖票
@Override
public void run() {
while (true) {
//payTicket();
payTicketStatic();
}
}
//定义一个同步方法
public synchronized void payTicket() {
//判断是否存在票源
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
ticket--;
}
}
//静态同步方法
public static synchronized void payTicketStatic() {
//判断是否存在票源
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
ticket--;
}
}
}
解决线程安全的第三种方法lock锁
/*
* lock锁实现了比synchronized 更广泛的锁定操作
* lock接口中的方法
* void lock() 获取锁
* void unlock() 释放锁
*
* 使用步骤:
* 1、在成员位置创建一个ReentrantLock对象
* 2、在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
* 3、在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
*
* */
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunnableImple implements Runnable{
private static int ticket = 100;
//1、在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();
@Override
public void run() {
//重复卖票操作
while (true) {
//2、在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
//判断是否存在票源
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3、在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock(); //无论程序是否异常,都会释放锁对象,写在这里更便捷
}
System.out.println(Thread.currentThread().getName() + "线程正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
线程的六种状态

唤醒多个线程
- 进入到TimeWaiting(记时等待)有俩种方式
- 1、使用sleep(long m)方法,在毫秒值结束后,线程唤醒进入到Runnable/Blocked状态
- 2、使用wait(long m)方法,wait方法在毫秒值结束时,还没有被notify唤醒,就会自动醒来,线程唤醒进入Runnable/Blocked状态
- 唤醒的方法:
- void notify() 唤醒在此对象监视器上等待的单个线程
- void notifyAll() 唤醒在此对象监视器上等待的所有线程
public class demo11waitandnotify {
public static void main(String[] args) {
//创建 锁对象,保证唯一
Object obj = new Object();
//创建一个顾客线程
new Thread() {
@Override
public void run() {
//一直等着卖包子
while (true) {
//使用同步技术 ,保证在等待线程和唤醒线程只有一个在执行
synchronized (obj) {
System.out.println("顾客1告知老板需要包子的种类和数量");
//调用wait方法放弃cpu的执行,进入到WAITING(无线等待状态)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后的代码
System.out.println("包子已经做好了,顾客1开吃");
}
}
}
}.start();
//创建一个顾客2线程
new Thread() {
@Override
public void run() {
//一直等着卖包子
while (true) {
//使用同步技术 ,保证在等待线程和唤醒线程只有一个在执行
synchronized (obj) {
System.out.println("顾客2告知老板需要包子的种类和数量");
//调用wait方法放弃cpu的执行,进入到WAITING(无线等待状态)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后的代码
System.out.println("包子已经做好了,顾客2开吃");
}
}
}
}.start();
//创建老板线程
new Thread() {
@Override
public void run() {
//一直在卖包子
while (true) {
//做包子花了5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使用同步技术 ,保证在等待线程和唤醒线程只有一个在执行
synchronized (obj) {
System.out.println("老板5秒后包子已经做好了,告知顾客");
//做好包子后,使用notify唤醒顾客吃包子
//obj.notify(); //随机唤醒一个线程
//唤醒所有线程
obj.notifyAll();
}
}
}
}.start();
}
}
等待唤醒机制
线程之间的通信
- 等待唤醒机制图解
- 多个线程处理同一个资源,但是执行任务不同

线程唤醒机制
- wait:线程不再活动,不再参加调度,等待notify()唤醒
- notify():选取wait set的一个线程释放
- notifyAll():选取wait set的所有线程释放
- 总结:如果可以获取到锁,则线程从WAITING状态进入RUNNING状态
- 否则,从wait set出来,又进入entey set状态,线程从WAITING 状态变成BLOCKED状态
- notify()和wat()要使用同一个锁对象调用,必须在同步代码块和同步方法中执行
- 包子铺案例
public class BaoZi {
//皮
String pi;
//肉馅
String xian;
//有无包子
boolean flag = false;
}
public class BaoZiPu extends Thread {
//设置一个包子变量
private BaoZi bz;
//为包子变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//设置线程任务:生产包子
@Override
public void run() {
//定义一个变量统计包子数量
int count = 0;
//让包子铺一直生产包子
while (true) {
//使用同步技术保证俩个线程只有一个在执行
synchronized (bz) {
//判断有无包子
if (bz.flag == true) {
//包子铺线程进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//包子铺线程被唤醒之后执行带代码
//增加趣味性,生产俩种包子
if (count % 2 == 0) {
//薄皮,三鲜馅
bz.pi = "薄皮";
bz.xian = "三鲜馅";
} else {
//厚皮,牛肉馅
bz.pi = "厚皮";
bz.xian = "牛肉馅";
}
count++;
System.out.println("正在生产包子:" + bz.pi + " " + bz.xian + "包子");
//生产包子需要3000毫秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//生产之后设置包子状态为有
bz.flag = true;
//唤醒消费者线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,可以开吃了");
}
}
}
}
public class ChiHuo extends Thread{
//设置包子变量
private BaoZi bz;
//为包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//设置线程任务:吃包子
@Override
public void run() {
//让吃货一直吃包子
while (true) {
//使用线程同步技术,保证生产者和消费者为同一个对象锁
synchronized (bz) {
//判断有无包子
if (bz.flag == false) {
//吃货调用wait()方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//线程被唤醒之后吃包子
System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
//设置包子状态
bz.flag = false;
//吃货唤醒包子铺线程生产包子
bz.notify();
System.out.println("吃货已经把" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
System.out.println("--------------------------------------------------------------");
}
}
}
}
public class Demo {
public static void main(String[] args) {
//创建包子对象
BaoZi bz = new BaoZi();
//创建包子线程,生产包子
new BaoZiPu(bz).start();
//创建吃货线程,吃包子
new ChiHuo(bz).start();
}
}