多线程情况下的单例模式

有时想起 2016-01-15 10:32:04
看网上一些文档,都说经典模式的单例 没有考虑线程并发获取实例问题,即可能出现两个线程同时获取instance实例,且此时其为null时,就会出现两个线程分别创建了instance,违反了单例规则。
于是我就写了个代码试了试。但是输出的始终证明只创建了一次instance实例。。。。。。

namespace 单例设计模式
{
class Program
{
static void Main(string[] args)
{
new Thread(new ThreadStart(SingleTown.getInstance().print)) { Name="线程1",IsBackground=true}.Start();
new Thread(new ThreadStart(SingleTown.getInstance().print)) { Name = "线程2", IsBackground = true }.Start();
new Thread(new ThreadStart(SingleTown.getInstance().print)) { Name = "线程3", IsBackground = true }.Start();
new Thread(new ThreadStart(SingleTown.getInstance().print)) { Name = "线程4", IsBackground = true }.Start();
Console.Read();
}
}

class SingleTown
{
private static SingleTown instance = null;
private static int count = 0;
private SingleTown()
{
}
public static SingleTown getInstance()
{
if (instance == null)
{
instance = new SingleTown();
count++;
}
return instance;
}
public void print()
{
Console.WriteLine(Thread.CurrentThread.Name+"打印实例个数"+count);
}
}
}

输出,试了很多次
请大神鉴定下
...全文
530 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
猴头 2016-01-18
  • 打赏
  • 举报
回复
9 喽 说的挺好,主线程执行的时候 子线程一般 被挂起了,多核的电脑 例外,一直是主线程初始化 实例
  • 打赏
  • 举报
回复
嗯,#14 楼的代码已经是“两次 if 判断”的标准写法了。 我比较“手懒”,往往少写lock 语句之外的那个 if 判断。但是 lock 块之内的if 判断是不会少写的。
  • 打赏
  • 举报
回复
如果非要抠“同步”,一点点重复都不想,那么 lock 语句通常要这样写

if (instance == null)
{
            lock (_lock)
            {
                if (instance == null)
                {
                           ..............
  (
也就是说,在 lock 之前要判断一次,然后在 lock (线程同步代码块)内部还要再次判断。这恰好反映了并发的特点。 不过一般为了“手懒”,少写第一个 if 判断。这样写固然有一点性能问题(经常多余进出 lock 代码块),但是如果觉得影响不大,也就这样写了。
hhddzz 2016-01-16
  • 打赏
  • 举报
回复
Lazy<T>……
notax 2016-01-16
  • 打赏
  • 举报
回复
http://csharpindepth.com/Articles/General/Singleton.aspx
有时想起 2016-01-15
  • 打赏
  • 举报
回复
引用 15 楼 u011672494 的回复:
 class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
              //  new Thread(new ThreadStart(() => { SingleTown.getInstance().print(); })) { Name = "线程" + i, IsBackground = true }.Start();
              new Thread(new ThreadStart(fun)) { Name = "线程" + i, IsBackground = true }.Start();
            }

            Console.Read();
        }
        private static void fun()
        {
            SingleTown.getInstance().print();
        }
    }

    class SingleTown
    {
        private static SingleTown instance = null;
        private static object _lock = new object();
        private static int count = 0;
        private SingleTown()
        {
        }
        public static SingleTown getInstance()
        {
            if (instance == null)
            {
                lock (_lock)
                {
                Thread.Sleep(1000);
                instance = new SingleTown();
                count++;
                 }
            }
            return instance;
        }
        public void print()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "打印实例个数" + count);
        }
    }
已修改 但是这个样子加了lock不应该是输出count的值就只为1 实例一次吗 为什么输出却是 1 2 3 4 5
已经发现问题。。网上很多资料上写的这个第二种方法,这样是有问题的 正确做法:

 public static SingleTown getInstance()
        {
            lock (_lock)
            {
                if (instance == null)
                {

                    Thread.Sleep(1000);
                    instance = new SingleTown();
                    count++;
                }
            }
            return instance;
        }
结果就对了。。 第三种方法测试过 正常 因此以后用到的话还是选择第三种吧简洁高效
有时想起 2016-01-15
  • 打赏
  • 举报
回复
 class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
              //  new Thread(new ThreadStart(() => { SingleTown.getInstance().print(); })) { Name = "线程" + i, IsBackground = true }.Start();
              new Thread(new ThreadStart(fun)) { Name = "线程" + i, IsBackground = true }.Start();
            }

            Console.Read();
        }
        private static void fun()
        {
            SingleTown.getInstance().print();
        }
    }

    class SingleTown
    {
        private static SingleTown instance = null;
        private static object _lock = new object();
        private static int count = 0;
        private SingleTown()
        {
        }
        public static SingleTown getInstance()
        {
            if (instance == null)
            {
                lock (_lock)
                {
                Thread.Sleep(1000);
                instance = new SingleTown();
                count++;
                 }
            }
            return instance;
        }
        public void print()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "打印实例个数" + count);
        }
    }
已修改 但是这个样子加了lock不应该是输出count的值就只为1 实例一次吗 为什么输出却是 1 2 3 4 5
newtee 2016-01-15
  • 打赏
  • 举报
回复
    /// <summary>
    /// 单例模式
    /// </summary>
    /// <typeparam name="T">T</typeparam>
    public class SingletonProvider<T> where T : new()
    {
        private static T m_instance;
        private static readonly object sync = new object();
        private SingletonProvider() { }

        /// <summary>
        /// 获取该类的单例 
        /// </summary>
        public static T Instance
        {
            get
            {
                if (m_instance == null)
                {
                    lock (sync)
                    {
                        if (m_instance == null)
                        {
                            m_instance = new T();
                        }
                    }
                }
                return m_instance;
            }
        }
    }
这样
  • 打赏
  • 举报
回复
正如上面已经有2层楼所说的,你的4行代码
SingleTown.getInstance().print
都是在主线程执行的,所以这个例子设计得出了问题。
xdashewan 2016-01-15
  • 打赏
  • 举报
回复
引用 3 楼 u011672494 的回复:
结果一样,这样不行
把你的SingleTown.getInstance().print放入一个方法里运行
xdashewan 2016-01-15
  • 打赏
  • 举报
回复
引用 3 楼 u011672494 的回复:
结果一样,这样不行
你没发现你在构造thread的时候就在调用单例模式吗,这时候线程都没start
  • 打赏
  • 举报
回复
嗯,你的代码有误,在实例化 Instance 方面,你的代码是顺序的。 可以这样改一下:
class Program
{
    static void Main(string[] args)
    {
        new Thread(new ThreadStart(() => SingleTown.getInstance().print())) { Name = "线程1", IsBackground = true }.Start();
        new Thread(new ThreadStart(() => SingleTown.getInstance().print())) { Name = "线程2", IsBackground = true }.Start();
        new Thread(new ThreadStart(() => SingleTown.getInstance().print())) { Name = "线程3", IsBackground = true }.Start();
        new Thread(new ThreadStart(() => SingleTown.getInstance().print())) { Name = "线程4", IsBackground = true }.Start();
        Console.Read();
    }
}

class SingleTown
{
    private static SingleTown instance = null;
    private static int count = 0;
    private SingleTown()
    {
    }
    public static SingleTown getInstance()
    {
        if (instance == null)
        {
            Thread.Sleep(1000);
            instance = new SingleTown();
            count++;
        }
        return instance;
    }
    public void print()
    {
        Console.WriteLine(Thread.CurrentThread.Name + "打印实例个数" + count);
    }
}
本拉灯 2016-01-15
  • 打赏
  • 举报
回复
要多试几次,就会有2出现了。
Poopaye 2016-01-15
  • 打赏
  • 举报
回复
1. 你的代码里调用getInstance还是在主线程里完成的,不存在并发 2. 即便修改代码使得getInstance进入了并发状态,多数情况下count还是等于1,所以你看不出出来 你应该这么改
class Program
{
	static void Main(string[] args)
	{
		Thread[] ths = new Thread[]
		{
			new Thread(NewThreadMethod) { Name = "线程1" }, //使用单独函数调用getInstance
			new Thread(NewThreadMethod) { Name = "线程2" },
		};
		foreach (var item in ths)
			item.Start();
		Console.Read();
	}

	static void NewThreadMethod()
	{
		Thread.Sleep(500);
		SingleTown.getInstance().print();
	}
}

class SingleTown
{
	private static SingleTown instance = null;
	private static int count = 0;
	private SingleTown()
	{
	}
	public static SingleTown getInstance()
	{
		if (instance == null)
		{
			instance = new SingleTown();
			Interlocked.Increment(ref count); //保证count能够+1
		}
		return instance;
	}
	public unsafe void print()
	{
		Console.WriteLine(Thread.CurrentThread.Name + "打印实例个数" + count);
	}
}
  • 打赏
  • 举报
回复
运行1000万次,总会有一次中彩的。你只是测试1次,不行。
  • 打赏
  • 举报
回复
public static SingleTown getInstance()
        {
            if (instance == null)
            {
Thread.Sleep(1000);//在这里加上这段
                instance = new SingleTown();
                count++;
            }
            return instance;
        }
本拉灯 2016-01-15
  • 打赏
  • 举报
回复
new Thread(new ThreadStart(SingleTown.getInstance().print)) { Name="线程1",IsBackground=true}.Start(); new Thread(new ThreadStart(SingleTown.getInstance().print)) { Name = "线程2", IsBackground = true }.Start(); 你这样写是有问题的。你在定义线程时就实例了(SingleTown.getInstance() 这个了所以你在线程启动后只会得到一个Count等于1 你应在线程的方法体里去调用,他就会变成2,因为线程同时调用了SingleTown.getInstance() 改成这样你就理解了


  class Program
    {
        static void Main(string[] args)
        {
            var t1 = new Thread(new ThreadStart(Fun)) { Name = "线程1", IsBackground = true };
            var t2 = new Thread(new ThreadStart(Fun)) { Name = "线程2", IsBackground = true };
            var t3 = new Thread(new ThreadStart(Fun)) { Name = "线程3", IsBackground = true };
            var t4 = new Thread(new ThreadStart(Fun)) { Name = "线程4", IsBackground = true };
            t1.Start();
            t2.Start();
            t3.Start();
            t4.Start();



           //for (var i = 0; i < 5; i++)
           //{
           //    var t = new Thread(new ThreadStart(Fun)) { Name = "线程1", IsBackground = true };
           //    t.Start();
           //}

        

            Console.Read();
        }
        private static void Fun()
        {
            SingleTown.getInstance().print();
        }
    }

有时想起 2016-01-15
  • 打赏
  • 举报
回复
引用 1 楼 xunzaosiyecao 的回复:
http://blog.csdn.net/jiankunking/article/details/50319493 用第三种吧 static 变量从clr层面上就保证了 只会初始化一次
指count变量吗,被初始化一次、但要是出现两个线程同时获取instance实例的话 count还是会+的
有时想起 2016-01-15
  • 打赏
  • 举报
回复
引用 2 楼 xdashewan 的回复:
instance = new SingleTown();前加个sleep模拟延长处理时间
结果一样,这样不行
衣舞晨风 2016-01-15
  • 打赏
  • 举报
回复
http://blog.csdn.net/jiankunking/article/details/50319493 用第三种吧 static 变量从clr层面上就保证了 只会初始化一次
加载更多回复(3)

111,129

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Creator Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧