第十四天
多线程学习
由于博主是转行搞开发的,对多线程并行并不太熟悉,之前从操作系统的书上看了一些,但是没有实际编写过,也不太清楚java里多线程是如何实现的。
今天来动手编程探究一下java具体如何实现多线程。
sleep
在Thread类中有一个sleep函数,用于将当前线程等待,在java API中有这样一句话
线程不会丢失任何显示器的所有权。 (翻译问题,The thread does not lose ownership of any monitors原意应该是说线程不会丢失监视器的所有权,也就是不会丢失锁的所有权)。
使用以下代码测试
public class Sleep extends Thread
{
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"inter");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
main:
int tNum=0;
public void test()
{
while(true)
{
tNum++;
Sleep s=new Sleep();
s.start();
if(tNum>1000)
break;
}
}
运行结果可以看到,在任务管理器中多出了1000个线程,并且每隔5秒,cpu的利用率会小幅上升一次,也就是说sleep方法确实释放了cpu的计算力。
另外值得注意的是,第一次运行时,输出基本是正向排序的,到了后面会越来越散乱,这跟Windows的调度方法有关。
那么接下来自然而然就会想到一个问题,既然线程被sleep了,那怎么唤醒它呢?查阅资料后,发现并无方法可以直接唤醒sleep的线程。只能通过中断的方法强行使线程产生中断。
public void run()
{
int n=0;
while(true)
{
System.out.println(Thread.currentThread().getName()+"inter"+n);
n=1;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("interrupted");
}
}
}
Main:
public void test()
{
Sleep s=new Sleep();
s.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s.interrupt();
}
很显然,这种方法属于一种取巧的方法。java的设计者并不是想让我们通过此种方法调用唤醒sleep的线程,事实上sleep仅仅用来充当一个定时器中断,两类sleep函数都规定了必须要等待的时间,我们无法sleep(-1)或者sleep(0)(sleep代码中并没有定义)来永久等待。那么想实现人为控制线程睡眠唤醒就需要使用wait方法。
wait
wait仅看字面意思同sleep是类似的,使线程等待一定时间,使用notify可以将其唤醒。
并且wait和notify都是定义在Object这个类中,即java所有对象的根父类,即java从底层上支持多线程实锤。
java API中关于wait的描述:
导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。 换句话说,这个方法的行为就好像简单地执行呼叫wait(0) 。
那么该怎么使用呢?我们如何确定wait的是哪一个线程,API中所说的当前是什么意思?
既然wait被绑定在一个Object上,那自然而然就想到了wait的线程是当前正在访问此object的线程,想到这里就又会出现一个问题,当多个线程同时访问一个对象时,那要怎么确定是哪个线程呢?
java API中的wait示例:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
所以wait一定要定义在synchronized关键字中,synchronized用于同步锁,即同一时间只能有一个线程访问synchronized控制下的对象。这样就很清楚了,使用synchronized来确定到底要wait哪一个线程。
为什么要多此一举呢?为什么不直接wait(线程名)来实现线程的挂起呢?
经查阅资料发现,java在过去使用suspend() 和resume()来实现阻塞和唤醒线程,如thread1.suspend()。但是这种方式很容易发生死锁,所以被java抛弃了。(不建议使用)
notify
跟wait结合使用,唤醒该被锁住的线程。若是同时有多个线程都在访问同一对象时被锁住了,可以使用notifyAll方法将他们都唤醒。
wait notify结合使用
public void run()
{
while(true)
{
synchronized(ThreadTest.d)
{
ThreadTest.d.num++;
//System.out.println(Thread.currentThread().getName()+" n:"+ThreadTest.d.num);
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(ThreadTest.d.num%2==0)
{
System.out.println(Thread.currentThread().getName()+" 2n "+ThreadTest.d.num);
try {
ThreadTest.d.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else
{
System.out.println(Thread.currentThread().getName()+" de2n "+ThreadTest.d.num);
ThreadTest.d.notify();
}
}
}
}
Main:
Wait w1=new Wait();
Wait w2=new Wait();
w1.start();
w2.start();
到了这里又会有一个新问题,在线程wait时,会自动释放被synchronized的对象,例如:
我们将wait类中的notify注释掉,在main中notifyAll
public void test()
{
Wait w1=new Wait();
Wait w2=new Wait();
w1.start();
w2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(d)
{d.notifyAll();}
}
会发现上一个线程wait时,下一个线程依然会获取该对象。就好像synchronized是循环,而wait是break一样,会自动被打破。
sleep会继续持有synchronized锁,而wait是会释放synchronized锁。但是sleep相当于线程休眠,并不是阻塞。
那么有什么方法可以在阻塞时保存住资源锁呢?
lock
lock是一种接口,提供了类似于synchronized的功能,但是synchronized是自动的内置关键字,lock可供程序员编程实现自己的方法。
java API中这么写到
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。
锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。
使用synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内。
虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。
示例:
public void run()
{
while(true)
{
ThreadTest.lock.lock();
ThreadTest.d.num++;
System.out.println(Thread.currentThread().getName()+" "+ThreadTest.d.num);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ThreadTest.lock.unlock();
}
}
Main:
public static Data d=new Data();
public static Lock lock=new ReentrantLock();
public void test()
{
Wait w1=new Wait();
Wait w2=new Wait();
w1.start();
w2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}