Java Code之线程与同步(一)

模拟一个实例,用一个线程负责写数据,一个线程负责读数据,清单如下:

FileManager.java 负责读和写的具体实现
Main.java 主类,入口
TaskThread.java 自定义线程类
TaskRead.java 读任务
TaskWrite.java 写任务

 

FileManager.java

package com.iteye.badpie.javacode.thread.sync;

import java.util.Random;

public class FileManager {

    private Random mRandom;
    private String mName = "默认名字";
    private String mEmail = "默认电子邮件";

    public FileManager() {
        mRandom = new Random();
    }

    public void write(String id, String name, String email) {
        mName = name;
        try {
            Thread.sleep(getRandomInt());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mEmail = email;
        System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email));
    }

    public void read(String id) {
        try {
            Thread.sleep(getRandomInt());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail));
    }

    private int getRandomInt() {
        return mRandom.nextInt(2000);
    }

}

 

Main.java

package com.iteye.badpie.javacode.thread.sync;

import com.iteye.badpie.javacode.thread.sync.TaskThread.ITask;

public class Main {

    public static void main(String[] args) {
        System.out.println("主线程开始运行");

        FileManager fileManager = new FileManager();
        ITask taskWrite = new TaskWrite("w", fileManager);
        ITask taskRead = new TaskRead("r", fileManager);

        new TaskThread(taskWrite).start();
        new TaskThread(taskRead).start();

        System.out.println("主线程运行结束");
    }

}

 

TaskThread.java

package com.iteye.badpie.javacode.thread.sync;

public class TaskThread extends Thread {

    private ITask mTask;

    public TaskThread(ITask task) {
        mTask = task;
    }

    @Override
    public void run() {
        mTask.execute();
    }

    public interface ITask {
        public void execute();
    }

}
 

TaskRead.java

package com.iteye.badpie.javacode.thread.sync;

import com.iteye.badpie.javacode.thread.sync.TaskThread.ITask;

public class TaskRead implements ITask {

    private String mId;
    private FileManager mFileManager;

    public TaskRead(String id, FileManager fileManager) {
        mId = id;
        mFileManager = fileManager;
    }

    @Override
    public void execute() {
        for (int i = 0; i < 5; i++) {
            mFileManager.read(mId);
        }
    }

}

 

TaskWrite.java

package com.iteye.badpie.javacode.thread.sync;

import com.iteye.badpie.javacode.thread.sync.TaskThread.ITask;

public class TaskWrite implements ITask {

    private String mId;
    private FileManager mFileManager;

    public TaskWrite(String id, FileManager fileManager) {
        mId = id;
        mFileManager = fileManager;
    }

    @Override
    public void execute() {
        for (int i = 0; i < 5; i++) {
            mFileManager.write(mId, i + "的名字", i + "的电子邮件");
        }
    }

}

 

某一次运行结果如下

主线程开始运行
主线程运行结束
写数据 id:w, name:0的名字, email:0的电子邮件
读数据 id:r, name:1的名字, email:0的电子邮件
写数据 id:w, name:1的名字, email:1的电子邮件
读数据 id:r, name:2的名字, email:1的电子邮件
读数据 id:r, name:2的名字, email:1的电子邮件
写数据 id:w, name:2的名字, email:2的电子邮件
读数据 id:r, name:3的名字, email:2的电子邮件
写数据 id:w, name:3的名字, email:3的电子邮件
读数据 id:r, name:4的名字, email:3的电子邮件
写数据 id:w, name:4的名字, email:4的电子邮件

  分析一下结果,有明显的问题,写数据时,0的名字和0的电子邮件,1的名字和1的电子邮件等等,他们是一一对应的,但是在第一次读数据时就出现了数据错误,竟然把1的名字和0的电子邮件配对了。

可能导致此问题的原因是:在写数据时,当前数据只写了一半,就有读操作产生,于是就将本次的前半段数据(比如1的名字)和上一次的后半段数据(比如0的电子邮件)配对了。

解决此问题的方法是对写和读做同步,意思是说当我在写数据时就不允许读,在读数据时,不允许写,这样每次写或者读总是能够保证数据是统一的,配对是正确的。

java中的每一个实例对象都有一个锁,称之为lock,就像这样的场景:数据在屋子里,门上有一把锁,读数据的人和写数据的人都在门外排队,一个人进屋就将门反锁住,他读或者写完毕了打开门出来,再让下一个人进屋,这个人再将们反锁住,于是,这就能保持屋子里的数据始终是配对的,不会将第一个人的一半数据和第二个人的一半数据配对。

实现方式如下:

只需要对此处的FileManager做一个小小的改动就可以了(由synchronized关键字负责锁门)

package com.iteye.badpie.javacode.thread.sync;

import java.util.Random;

public class FileManager {

    private Random mRandom;
    private String mName = "默认名字";
    private String mEmail = "默认电子邮件";
    private Object mLock;

    public FileManager() {
        mRandom = new Random();

        // 实例化一个锁
        mLock = new Object();
    }

    public void write(String id, String name, String email) {

        // 锁门
        synchronized (mLock) {
            mName = name;
            try {
                Thread.sleep(getRandomInt());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mEmail = email;
            System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email));
        }
        // 打开门

    }

    public void read(String id) {

        // 锁门
        synchronized (mLock) {
            try {
                Thread.sleep(getRandomInt());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail));
        }
        // 打开门

    }

    private int getRandomInt() {
        return mRandom.nextInt(2000);
    }

}

再次执行,结果如下:

主线程开始运行
主线程运行结束
读数据 id:r, name:默认名字, email:默认电子邮件
读数据 id:r, name:默认名字, email:默认电子邮件
读数据 id:r, name:默认名字, email:默认电子邮件
读数据 id:r, name:默认名字, email:默认电子邮件
读数据 id:r, name:默认名字, email:默认电子邮件
写数据 id:w, name:0的名字, email:0的电子邮件
写数据 id:w, name:1的名字, email:1的电子邮件
写数据 id:w, name:2的名字, email:2的电子邮件
写数据 id:w, name:3的名字, email:3的电子邮件
写数据 id:w, name:4的名字, email:4的电子邮件

 上面的锁也可以这样挂:

package com.iteye.badpie.javacode.thread.sync;

import java.util.Random;

public class FileManager {

    private Random mRandom;
    private String mName = "默认名字";
    private String mEmail = "默认电子邮件";

    public FileManager() {
        mRandom = new Random();
    }

    public void write(String id, String name, String email) {

        // 锁门 对当前实例挂锁
        synchronized (this) {
            mName = name;
            try {
                Thread.sleep(getRandomInt());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mEmail = email;
            System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email));
        }
        // 打开门

    }

    public void read(String id) {

        // 锁门
        synchronized (this) {
            try {
                Thread.sleep(getRandomInt());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail));
        }
        // 打开门

    }

    private int getRandomInt() {
        return mRandom.nextInt(2000);
    }

}

 还可以这样挂锁:

package com.iteye.badpie.javacode.thread.sync;

import java.util.Random;

public class FileManager {

    private Random mRandom;
    private String mName = "默认名字";
    private String mEmail = "默认电子邮件";

    public FileManager() {
        mRandom = new Random();
    }

    public synchronized void write(String id, String name, String email) {
        mName = name;
        try {
            Thread.sleep(getRandomInt());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mEmail = email;
        System.out.println(String.format("写数据 id:%s, name:%s, email:%s", id, name, email));
    }

    public synchronized void read(String id) {
        try {
            Thread.sleep(getRandomInt());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(String.format("读数据 id:%s, name:%s, email:%s", id, mName, mEmail));
    }

    private int getRandomInt() {
        return mRandom.nextInt(2000);
    }

}

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java,thread,线程,同步)