版权声明:作者已开启版权声明,如转载请注明转载地址。 https://blog.csdn.net/qq_34829447/article/details/82190389
一.多线程安全问题
1.线程操作共享数据的安全问题
- 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
- 程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
2.售票的案例
- 多个线程并发访问同一个数据资源,实例代码:
/*
* 多线程并发访问同一个数据资源
* 3个线程,对一个票资源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现类对象
Tickets t = new Tickets();
//创建3个Thread类对象,传递Runnable接口实现类
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
/*
* 通过线程休眠,出现安全问题
*/
public class Tickets implements Runnable{
//定义出售的票源
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
//对票数判断,大于0,可以出售,变量--操作
if( ticket > 0){
try{
Thread.sleep(10); //加了休眠让其他线程有执行机会
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
}
- 同步代码块synchronized解决线程安全问题(同步技术)
同步代码块公式:同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全
synchronized(任意对象){ 线程要操作的共享数据 }
同步的上厕所原理
- 不使用同步:线程在执行的过程中会被打扰,线程比喻成人,线程执行代码就是上一个厕所,第一个人正在上厕所,上到一半,被另外一个人拉出来
- 使用同步:线程比喻成人,线程执行代码就是上一个厕所,锁比喻成厕所门,第一个人上厕所,会锁门,第二个人上厕所,看到门锁上了,等待第一个人上完再去上厕所
同步代码块应用实例代码
/*
* 多线程并发访问同一个数据资源
* 3个线程,对一个票资源,出售
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现类对象
Tickets t = new Tickets();
//创建3个Thread类对象,传递Runnable接口实现类
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
/*
* 通过线程休眠,出现安全问题
* 解决安全问题,Java程序,提供技术,同步技术
* 公式:
* synchronized(任意对象){
* 线程要操作的共享数据
* }
* 同步代码块
*/
public class Tickets implements Runnable{
//定义出售的票源
private int ticket = 100;
private Object obj = new Object();
public void run(){
while(true){
//线程共享数据,保证安全,加入同步代码块
synchronized(obj){
//对票数判断,大于0,可以出售,变量--操作
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
}
}
3.同步方法
同步方法:将线程共享数据,和同步,抽取到一个方法中,在方法声明上加上synchronized (同步方法中的锁对象是 this)
同步方法的格式
public synchronized void method(){ 可能会产生线程安全问题的代码 }
实例代码
public class Ticket implements Runnable { //共100票 int ticket = 100; //定义锁对象 Object lock = new Object(); @Override public void run() { //模拟卖票 while(true){ //同步方法 method(); } } //同步方法,锁对象this public synchronized void method(){ if (ticket > 0) { //模拟选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } }
静态同步方法:在方法声明上加上
static synchronized
(静态同步方法中的锁对象是类名.class)静态同步方法的格式
public static synchronized void method(){ 可能会产生线程安全问题的代码 }
4.JDK1.5新特性Lock接口
查阅API,查阅Lock接口描述,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的常用方法
void lock()
void unlock()
Lock接口改进售票案例
/* * 多线程并发访问同一个数据资源 * 3个线程,对一个票资源,出售 */ public class ThreadDemo { public static void main(String[] args) { //创建Runnable接口实现类对象 Tickets t = new Tickets(); //创建3个Thread类对象,传递Runnable接口实现类 Thread t0 = new Thread(t); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t0.start(); t1.start(); t2.start(); } } /* * 使用JDK1.5 的接口Lock,替换同步代码块,实现线程的安全性 * Lock接口方法: * lock() 获取锁 * unlock()释放锁 * 实现类ReentrantLock */ public class Tickets implements Runnable{ //定义出售的票源 private int ticket = 100; //在类的成员位置,创建Lock接口的实现类对象 private Lock lock = new ReentrantLock(); public void run(){ while(true){ //调用Lock接口方法lock获取锁 lock.lock(); //对票数判断,大于0,可以出售,变量--操作 if( ticket > 0){ try{ Thread.sleep(10); System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--); }catch(Exception ex){ }finally{ //释放锁,调用Lock接口方法unlock lock.unlock(); } } } } }
二.等待唤醒机制
1.死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
出现代码样式
synchronzied(A锁){ synchronized(B锁){ } }
死锁代码情况演示
- 定义锁对象类
public class MyLock { public static final Object lockA = new Object(); public static final Object lockB = new Object(); }
- 线程任务类
public class ThreadTask implements Runnable { int x = new Random().nextInt(1);//0,1 //指定线程要执行的任务代码 @Override public void run() { while(true){ if (x%2 ==0) { //情况一 synchronized (MyLock.lockA) { System.out.println("if-LockA"); synchronized (MyLock.lockB) { System.out.println("if-LockB"); System.out.println("if大口吃肉"); } } } else { //情况二 synchronized (MyLock.lockB) { System.out.println("else-LockB"); synchronized (MyLock.lockA) { System.out.println("else-LockA"); System.out.println("else大口吃肉"); } } } x++; } } }
- 测试类
public class ThreadDemo { public static void main(String[] args) { //创建线程任务类对象 ThreadTask task = new ThreadTask(); //创建两个线程 Thread t1 = new Thread(task); Thread t2 = new Thread(task); //启动线程 t1.start(); t2.start(); } }
2.线程等待与唤醒案例介绍
线程等待与唤醒机制所涉及到的方法
- wait() :等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中
- notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的
- notifyAll():唤醒全部:可以将线程池中的所有wait() 线程都唤醒
所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
为什么这些操作线程的方法(notify()/notifyAll()/wait())定义在Object类中?因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。
实例代码
- 模拟资源类
public class Resource { private String name; private String sex; private boolean flag = false; public synchronized void set(String name, String sex) { if (flag) try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 设置成员变量 this.name = name; this.sex = sex; // 设置之后,Resource中有值,将标记该为 true , flag = true; // 唤醒output this.notify(); } public synchronized void out() { if (!flag) try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 输出线程将数据输出 System.out.println("姓名: " + name + ",性别: " + sex); // 改变标记,以便输入线程输入数据 flag = false; // 唤醒input,进行数据输入 this.notify(); } }
- 输入线程任务类
public class Input implements Runnable { private Resource r; public Input(Resource r) { this.r = r; } @Override public void run() { int count = 0; while (true) { if (count == 0) { r.set("小明", "男生"); } else { r.set("小花", "女生"); } // 在两个数据之间进行切换 count = (count + 1) % 2; } } }
- 输出线程任务类
public class Output implements Runnable { private Resource r; public Output(Resource r) { this.r = r; } @Override public void run() { while (true) { r.out(); } } }
- 测试类
public class ResourceDemo { public static void main(String[] args) { // 资源对象 Resource r = new Resource(); // 任务对象 Input in = new Input(r); Output out = new Output(r); // 线程对象 Thread t1 = new Thread(in); Thread t2 = new Thread(out); // 开启线程 t1.start(); t2.start(); } }
三.线程知识点总结
1.同步锁
多个线程想保证线程安全,必须要使用同一个锁对象
同步代码块(同步代码块的锁对象可以是任意的对象)
synchronized (锁对象){ 可能产生线程安全问题的代码 }
同步方法(同步方法中的锁对象是this)
public synchronized void method(){ 可能产生线程安全问题的代码 }
静态同步方法(锁对象是类名.class)
public synchronized static void method(){ 可能产生线程安全问题的代码 }
2.多线程有几种实现方案
- 继承Thread类
- 实现Runnable接口
- 通过线程池,实现Callable接口
3.同步有几种方式,分别是什么?
- 同步代码块
- 同步方法
- 静态同步方法
4.启动一个线程是run还是start,他们的区别是?
- 启动一个线程是start()
- 区别
- start:启动线程,并调用线程中的run方法
- run:执行该线程对象要执行的任务
5.sleep和wait方法的区别?
- sleep:不释放锁对象,释放cpu使用权;在休眠时间内不能唤醒
- wait:释放锁对象,释放cpu使用权;在等待时间内能唤醒
6.为什么wait、notify、notifyAll等方法都定义在Object类中?
- 锁对象可以使任意类型对象