Java学习-节点4

本文详细介绍了Java中的多线程概念,包括sleep、wait、notify和lock等方法的使用,并探讨了中断机制。此外,还讨论了自定义双向链表的实现,分析了与ArrayList和LinkedList的性能差异,以及Java中引用类型和基本类型在传参时的处理。
摘要由CSDN通过智能技术生成

第十四天

多线程学习

由于博主是转行搞开发的,对多线程并行并不太熟悉,之前从操作系统的书上看了一些,但是没有实际编写过,也不太清楚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) {
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值