/*********************************************************************************************/
// Java 多线程实现
java实现多线程有2种方法:1扩展java.lang.Thread类;2实现java.lang.Runnable接口
1、扩展Thread类
public class Hello extends Thread
{
public Hello(){}
public Hello(String name)
{
this.name = name;
}
public run()
{
for(int i = 0; i < 5; ++i)
System.out.println(name + “ Run ” + i);
}
// main
public static void main(String[] args)
{
Hello h1 = new Hello("A");
Hello h2 = new Hello("B");
h1.start();
h2.start();
}
}
2、实现Runable接口
public class Hello implements Runable
{
public Hello(){}
public Hello(String name)
{
this.name = name;
}
public run()
{
for(int i = 0; i < 5; ++i)
System.out.println(Thread.currentThread().getName() + “ Run ” + i);
}
// main
public static void main(String[] args)
{
Hello h1 = new Hello("A");
Thread demo1 = new Thread(h1, "h1");
Hello h2 = new Hello("B");
Thread demo2 = new Thread(h2, "h2");
demo1.start();
demo2.start();
}
}
比较: Runable比Thread具有优势
1) 适合多个相同的程序代码的线程去处理同一个资源// 在Thread中需要用synchronized来同步
2) 可以避免java中单继承的限制
3) 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
/***********************************************************************************************************/
MFC中有两类线程: 工作者线程和用户界面线程。
二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。
用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。
但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。
两种重载函数原型和参数分别说明如下:
(1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID pParam);
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
lpSecurityAttrs:线程的安全属性指针,一般为NULL;
(2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
pThreadClass 是指向 CWinThread的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。
使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。
下面我们对CWinThread类的数据成员及常用函数进行简要说明。
m_hThread:当前线程的句柄;
m_nThreadID:当前线程的ID;
m_pMainWnd:指向应用程序主窗口的指针
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
UINT nStackSize=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。
一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个
对象,然后调用该对象的成员函数CreateThread()来启动该线程。
virtual BOOL CWinThread::InitInstance();
重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。
virtual int CWinThread::ExitInstance();
在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同
InitInstance()成员函数一样,该函数也只适用于用户界面线程。
MFC中的所有线程都是由CWinThread对象表示。而进程是采用CreateProcess创建。
(1) 调用AfxBeginThread一次创建一个进程,该函数将为你创建CWinThread对象。
创建线程:
When calling the function, use this sentence: m_pThread = AfxBeginThread(ThreadFunc, &lpParam);
首先CREATE_SUSPENDED让他挂起,然后m_bAutoDelete=FALSE,接着才ResumeThread,最后还要delete那个指针
// --关键:在线程中可以接触到的信息就是lpParam了,因此这个类很重要,是线程与线程间的通信数据链。
结束线程:
m_pThread->SuspendThread();
m_pThread->ExitInstance();
TerminateThread(m_pThread->m_hThread, 0);
delete m_pThread;
m_pThread = NULL;
(2) 创建一个进程的另一种方法:
首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。
virtual BOOL CWinThread::InitInstance();
重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不
使用InitInstance()。
virtual int CWinThread::ExitInstance();
在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同
InitInstance()成员函数一样,该函数也只适用于用户界面线程。
注意:
如果用MFC编程,不要用CreateThread,如果只是使用Runtime Library,用_BegingThread。
CreateThread是由操作系统提供的接口,而AfxBeginThread和_BeginThread则是编译器对它的封装。
一般不要使用ExitThread,TerminateThread 结束一个线程;是一个比较危险的动作。
线程自己有个ThreadProc函数,在线程执行完函数体后,就能结束执行。另外,主线程结束,子线程也就结束。
(3) 多线程之间的关系:
// 等待多个线程
// ===================================================
CWinThread* pThread1 = AfxBeginThread(...);
CWinThread* pThread2 = AfxBeginThread(...);
HANDLE Handles[2];
Handles[0]=pThread1->m_hThread;
Handles[1]=pThread2->m_hThread;
WaitForMultipleObjects(2,Handles,TRUE,1000);
// WaitForSingleObject(handle, INFINITE);// wait one thread
// ====================================================
附录线程系列函数:
Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。
1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId);
该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:
lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;
dwStackSize:指定了线程的堆栈深度,一般都设置为0;
lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,
ThreadFunc是线程函数名;
lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;
dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
lpThreadId:该参数返回所创建线程的ID;
如果创建成功则返回线程的句柄,否则返回NULL。
2、DWORD SuspendThread(HANDLE hThread);
该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。 3、DWORD ResumeThread(HANDLE hThread);
该函数用于结束线程的挂起状态,执行线程。
4、VOID ExitThread(DWORD dwExitCode);
该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。
5、BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下:
hThread:将被终结的线程的句柄;
dwExitCode:用于指定线程的退出码。
使用TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。
6、BOOL PostThreadMessage(DWORD idThread,
UINT Msg,
WPARAM wParam,
LPARAM lParam);
该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。
idThread:将接收消息的线程的ID;
Msg:指定用来发送的消息;
wParam:同消息有关的字参数;
lParam:同消息有关的长参数;
调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。
(4) 多线程同步
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
//==========================================================
我使用最多的是互斥对象,因此解释下互斥对象的使用:
互斥(Mutex)是一种用途非常广泛的内核对象,能够保证多个线程对同一共享资源的互斥访问。
只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。
以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、ReleaseMutex()、WaitForSingleObject()、WaitForMultipleObjects()等。
//互斥对象句柄
HANDLE hMutex = NULL;
char g_cArray[10];
UINT ThreadProcA(LPVOID pParam)
{
//等待互斥对象通知
WaitForSingleObject(hMutex, INFINITE);
for(int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
//释放互斥对象
ReleaseMutex(hMutex);
return 0;
}
UINT ThreadProcB(LPVOID pParam)
{
//等待互斥对象通知
WaitForSingleObject(hEvent, INFINITE);
for(int i = 0; i < 10; i++)
{
g_cArray[10-i-1] = 'b';
Sleep(1);
}
//释放互斥对象
ReleaseMutex(hMutex);
return 0;
}
...
void CSampleDialog::OnThreadStart()
{
//创建互斥对象
hMutex = CreateMutex(NULL, FALSE, NULL);
AfxBeginThread(ThreadProcA, NULL);
AfxBeginThread(ThreadProcB, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
互斥对象在Mfc中通过CMutex类进行表述。
使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后,即可访问此互斥变量。
CMutex的父类也是CSyncObject,当然也是用Lock()和UnLock()成员函数来访问或释放CMutex对象。
//Mfc互斥类对象
CMutex g_clsMutex(FALSE, NULL);
char g_cArray[10];
UINT ThreadProcA(LPVOID pParam)
{
//等待互斥对象通知
g_clsMutex.Lock();
for(int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
//释放互斥对象
g_clsMutex.Unlock();
return 0;
}
UINT ThreadProcB(LPVOID pParam)
{
//等待互斥对象通知
g_clsMutex.Lock();
for(int i = 0; i < 10; i++)
{
g_cArray[10-i-1] = 'b';
Sleep(1);
}
//释放互斥对象
g_clsMutex.Unlock();
return 0;
}
...
void CSampleDialog::OnThreadStart()
{
AfxBeginThread(ThreadProcA, NULL);
AfxBeginThread(ThreadProcB, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}