目录
一、线程安全问题概述
多个线程操作同一个数据的情况下,线程不安全了!
二、线程安全问题的代码实现
多线程类:
package study.thread;
public class ThreadSafeImpl 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--;
}else {
break;
}
}
}
}
测试类:
package study.thread;
public class ThreadSafeTest {
public static void main(String[] args) {
ThreadSafeImpl threadSafe = new ThreadSafeImpl();
//多线程干一件事
new Thread(threadSafe).start();
new Thread(threadSafe).start();
new Thread(threadSafe).start();
}
}
运行结果(截取部分):
(发现票卖重了,而且卖了不存在的票,这就有问题了!)
售票员Thread-2正在卖第100张票……
售票员Thread-0正在卖第100张票……
售票员Thread-1正在卖第100张票……
...
售票员Thread-2正在卖第1张票……
售票员Thread-0正在卖第1张票……
售票员Thread-1正在卖第-1张票……
三、线程安全问题产生的原理
四、解决线程安全问题
1、引入线程同步机制的三种方法
①同步代码块;
②同步方法;
③锁机制;
2、同步代码块
格式:
synchronized(锁对象){
可能出现线程安全问题的代码(从访问到共享数据的代码开始)
}
锁对象的含义:
前面的线程开始执行后回去拿取堆内存中的锁对象,后面的线程开始执行后再去拿锁对象就拿不到了,所以无法继续执行,需要等待前面的线程执行完毕,归还锁对象,后面的线程才能拿到锁对象,继续执行;
注意:
①通过代码块中的锁对象,可以使用任意的对象;
②但必须保证多个线程使用的所对象是同一个;
③所对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行;
代码示例:
加入同步代码块后的多线程类:
package study.thread;
public class ThreadSafeImpl implements Runnable {
private int ticket = 100;
private final Object object = new Object();
@Override
public void run() {
while (true){
synchronized (object){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
ticket--;
}else {
break;
}
}
}
}
}
测试类:
package study.thread;
public class ThreadSafeTest {
public static void main(String[] args) {
ThreadSafeImpl threadSafe = new ThreadSafeImpl();
//多线程干一件事
new Thread(threadSafe).start();
new Thread(threadSafe).start();
new Thread(threadSafe).start();
}
}
运行结果(截取部分):
售票员Thread-0正在卖第100张票……
售票员Thread-0正在卖第99张票……
售票员Thread-2正在卖第98张票……
售票员Thread-1正在卖第97张票……
售票员Thread-1正在卖第96张票……
售票员Thread-1正在卖第95张票……
3、同步代码块的原理
4、同步方法
使用步骤:
①把访问了共享数据的代码抽取出来,放到一个方法中;
②在方法上添加synchronized修饰符;
格式
访问修饰符 synchronized 返回值类型 方法名(参数列表){
//方法体
}
代码示例:
package study.thread;
public class ThreadSafeImpl implements Runnable {
private int ticket = 100;
@Override
public void run() {
printMessage();
}
private synchronized void printMessage(){
while (true){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
ticket--;
}else {
break;
}
}
}
}
但经测试发现,此方法一个会出现一个线程将票卖完的情况,我自己猜测也许是因为while循环放进了锁住的方法中。
改进后的代码:
package study.thread;
public class ThreadSafeImpl implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true){
printMessage();
if(ticket==0){
break;
}
}
}
private synchronized void printMessage(){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
ticket--;
}
}
}
这样写确实避免了上述情况。
备注:
同步方法也会把方法内部的代码锁住,只让一个线程执行,实际上所的对象是new RunnableImpl(),也就是this(自身);
5、静态同步方法
概述:
静态同步方法就是在一般的同步方法synchronized前加上static;
注意:
此时的锁对象不是this(本身),而是本类的class属性-->class文件对象(反射);
6、锁机制(Lock锁)
概述:
Lock接口实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作;
使用步骤:
①在成员位置创建一个ReentrantLock(可重入锁);
②在有可能出现安全问题的代码前,调用获取锁的方法(闭锁);
③在有可能出现安全问题的代码后,调用释放锁的方法(开锁);
代码示例:
package study.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadSafeImpl implements Runnable {
private int ticket = 100;
//1、在成员位置创建一个
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//2、在有可能出现安全问题的代码前,调用获取锁的方法(闭锁)
lock.lock();
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
ticket--;
}else {
break;
}
//3、在有可能出现安全问题的代码后,调用释放锁的方法(开锁)
lock.unlock();
}
}
}
代码示例更好的写法:
package study.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadSafeImpl implements Runnable {
private int ticket = 100;
//1、在成员位置创建一个
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//2、在有可能出现安全问题的代码前,调用获取锁的方法(闭锁)
lock.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();
}
}else {
break;
}
}
}
}
7、死锁
概述:
多个线程同时站有一些共享资源,并且相互等待其他线程占有的资源才能运行,而这导致两个或多个线程都在等待对方释放资源,都停止运行的情形;
某一个同步代码块同时拥有“两个以上对象的锁”时,就可能发生“死锁”;
又两个小朋友,小明和小强,小明有玩具汽车,但是小明想要小强的玩具枪,相反小强有玩具枪却想要小明的玩具汽车,双方都说如果你先把你的玩具给我,我就给你,所以两个小朋友都在等对方把玩具给自己,这就僵持住了,在程序中也就是“死锁”;
代码演示:
package com.zb.thread;
import lombok.SneakyThrows;
//测试死锁
public class TestDeadlock {
public static void main(String[] args) {
Object gan = new Object();
Object car = new Object();
new Thread(new Play("小明",gan,car)).start();
new Thread(new Play("小强",gan,car)).start();
}
}
class Play implements Runnable{
private final String name;
private final Object gan;
private final Object car;
public Play(String name, Object gan, Object car) {
= name;
this.gan = gan;
this.car = car;
}
@SneakyThrows
@Override
public void run() {
if("小明".equals(name)){
synchronized (car){//小明持有玩具车
System.out.println("小明持有玩具车!");
System.out.println("小明想要玩具枪!");
synchronized (gan){
System.out.println("小明获得了玩具枪!");
}
}
}else if("小强".equals(name)){
synchronized (gan){//小强持有玩具枪
System.out.println("小强持有玩具枪!");
System.out.println("小强想要玩具车!");
synchronized (car){
System.out.println("小明获得了玩具枪!");
}
}
}
}
}
运行结果:
小明持有玩具车!
小明想要玩具枪!
小强持有玩具枪!
小强想要玩具车!
修改代码,使得双方都愿意先让出自己的玩具:
package com.zb.thread;
import lombok.SneakyThrows;
//测试死锁
public class TestDeadlock {
public static void main(String[] args) {
Object gan = new Object();
Object car = new Object();
new Thread(new Play("小明",gan,car)).start();
new Thread(new Play("小强",gan,car)).start();
}
}
class Play implements Runnable{
private final String name;
private final Object gan;
private final Object car;
public Play(String name, Object gan, Object car) {
= name;
this.gan = gan;
this.car = car;
}
@SneakyThrows
@Override
public void run() {
if("小明".equals(name)){
synchronized (car){//小明持有玩具车
System.out.println("小明持有玩具车!");
System.out.println("小明想要玩具枪!");
}
Thread.sleep(1000);
synchronized (gan){
System.out.println("小明获得了玩具枪!");
}
}else if("小强".equals(name)){
synchronized (gan){//小强持有玩具枪
System.out.println("小强持有玩具枪!");
System.out.println("小强想要玩具车!");
}
synchronized (car){
System.out.println("小明获得了玩具枪!");
}
}
}
}
运行结果:
小明持有玩具车!
小明想要玩具枪!
小强持有玩具枪!
小强想要玩具车!
小明获得了玩具枪!
小明获得了玩具枪!