今天我们聊聊线程同步的问题:我们知道在多线程共享数据时,有可能遇到多个线程同时处理同一个数据或者资源的问题如果这时候一个线程对数据进行增加操作,一个线程对数据进行删除操作我们知道这样是不行的,这时候我们就要用到线程的同步了,线程的同步就是为了解决数据共享的问题,所谓线程的同步就是指多个线程在同一时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才能继续执行。
线程进行同步有三种方式:
1.同步代码块:synchronized(要同步的对象){要同步的操作},即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
2.同步方法: public synchronized void method(){要同步的操作}即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
3.Lock(ReentrantLock):
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。 ReenreantLock类的常用方法有:ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁
下面我们通过模拟我们买火车票时的场景来测试一下我们的这三种同步方式:我们假设一趟火车有300张票,被分配到到各个售票站点,为了方便测试,我们假设还剩10张票,我们在两个售票站去卖这10张票,代码如下:
public class DamoServer {
/**
* @param args
*/
public static void main(String[] args) {
Damo4 dm=new Damo4();
Thread t1=new Thread(dm);
Thread t2=new Thread(dm);
t1.start();
t2.start();
}
}
class Damo4 implements Runnable{
private int ticket=10;
public void run() {
for (int i = 0; i <300; i++) {
if(ticket>0){
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你购买的火车票还剩余"+ticket+"张");
}
}
}
}
如果我们没有加线程的同步测试结果如下
我们会发现结果有重叠,同一张票被买了两次。下面我们给它加上同步方法的第一种,同步代码块,代码如下:
public class DamoServer {
/**
* @param args
*/
public static void main(String[] args) {
Damo4 dm=new Damo4();
Thread t1=new Thread(dm);
Thread t2=new Thread(dm);
t1.start();
t2.start();
}
}
class Damo4 implements Runnable{
private int ticket=10;
private Object obj=new Object();
public void run() {
for (int i = 0; i <300; i++) {
synchronized (obj) {//同步代码块 里面给的同步对象是任意的只要线程处理的是同一个对象即可
if(ticket>0){
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你购买的火车票还剩余"+ticket+"张");
}
}
}
}
}
测试结果如图所示: 我们发现现在卖的票正常了
下面我们把其他两种方法都实现一遍代码如下:同步方法的代码
public class DamoServer1 {
/**
* @param args
*/
public static void main(String[] args) {
Damo5 dm = new Damo5();
Thread t1 = new Thread(dm);
Thread t2 = new Thread(dm);
t1.start();
t2.start();
}
}
class Damo5 implements Runnable {
private int ticket = 10;
public void run() {
for (int i = 0; i < 300; i++) {
meted();
}
}
private synchronized void meted() {//同步方法
if (ticket > 0) {
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你购买的火车票还剩余" + ticket + "张");
}
}
}
Lock()方法代码如下:
public class DamoServer2 {
/**
* @param args
*/
public static void main(String[] args) {
Damo4 dm=new Damo4();
Thread t1=new Thread(dm);
Thread t2=new Thread(dm);
t1.start();
t2.start();
}
}
class Damo6 implements Runnable{
private int ticket=10;
ReentrantLock lock=new ReentrantLock();
public void run() {
for (int i = 0; i <300; i++) {
lock.lock();//开启锁
if(ticket>0){
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("你购买的火车票还剩余"+ticket+"张");
lock.unlock();//释放锁
}
}
}
}
以上是线程同步的3种方式,但是我们在用线程的同步方式时要注意几个准则,以避免线程的死锁:
我们在使用Lock时可以更加灵活的进行代码控制,但是一定要记得释放锁,在多线程共享数据时会有安全问题,使用同步方法可以解决安全问题,但是同时会牺牲性能
1.使代码块保持间短,把不随线程变化的预处理和后处理移出synchronize块
2.不要阻塞,如InputStream.read()方法等;
3.在持有锁的时候,不要对其他对象的同步方法进行调用!
就此收笔,欢迎各位大佬们指正