漫谈多线程(下)

接着上一篇继续学习多线程。

死锁(DeadLock)

当多线程共享资源时,各占一部分资源,而又在等待对方释放资源,这样的情况我们称为死锁。下面通过一个生动的程序来理解死锁。

class Program
    {
       private static object knife = new object();  //临界资源:刀子
        private static object fork = new object();   //临界资源:叉子

        //方法:拿起刀子
        static void GetKnife()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "拿起刀子. ");
        }

        //方法:拿起叉子
        static void GetFork()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "拿起叉子. ");
        }

        //方法:吃东西
        static void Eat()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "吃东西. ");
        }
        static void Main(string[] args)
        {
            //线程:女孩的行为
            Thread girlThread = new Thread(delegate()
            {

                Console.WriteLine("今天的月亮好美啊````");

                //过了一会儿,女孩子饿了,就去拿刀子和叉子
                lock (knife)
                {
                    GetKnife();

                    //* (待会儿会在这里添加一条语句)
                      Thread.Sleep(20);
                     lock (fork)
                    {
                        GetFork();
                        Eat();  //同时拿到刀子和叉子后开始吃东西
                        Console.WriteLine("女孩子放下叉子");
                        Monitor.Pulse(fork);
                    }
                    Console.WriteLine("女孩放下刀子");
                    Monitor.Pulse(knife);
                }
            });

            girlThread.Name = "女孩子";  //定义线程的名称

            //线程:男孩子的行为
            Thread boyThread = new Thread(delegate()
            {
                //男孩和女孩聊天
                Console.WriteLine("\n你更美!");

                lock (fork)
                {
                    GetKnife();

                    lock (knife)
                    {
                        GetKnife();
                        Eat();   //同时拿到刀子和叉子后开始吃东西

                        Console.WriteLine("男孩子放下刀");
                        Monitor.Pulse(knife);
                    }

                    Console.WriteLine("男孩子放下刀子");
                    Monitor.Pulse(fork);
                }
            });

            boyThread.Name = "男孩子";    //定义线程的名称

            //启动线程
            girlThread.Start();
            boyThread.Start();

        }
    }

当同时满足叉子、刀子的情况下才可以吃饭。正常情况下,这个程序是没有问题的。但是,有时候会出现死锁现象。例如,当女孩子拿起刀子,准备去拿叉子的时候,线程切换到了男孩子,男孩子也想吃饭,就拿起了叉子,当去拿刀子的时候,发现刀子被女孩子占有。所以,就等待女孩子释放刀子。此时,线程切换到女孩子。女孩子去拿叉子的时候,发现叉子被男孩子占有。所以就等待男孩子释放叉子。他们互相等待对方释放资源,这就造成了死锁。因为这个程序很多,一般不会出现死锁的现象,越是比骄长时间的交替执行线程,越容易造成死锁。我们在//*添加的Thread.Sleep(20),就是为了延长交替执行时间,让其出现死锁现象。运行程序,效果如下图:

卡在这里不动了,这就是死锁现象。

由此,我们可以发现,出现死锁的前提是:1.线程之间出现交替 2.交替过程中各占一部分资源

那么应该如何决解这个死锁问题呢?解决方案非常简单,其实不难想象。出现死锁的原因是,当线程A获取资源a后,准备获取资源b。但是此时资源被线程B获取。那么解决方案就是,让线程A、B顺序获取资源。就是说,如果线程B获取不到资源a就不允许它获取资源b.这样,就不会出现死锁的现象了。让我们把上面的代码重新修改一下:

class Program
    {
        private static object knife = new object();  //临界资源:刀子
        private static object fork = new object();   //临界资源:叉子

        //方法:拿起刀子
        static void GetKnife()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "拿起刀子. ");
        }

        //方法:拿起叉子
        static void GetFork()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "拿起叉子. ");
        }

        //方法:吃东西
        static void Eat()
        {
            Console.WriteLine(Thread.CurrentThread.Name + "吃东西. ");
        }
        static void Main(string[] args)
        {
            //线程:女孩的行为
            Thread girlThread = new Thread(delegate()
            {

                Console.WriteLine("今天的月亮好美啊````");

                //过了一会儿,女孩子饿了,就去拿刀子和叉子
                lock (knife)
                {
                    GetKnife();

                    //* (待会儿会在这里添加一条语句)
                    Thread.Sleep(20);
                    lock (fork)
                    {
                        GetFork();
                        Eat();  //同时拿到刀子和叉子后开始吃东西
                        Console.WriteLine("女孩子放下叉子");
                        Monitor.Pulse(fork);
                    }
                    Console.WriteLine("女孩放下刀子");
                    Monitor.Pulse(knife);
                }
            });

            girlThread.Name = "女孩子";  //定义线程的名称

            //线程:男孩子的行为
            Thread boyThread = new Thread(delegate()
            {
                //男孩和女孩聊天
                Console.WriteLine("\n你更美!");

                lock (knife)
                {
                    GetKnife();

                    lock (fork)
                    {
                        GetFork();
                        Eat();   //同时拿到刀子和叉子后开始吃东西

                        Console.WriteLine("男孩子放叉子");
                        Monitor.Pulse(fork);
                    }

                    Console.WriteLine("男孩子放下刀子");
                    Monitor.Pulse(knife);
                }
            });

            boyThread.Name = "男孩子";    //定义线程的名称

            //启动线程
            girlThread.Start();
            boyThread.Start();

        }
    }

 

 

线程池

我们通过Thread类来创建线程,并通过它控制线程,对线程进行一些操作。但是过多的创建线程,销毁线程,会消耗内存与CPU的资源。例如,同一时间,创建了100个线程。那么创建与销毁这些线程所需的时间,可能远远大于线程本身执行的时间。好在C#为我们提供了线程池(Thread Pool)的技术。线程池为我们创建若干个线程,当一个线程执行完任务时不会理解销毁,而是接收别的任务。线程池内的线程轮流工作。这样解决了,创建、销毁线程所带来的消耗了。线程池由命名空间System,Threading下的ThreadPool实现。ThreadPool是一个静态类,不用实例化对象,可以直接使用。一个程序中只能有一个线程池,它会在首次向线程池中排入工作函数时自动创建。下面我们看一段程序:

class Program
    {
        public static void ThreadPoolTest()
        {
            //向线程池中添加100个工作线程
            for (int i = 1; i <= 100; i++)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(WorkFunction), i);
            }
        }

        //工作函数
        public static void WorkFunction(object n)
        {
            Console.Write(n + "\t");
        }
        static void Main(string[] args)
        {
            ThreadPoolTest();
            Console.ReadKey();  //按下任意键结束程序
        }
    }

我们通过ThreadPool的QueueUserWorkItem()方法,向线程池中排入工作函数。线程池中的线程会轮流执行这些函数。QueueUserWorkItem()方法的参数是一个waitCallback类型的委托。

Public delegate void WaitCallback(object dataForFunctionj);

下面,我们通过研究线程池中的线程数量,来深一步的了解一下线程池。我们假设线程池内线程数量的上限为30,下限为10.当我们向线程池中排入工作函数时。线程池会为我们创建10个空线程,这10个空线程来处理工作函数。随着工作函数的数量大于下限10时,线程池不是立即创建新的线程。而是先检查一下这10个线程有没有空闲,如果有,就去接新的工作。50毫秒后,如果检查没有发现空闲线程,那么线程池就会创建新的线程。随着工作函数的增加,线程池内的线程也会增加,直到达到上限30.如果工作函数的数量超过上限,线程池内的线程也不会增加,一直使用30个线程工作。比如,排入100个任务,只有30个进入线程池,另外70个在池外等候。随着任务低于上限30,空闲的线程会在2分钟后回收释放。直到达到下限10为止。

由此,我们可以发现线程池提高效率的关键是,线程执行完任务后,不会马上回收,而是继续接其他任务。

在一下情况不宜使用线程池:

1.需要为线程设置优先级(线程池内的线程不受程序员控制)

2.在执行过程中需要对线程进行操作,例如睡眠,挂起等。

3.线程执行需要很长时间。(如果有些线程长时间占用线程池,那么对于线程池外排队的任务来说就是灾难)。

 

好了,多线程学习至此学完了。感觉一口气写下三篇技术文章,内心很有成就感。但是,有些话语组织的还是不好,技术讲解的也不是特别清楚。这些都需要改进,学如逆水行舟,不进则退。要坚持,要踏实。勤能补拙是良训,学习技术,一定不能手懒。要敲代码,要跑程序,要写博客,要善于总结。

时间: 2024-12-15 01:54:07

漫谈多线程(下)的相关文章

Java多线程21:多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask

CyclicBarrier 接着讲多线程下的其他组件,第一个要讲的就是CyclicBarrier.CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程都达到了这个屏障时,再一起继续执行后面的动作.看一下CyclicBarrier的使用实例: public static class CyclicBarrierThread extends Thread { private CyclicBarrier cb; private int sleep

Window下高性能IOCP模型队列多线程下应用

IOCP,先从概念上认识一下.IOCP全称I/O Completion Port,中文译为I/O完成端口.是Windows平台最高效的I/O模块,现在IIS服务器,就采用IOCP模型.IOCP是一个异步I/O的API,它可以高效地将I/O事件通知给应用程序.与使用select()或是其它异步方法不同的是,现在很多书,文字都直接将IOCP模块和网络编程关联起来,好像IOCP就是和网络打交道的.典型的IOCP模型的使用,是 将一个套接字(socket)与一个完成端口关联了起来,当一个网络事件发生的时

hashmap,hashtable,concurrenthashmap多线程下的比较(持续更新)

1.hashMap 多线程下put会造成死循环,主要是扩容时transfer方法会造成死循环. http://blog.csdn.net/zhuqiuhui/article/details/51849692(具体原因) 2.hashTable,使用synchornized保证线程安全,线程竞争竞争激烈的情况下,效率低下.当一下线程访问hashTable方法的时候,其他的线程会进入轮询或者阻塞的情况. 如果线程1是用put方法添加元素,线程2不能put元素也不能get元素,所以竞争越激烈越低. 3

HashMap简单源码及多线程下的死循环

主要记录hashMap的一些基本操作源码实现原理以及多线程情况下get()操作的死循环引发原因 一.hashMap简介 1.hashMap集合的主要属性及方法 (默认初始化容量)DEFAULT_INITIAL_CAPACITY = 16 (默认最大容量)MAXIMUM_CAPACITY = 1 << 30 (默认加载因子)DEFAULT_LOAD_FACTOR = 0.75f (Entry数组)Entry[] table (Entry实例的数量)size put(K key, V value)

HashMap为什么在多线程下会让cpu100%

首先HashMap并不是sun公司多线程提供的集合,很多时候我们的程序是一个主线程,用了hashmap并没有什么问题,但是在多线程下会出现问题. hashmap是一个哈希表,存储的数据结构也可以是一个线性数组,我们的存储的数据都在entry里,默认的大小是16, 因子是0.75  当达到16*0.75的时候就会扩充 把原来的数据transfer在新的扩充的容器里,在转换的时候如果是一个线程并没有什么问题,但是在多线程的时候,如果在临界点的时候,如果存储的值得hash值对数组的长度去摸一样,就会存

转发 FMDB多线程下&quot;is currently in use&quot; 或者 &quot;database is locked&quot; 问题

FMDB多线程下"is currently in use" 或者 "database is locked" 问题 问题一: "is currently in use" 出现的场景是这样的,多线程操作数据库,每个线程都使用了FMDatabase实例(注意没有使用FMDatabaseQueue). 问题二:“database is locked"出现的场景是这样的,多线程操作数据库,每个线程各自创建了FMDatabaseQueue实例操作数

多线程下HashMap与Hashtable

最近在多线程环境下操作HashMap,在程序中执行最多的是“查询”,但同时也要维护数据的“添加”和“删除” 早在开发前就知道Hashtable是同步的,而HashMap是异步的. 好吧在这里承认错误:限于没有犯过这个错误也没有见过实例场景,开发前未做好评估 现在说说我的这次需求吧: 1.要求新增一个功能页面,点击功能按钮弹出页面 2.页面前后台校验器两个(在这里不做追述) 3.提交修改后,提交按钮并disabled该按钮,页面特定区域显示动态Loading图标,并要求不影响主功能进程,若关闭弹出

多线程下的单例模式

参加一个面试,被问到多线程下的单例模式会创建几个对象,总结一下: 首先我的单例是这么写的(懒汉式) public class Singleton{ private static Singleton singleton; private Singleton(){} public Singleton getInstance(){ if(singleton == null){ singleton = new singleton(); } return singleton; } } 这样写的话, 当线程

java多线程下如何调用一个共同的内存单元(调用同一个对象)

1 /* 2 * 关于线程下共享相同的内存单元(包括代码与数据) 3 * ,并利用这些共享单元来实现数据交换,实时通信与必要的同步操作. 4 * 对于Thread(Runnable target)构造方法创建的线程,轮到它来享用CPU资源时. 5 * 目标对象就会自动调用接口中的run()方法 6 * */ 7 8 /* ----------------举例子------------------- */ 9 10 /* 11 * 使用Thread类创建两个模拟猫和狗的线程,猫和狗共享房屋中的一桶