java多线程知识点汇总(二)多线程实例解析

本实验主要考察多线程对单例模式的操作,和多线程对同一资源的读取,两个知识。实验涉及到三个类:

1)一个pojo类Student,包括set/get方法。

2)一个线程类,设置student的成员变量age和name的值为111和111

3)另一个线程类,设置student的成员变量age和name的值为222和2222

4)main类,for循环200次,分别创建200个线程1和线程2对同一资源访问。(共400个线程)

1.第一种情况:饿汉式单例模式保证多线程操控的是同一对象

//饿汉式单例模式pojo类
public
class Student { private String age = "12"; private String name = "Tome"; private static Student student = new Student();//类加载时候创建对象 public String getNameAndAge() { return name+":"+age; } public void setNameAndAge(String name,String age) { this.name = name; this.age = age; } private Student() //构造函数私有化 { } public static Student GetInstace() { //方法区函数,静态函数 return student; } }

线程2类:

public class MyThread extends Thread {
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Student.GetInstace().hashCode());
    }
}

测试类,创建并启动400个线程:

public class AppMain implements Runnable{

    public static void main(String[] args) {
        AppMain appMain = new AppMain();
        for(int i =0;i<200;i++)
        {
        Thread thread1 = new Thread(appMain);//线程1
        MyThread thread2 = new MyThread();//线程2
        thread1.start();
        thread2.start();
        }
    }    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Student.GetInstace().hashCode());
    }
}

结果:

2.第二种情况:共享资源的写方法不设置任何同步,多个线程可以交叉写数据

  public String getNameAndAge() {
        return name+":"+age;
    }
     public void setNameAndAge(String name,String age) { //没有设置任何写同步
        this.name = name;
        this.age = age;
    }

俩线程操控类:

public class MyThread extends Thread {
    @Override
    public void run() {
        // TODO Auto-generated method stub
        Student.GetInstace().setNameAndAge("111", "111");//设置name和age值为1
System.out.println(Student.GetInstace().getNameAndAge();); } }

线程2

public class AppMain implements Runnable{   
public static void main(String[] args) { AppMain appMain = new AppMain(); for(int i =0;i<200;i++) { Thread thread1 = new Thread(appMain); MyThread thread2 = new MyThread(); thread1.start(); thread2.start(); } } @Override public void run() { // TODO Auto-generated method stub Student.GetInstace().setNameAndAge("222", "2222");//设置name和age为2
System.out.println(Student.GetInstace().getNameAndAge();); } }

执行结果:

3.第三种情况:共享资源的写方法设置同步synchronized,保证同一时刻只有一个线程才能执行写,执行完后才释放锁。

  public String getNameAndAge() {
        return name+":"+age;
    }
    synchronized public void setNameAndAge(String name,String age) { //写方法设置synchronized了
        this.name = name;
        this.age = age;
    }

测试类添加打印:

public static void main(String[] args) {
        AppMain appMain = new AppMain();
        for(int i =0;i<200;i++)
        {
        Thread thread1 = new Thread(appMain);
        MyThread thread2 = new MyThread();
        thread1.start();
        thread2.start();
        System.out.println(Student.GetInstace().getNameAndAge());//添加打印,显示name和age值
        }
    }    

这样就能多个线程按序设置name和set值了。但为什么测试结果依然有脏数据呢?比如111:222这种脏数据呢?

答案:因为没设置单例对象读get方法的锁,这样读方法可以随时获取值,即使set线程还没执行完,因为没有synchronized限制可以随时访问。

4.第四种情况,共享资源的读方法不同步不synchronized,方便随时读取不受锁的限制。但就像之前说的,会读到写线程还没执行完时的数据,造成数据混乱。因为读线程可以随时读,没有锁的限制。

  public String getNameAndAge() { //读方法没有做同步synchronized处理,可以随时读取,就可以读出写线程未执行完的中间数据
        return name+":"+age;
    }
    synchronized public void setNameAndAge(String name,String age) {
        this.name = name;
        this.age = age;
    }

操作结果:

5.第五种情况,读方法也设置synchronized,锁的对象也是this。保证写的时候不能读,保证读的时候不能写。即读写用同一个锁。

    synchronized public String getNameAndAge() {
        return name+":"+age;
    }
    synchronized public void setNameAndAge(String name,String age) {
        this.name = name;
        this.age = age;
    }

测试结果:

这样数据就全部准确了,但是这样效率很低,因为读写共同设置一个锁。读的时候不能写,写的时候不能读。全部都是按序来访问。

结论:当多线程共同访问同一资源时候,此共享对象的读写方法,要都设置同一个锁,保证写的时候不能读,读的时候不能写,且读写都是按序执行。才能保证数据的准确性。

同时,也说明了,没有设置锁的方法可以随时执行,随时执行,随时可能被cpu调度以至打断线程的执行,以至读到线程执行一半产生的脏数据。

原文地址:https://www.cnblogs.com/panxuejun/p/5920450.html