线程同步机制

1.线程安全问题概述

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

以上3例说明只有多线程访问共享的数据才会产生线程安全问题

2.线程安全问题的代码实现

public class DemoTicket{

//模拟卖票案例   创建3个线程,同时开启,对共享的票进行出售

public static void main(String[] args){

//创建Runnable接口的实现类对象

Runnable run = new RunnableImpl();//共享100张票

//创建Thread类对象,构造方法中传递Runnable接口的实现类对象

Thread t0 = new Thread(run);

Thread t1 = new Thread(run);

Thread t2 = new Thread(run);

//调用start方法开启多个线程

t0.start();t1.start();t2.start();

}

}

/*

实现卖票案例

*/

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();}//提高安全问题出现的概率,让程序睡眠

System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--

ticket--;}}

}

}

3.线程安全问题产生的原理

 

 因为t0,t1,t2都进入while循环中(共享数据区),此时3个线程都执行sleep()方法,从而失去cpu执行权,当sleep()方法结束,3个线程都在共享数据区,t2率先获得执行权,使ticket--至0,从而退出循环,可此时t0,t1都在循环中,即使ticket=0了,仍要执行完循环,从而出现ticket=-1;

 

 4.解决线程安全问题  同步代码块

为了保证每个线程都能正常执行原子操作,java引入了线程同步机制,那么怎么去使用呢,有三种完成同步操作

1.同步代码块

2.同步方法

3.锁机制

同步代码块格式:

synchronized(锁对象){

可能会出现线程安全问题的代码(访问了共享数据的代码)

}

注意:

1.同步代码块中的锁对象可以是任意对象

2.但是必须保证多个线程使用的锁对象是同一个

3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块运行

 

 //创建一个锁对象  创建在run方法外,不然线程每执行一次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--;}

}}}

 

 5.同步技术的原理

 

 

6.解决线程安全问题  同步方法

 

同步方法的使用步骤:

1.把访问了共享数据的代码块抽取出来,放到一个方法中

2.在方法上添加synchronized修饰符

格式:定义方法的格式

修饰符 synchronized 返回值类型 方法名(参数列表){

可能会出现线程安全问题的代码(访问了共享数据的代码)

}

 

//定义一个同步方法   同步方法也会把方法内部的代码锁住,只让一个线程执行,同步方法的锁对象是谁?就是实现类对象  new RunnableImpl(),也就是this

public synchronized void payticket(){

if(ticket>0){//票是否存在

try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠

System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--

ticket--;}

}

//使用时

while(true){ payticket(); }

 

7.静态同步方法

private static int ticket = 100;//将ticket设置为静态,否则同步方法中的ticket会报错

public  static synchronized void payticket(){

if(ticket>0){//票是否存在

try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠

System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--

ticket--;}

}

静态同步方法:

1.锁对象是谁?

不能是this,this是创建对象之后产生的,静态方法优先于对象,静态方法的锁对象是本类的class属性-->class文件对象(反射),则同步代码块可如下使用

public  static  /*synchronized*/ void payticket(){

synchronized(RunnableImpl.class){

if(ticket>0){//票是否存在

try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}//提高安全问题出现的概率,让程序睡眠

System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--

ticket--;}}

}

 

8.解决线程安全问题  Lock锁

Lock锁:

java.util.concurrent.locks.lock接口,lock接口实现提供比使用synchronized方法和语句可获得更广泛的锁定操作

lock接口中的方法:

void lock();获取锁

void unlock();释放锁

java.util.concurrent.locks.ReenrantLock implements lock接口

lock锁使用步骤:

1.在成员位置创建一个Reenrantlock对象

2.在可能出现线程安全问题的代码前调用lock接口中的方法lock()获取锁

3.在可能出现线程安全问题的代码后调用lock接口中的方法unlock()释放锁

 

 

public class RunnableImpl implements Runnable{

//定义一个多个线程共享的票源

private int ticket = 100;

1.在成员位置创建一个Reenrantlock对象

 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();}//提高安全问题出现的概率,让程序睡眠

System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");//票存在,卖票,票--

ticket--;}

//3.在可能出现线程安全问题的代码后调用lock接口中的方法unlock()释放锁

l.unlock();

}

}

}

 

更优化的Lock用法:

 

public class RunnableImpl implements Runnable{

//定义一个多个线程共享的票源

private int ticket = 100;

1.在成员位置创建一个Reenrantlock对象

 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();//移动到finally块

}

 

}

}

}

 

优化后的作用:无论程序是否出现异常,锁对象都会被释放

 

posted @ 2019-07-23 01:53  小蟠  阅读(205)  评论(0编辑  收藏  举报