多线程访问了共享数据,会产生线程安全的问题
只有多线程会产生安全问题
/*
卖票案例出现了线程安全问题,迈出了不存在的票和重复的票
解决线程安全的一种方式:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全的代码(访问了共享数据的代码)
}
注意:
1. 同步代码块中的锁对象,可以是任意的对象
2. 必须保证多个线程使用的锁对象是同一个
3. 锁对象作用:
把同步代码块所著,只让一个线程在同步代码块中执行
*/
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享的票源
private int ticket = 100;
// 创建一个锁对象,必须创建在run的外面
Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
if (ticket>0){
// 提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
/*
模拟买票
创建三个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
}
同步代码块的原理
/*
第二种方式:使用同步方法
使用步骤:
1. 把访问了共享数据的代码取出来,放到一个方法中
2. 在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全的代码(访问了共享数据的代码)
}
*/
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享的票源
private int ticket = 100;
// 创建一个锁对象,必须创建在run的外面
Object obj = new Object();
@Override
public void run() {
while(true){
payTicket();
}
}
// 定义一个同步方法
public synchronized void payTicket(){
if (ticket>0) {
// 提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
同步方法也会把方法内部的代码锁住,只让一个线程执行
同步方法的锁对象是谁? 就是实现类对象new Runnable(),也就是this
public class RunnableImpl implements Runnable {
// 定义一个多个线程共享的票源
private static int ticket = 100;//静态同步方法访问的变量也必须是静态的
// 创建一个锁对象,必须创建在run的外面
Object obj = new Object();
@Override
public void run() {
while(true){
payTicketStatic();
}
}
/*
静态同步方法的锁对象是谁?
不能是this,this是创建对象之后产生的,静态方法优先于对象,
静态方法的锁对象是本类的class属性——class文件对象(反射)
*/
public static synchronized void payTicketStatic(){
if (ticket>0) {
// 提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
/*
解决线程安全的第四种解决方案:Lock锁
java.util.concurrent.locks
Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作
Lock接口种的方法:
void lock() 获取锁。
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);
// 票存在,卖票
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 无论程序是否异常,否会把锁释放
//3. 在可能会出现安全问题的代码后调用Lock接口种的方法unLock释放锁
l.unlock();
}
}
}
}
}
等待唤醒案例代码实现
/*
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能由一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
Object类种的方法
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
Object obj = new Object();
// 顾客的线程
new Thread(){
@Override
public void run() {
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("告知老板要的包子的种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("开吃");
}
}
}.start();
// 老板的线程
new Thread(){
@Override
public void run() {
try {
Thread.sleep(5000);//画5s做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("告知顾客,可以吃包子");
obj.notify();
}
}
}.start();
}
}
进入到TimeWaiting(计时等待)有两种方式
/*
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类种的静态方法
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
返回值:
ExecutorService接口:返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
关闭线程池的方法:
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
线程池的使用步骤:
1. 使用线程池的工厂类Executors里面提供的newFixedThreadPool生产一个指定线程数量的线程池
2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
3. 调用ExecutorService中的submit,传递线程任务(实现类),开启线程,执行run方法
4. 调用ExecutorService中的shutdown,销毁线程池(不建议使用)
*/
public class Demo01ThreadPool {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行
// 线程池会一直开启,会自动把线程归还给线程池
es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行
es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行
}
}