出现线程安全问题我们如何处理?? ==》同步原理
1.同步方法:synchronized 修饰的方法 ex:public synchronized void test(){}
弊端:方法中的所有代码,都只允许一个线程访问。
(有一种情况:一个方法中,有一个部分代码不会涉及到线程安全问题,可以允许多个线程同时访问 == 》即下面的2.同步代码块)
2.同步代码块 : synchronized(被加锁的对象){ 代码 }
3.锁机制Lock
①创建ReentrantLock对象
②调用lock方法:加锁
{代码....}
③调用unlock方法:解锁
注意:可把解锁的unlock方法的调用放在finally{}代码块中,保证一定能解锁提醒:在同步的时候,其他代码都可以多个线程同时执行!只是被同步的代码不能同时执行!
ex1: 同步方法 使用synchronized 修饰方法 ==》 解决线程安全问题
/**
* 线程安全问题
* @author 郑清
*/
public class Demo {
public static void main(String[] args) {
A a = new A();
MyThread t1 = new MyThread(a);
MyThread t2 = new MyThread(a);
t1.start();
t2.start();
}
}
class A{
private int tickets = 20;//记录车票的数量
/*
* 在方法定义时,添加一个synchronized修饰符
* 效果:
* 使用synchronized修饰的方法就是同步方法
* 特点: 1.效率会变低
* 2.没有线程安全问题了
* 原理:在方法添加synchronized之后,就相等于给当前对象(当前场景是a)加了一把锁.
* 锁的作用:当一个线程进入该方法时,先看锁还在不在,锁在:把锁取走
* 在第一个线程还没有结束时,第二个线程访问该方法,先看锁还在不在? 锁不在==》等待锁回来
* 第一个线程执行结束,把锁还回去,第二个线程发现锁回来了,把锁取走,开始执行方法
*/
public synchronized void getTicket(){
if(tickets>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
* 打印出来的结果有可能有重复的车票数量
* 甚至有负数,就是发生了线程安全问题
* 原因是:两个线程同时进入了此对象的getBean方法,读取的车票数量是另一个线程修改之前的值
*/
System.out.println("车票还剩: "+ (--tickets) + "张 !");
}
}
}
class MyThread extends Thread{
private A a;
public MyThread(A a) {
this.a = a;
}
public void run() {
while(true){
a.getTicket();
}
}
}
运行结果图:(如果没有使用synchronized 修饰方法 getTicket( ) ==》 可能就会出现下图2线程安全问题)
ex2 : 同步代码块 synchronized(被加锁的对象){ 代码 } ==》 解决线程安全问题
/**
* 同步代码块 :synchronized(被加锁的对象){ 代码 }
* @author 郑清
*/
public class Demo2 {
public static void main(String[] args) {
WC wc = new WC();
Person person1 = new Person(wc);
Thread t1 = new Thread(person1);
t1.setName("张三");
Person person2 = new Person(wc);
Thread t2 = new Thread(person2);
t2.setName("李四");
t1.start();
t2.start();
}
}
//上厕所
class WC{
//定义一个方法实现上厕所
public void test(){
try{
System.out.println(Thread.currentThread().getName()+" : 拿纸...");
Thread.sleep(1000);
/*
* 把上厕所的代码 放在 同步代码块中
* 该代码块中的代码在一个时间点只能被一个线程访问,其他线程需要排队等待
*/
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" : 上厕所...");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" : 出厕所");
}
System.out.println(Thread.currentThread().getName()+" : 上完厕所,真舒服,洗个手 ... end");
}catch (Exception e) {
// TODO: handle exception
}
}
}
class Person implements Runnable{
private WC wc;
public Person(WC wc) {
super();
this.wc = wc;
}
public void run() {
wc.test();
}
}
运行结果图: (如果不处理线程安全问题可能就会出现下图2情况:张三和李四同时在一个厕所上厕所 很恶心...哈哈)
ex3 : 锁机制Lock ==》 解决线程安全问题 (这个例子在ex2上修改而成的)
/**
* 使用Lock的步骤:
* 1.创建Lock实现类的对象
* 2.使用Lock对象的lock方法加锁
* 3.使用Lock对象的unlock方法解锁
* 注意:可把unlock方法的调用放在finally代码块中,保证一定能解锁
* @author 郑清
*/
public class Demo3 {
public static void main(String[] args) {
WC wc = new WC();
//自定义线程步骤③:创建自定义类对象
Person person1 = new Person(wc);
Person person2 = new Person(wc);
//自定义线程步骤④:创建Thread类对象
Thread t1 = new Thread(person1);
Thread t2 = new Thread(person2);
//给线程名称赋值
t1.setName("张三");
t2.setName("李四");
//自定义线程步骤⑤:启动线程
t1.start();
t2.start();
}
}
// 上厕所
class WC {
// 1.创建Lock实现类的对象 注意:需在test()方法外执行,如果在test()方法里执行,在test()方法被调用的时候就会被创建很多ReentrantLock对象,即出现很多锁
ReentrantLock lock = new ReentrantLock();
// 定义一个方法实现上厕所
public void test() {
System.out.println(Thread.currentThread().getName() + " : 拿纸...");
// 2.使用Lock对象的lock方法加锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " : 上厕所...");
System.out.println(Thread.currentThread().getName() + " : 出厕所");
System.out.println(Thread.currentThread().getName() + " : 上完厕所,真舒服,洗个手 ... end");
} finally {
// 3.使用Lock对象的unlock方法解锁
lock.unlock();
}
}
}
//自定义线程步骤①:创建自定义类 实现 Runnable接口
class Person implements Runnable {
private WC wc;
public Person(WC wc) {
super();
this.wc = wc;
}
//自定义线程步骤②:覆写run方法
public void run() {
wc.test();
}
}
运行结果图:(这里注意:在lock锁的地方 比如这个例子,图2中 张三上厕所的同时,李四可以去拿纸,但是不能去厕所!!)