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调度以至打断线程的执行,以至读到线程执行一半产生的脏数据。

时间: 2024-10-17 06:30:25

java多线程知识点汇总(二)---多线程实例解析的相关文章

java多线程知识点汇总(四)多线程知识点脉络图

1.多线程安全问题 1)synchronized关键字:如何加锁的问题,选择synchronized方法还是synchnized代码块. 选择哪个锁问题,this对象,还是class对象(针对static方法或者变量),或者object对象 推荐使用同步代码块,因为加锁灵活,可以自己选择使用哪个锁,尤其一个类里面有多个同步方法时. 读写问题,保证多线程对同一对象的读写正常,不至于读到脏数据.读写方法共用同一个锁. 共享资源,这又涉及到单例模式的线程安全问题,线程池等. 2)线程安全类,比如str

多线程知识点(二)

1:互斥锁,只有一个线程进行(例如:读写)操作 2.栈区上面的对象随时可能销毁,ARC下block会自动保存到堆区 //NSGlobalBlock全局区(未使用局部变量) //    void (^task)() = ^{ //        NSLog(@"task"); //    }; //    NSLog(@"%@",task); // //    int a = 6; //    //NSStackBlock  栈区上面的对象随时可能会被销毁 //   

一个简单的Java模板工具类(二)—简单表达式解析实现

以前写过一个, 用正则比较不高效, 所以用表达式解析方式又实现了一个, 练手. 以前的: http://my.oschina.net/haogrgr/blog/222349 现在的: import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; /**  * 非常非常简单的模板实现  *   * @author desheng.tu  * @date 2015年

java&amp;android知识点汇总整理(不定期更新)

1 .java中,有的方法的参数类型是charsequence,这时候带入的参数实际上是字符串,这是怎么回事呢????? 答: CharSequence是String实现的一个接口,相当简单,就是要求是一串字符.所以每个参数类型是CharSequence的方法,都可以实际代入String对象. 为什么这些方法不干脆定义String作为参数类型?因为还有其他的CharSequence类型的类,比如StringBuffer和StringBuilder这两个很重要的类.String对象是不可变的,这两

[clone]Java中的深拷贝和浅拷贝 实例解析

我们平时在开发中经常用到clone这个Object类的方法,但是super.clone()方法所返回的拷贝是浅拷贝,(所谓浅拷贝和深拷贝是相对的,浅拷贝中的内部对象与原始对象的内部对象是共享的,是同一个:而深拷贝中的内部对象也是不同的.),有些情况下,我们需要得到对象的深拷贝,如下面的情况 package day0815; import java.io.File; import java.util.Stack; import org.junit.Test; public class BaseTr

JAVA学习知识点汇总

异常处理 1.在异常处理中,如果子类覆盖了父类的一个方法,子类方法中声明的已检查异常不能比父类方法声明的异常更通用,即子类方法可以抛出更特定的异常(不超过父类方法声明的异常范围,即由父类方法声明异常派生的子异常),或者不抛出任何异常.并且,如果父类方法没有抛出异常,子类也不能抛出任何已检查异常. 个人理解:由于面向对象具有多态的特性,即父类可以引用子类对象,通过父类访问子类方法实现多态(重写).在这个调用链中,子类方法不能抛出父类方法无法接受的异常,即父类声明的异常应该更通用.

Java 面试知识点解析(二)——高并发编程篇

前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部分内容参照自这一篇文章,有一些自己补充的,也算是重新学习一下 Java 吧. 前序文章链接: Java 面试知识点解析(一)--基础知识篇 (一)高并发编程基础知识 这里涉及到一些基础的概念,我重新捧起了一下<实战 Java 高并发程序设计>这一本书,感觉到心潮澎湃,这或许就是笔者叙述功底扎实的

Java核心知识点学习----多线程 倒计时记数器CountDownLatch和数据交换的Exchanger

本文将要介绍的内容都是Java5中的新特性,一个是倒计时记数器---CountDownLatch,另一个是用于线程间数据交换的Exchanger. 一.CountDownLatch 1.什么是CountDownLatch? 倒计时计数器,调用CountDownLatch对象的CountDown()方法就将计数器减一,当计数到达0时,则所有等待者或者全部等待者开始执行. 2.如何用? new CountDownLatch(1); 直接new,其构造函数必须传一个int类型的参数,参数的意思是: c

Java基础学习笔记二十一 多线程

多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序. 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 什么是多线程呢?即就是一个程序中有多个线程在同时执行.通过下图来区别单线程程序与