线程出现安全问题的代码:
/*
实现卖票案例
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让买票操作重复执行
while (true) {
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票ticket
System.out.println(Thread.currentThread().getName() + "正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
主类:
/*
模拟卖票案例
创建三个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实现互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码
}
使用代码:
/*
实现卖票案例出现了线程安全问题,卖出了不存在的票和重复的票
解决线程安全问题的第一种方案:使用同步代码块
格式:
synchronized(同步锁){
需要同步操作的代码
}
注意:
1、通过代码块中的锁对象,可以使用任意的对象
2、但是必须保证多个线程使用的锁对象是同一个
3、锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让买票操作重复执行
while (true) {
//同步代码块
synchronized (obj){
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票ticket
System.out.println(Thread.currentThread().getName() + "正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
主代码不变。
解决线程安全的第二种方案:使用同步方法
使用步骤:
1、把访问了共享数据的代码抽取出来,放到一个方法中
2、在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法民(参数列表) {
可能出现线程安全问题的代码(访问了共享数据的代码)
}
代码:
/*
实现卖票案例出现了线程安全问题
解决线程安全的第二种方案:使用同步方法
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让买票操作重复执行
while (true) {
payTicket();
}
}
/*
定义同步方法
*/
public synchronized void payTicket() {
//先判断票是否存在
if (ticket > 0) {
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票ticket
System.out.println(Thread.currentThread().getName() + "正在卖第"+ticket+"张票");
ticket--;
}
}
}
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock( ) :加同步锁
public void unlock( ) :释放同步锁
代码及使用步骤如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
实现卖票案例
出现了线程安全问题
解决线程安全问题的第三种方案:使用Lock锁
java.util.concurrent.Locks.Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
public void lock( ) :加同步锁
public void unlock( ) :释放同步锁
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1、在成员位置创建一个ReentrantLock对象
2、可能会出现安全问题的代码前调用lock接口中的方法lock获取锁
3、可能会出现安全问题的代码后调用lock接口中的方法unlock释放锁
*/
public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private 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(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票ticket
System.out.println(Thread.currentThread().getName() + "正在卖第"+ticket+"张票");
ticket--;
}
// 3、可能会出现安全问题的代码后调用lock接口中的方法unlock释放锁
l.unlock();
}
}
}
Timed Waiting 计时等待
Blocked 锁阻塞状态
Waiting 无限等待
代码:
/*
等待唤醒案例:线程之间的通信
创建一个顾客线程,告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到
waiting状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
1、顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
2、同步使用的锁对象必须保证是唯一的
3、只有锁对象才能调用wait和notify方法
Object类中的方法
void wait()
在其他线程调用此对象的notify()方法或者notifyAll()方法前,导致当前线程等待
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//创建锁对象,保证唯一
Object obj = new Object();
//创建一个顾客线程
new Thread(){
@Override
public void run() {
while (true) {
//一直有顾客买包子
//保证等待和唤醒的线程只能有一个在执行,同步技术
synchronized (obj) {
System.out.println("告诉老板要的包子的种类和数量");
//调用wait方法,放弃cpu的执行,进入到waiting状态(无限等待)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子已经做好了,开吃");
System.out.println("——————————————————————");
}
}
}
}.start();
//创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
//一直做包子
while (true) {
//花了5秒做包子
try {
Thread.sleep(5000); //5秒做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("老板5秒之后做好包子,告知顾客可以吃了");
//调用notify方法,唤醒顾客吃包子
obj.notify();
}
}
}
}.start();
}
}
运行截图:
进入到TimeWaiting(计时等待)有两种方式
1、使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2、使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify( )唤醒单个线程
void notifyAll( )唤醒所有线程。