java

来源:互联网 发布:java 整型转字符串 编辑:程序博客网 时间:2024/05/15 23:45

1    什么情况下使用多线程

为了等待网络、文件系统、用户或者其他I/O响应而耗费大量的执行时间

 

2    线程同步问题

锁是线程控制的重要途径。Java为此也提供了2种锁机制,synchronized和lock。

 

1  synchronized:类似于面向对象,修饰类、方法、对象。把任何一个非null对象作为锁,在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。当synchronized作用在方法上时,锁住的便是对象实例(this),当作用在静态方法时锁住的便是对象对应的class实例,因为class数据存在于永久带,因此静态方法锁相对于该类的一个全局锁,当synchronized作用于某一个对象实例时,锁住的便是对应的代码块。

 

2   lock:不作为修饰,类似于面向过程,在方法中需要锁的时候lock,在结束的时候unlock。需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

先举一个例子,用这两种方式实现,了解下它们的使用方法

疯狂java讲义上的银行取钱例子

 

 

3    具体讲解下这两种锁

 

主要相同点:Lock能完成synchronized所实现的所有功能

 

主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。(lock提供了机制主动的去控制锁的释放,更灵活,

  线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,

如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

因为ReentrantLock提供了三种方式获取锁定

  a)  lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

  b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

  c)tryLock(long timeout,TimeUnit unit),   如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

  d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。

 正因为它锁的释放由程序员去控制手动释放,所以如果没有释放,会造成安全隐患

 

一、synchronized和lock的用法区别

 

synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。

 

lock:需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

 

二 、性能区别

 

synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。

synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

 

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。

现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

 

 

但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等解决synchronized低性能问题。导致在Java1.6上synchronize的性能并不比Lock差多少。

 

三 用途区别

 

虽然lock的性能在高并发的时候,高于synchronize,但是对于 java.util.concurrent.lock 中的锁定类来说,synchronized 仍然有一些优势。

 

1在退出 synchronized 块时,JVM会自动释放锁,但如果使用loc k忘记finally 块释放锁,这对程序非常有害。您的程序能够通过测试,但会在实际工作中出现死锁,那时会很难指出原因。

2当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。不方便定位异常来源

3lock不如synchoronized那样熟悉

 

 

什么时候选择用 ReentrantLock 代替 synchronized

 

在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。

 

4    线程的通信问题

 

当线程在系统内运行的时候,线程的调度具有一定的透明性,程序通常无法准确的控制线程的轮换执行,我们可以通过一定的机制保证线程的协调运行

 

 

1)  object类提供的wait \notify 、notifyall这三个方法,这三个方法并不属于thread类而是object类,但是这三个方法必须由同步监视器对象来调用

对于synchronized修饰的同步方法,该类的默认实例就是同步监视器,所以可以在同步方法中直接调用这三个方法

同步代码块,同步监视器是括号里的对象,所以必须使用该对象来调用这三个方法

 

2)  使用condition来控制线程通信

如果程序不使用synchronized关键字来保证同步而是直接使用lock,则系统中不存在隐式的同步监视器,也就不能使用上面那三个方法

Condition代替了同步监视器的功能

Condition实例被绑定在一个lock对象上,要获得lock实例的condition实例,调用lock对象的newConditon,

 

Await

 

Signal

Signalnall

 

 

3)  使用阻塞队列(blockingqueue)

虽然blockingqueue是queue的子接口,但是它的主要用途不是作为容器而是作为线程同步的工具,它具有一个特征,当生产者线程试图向blockingqueue中放入元素的时候,如果该队列已满,则该线程阻塞,当消费者试图从blockingqueue中取出元素的时候,如果队列空,则线程阻塞

Put

Take

 

1 什么情况下使用多线程当需要处理大量的IO操作时或处理的情况需要花大量的时间比如文件读写、视频下载播放、等待网络传输等耗费大量时间的操作ps:(CPU是以时间片的方式为进程分配CUP处理时间的,如果把CUP看作是一共有100个时间片的话,CPU可能一直都只是花了其中的10个时间片来处理当前进程所要做的事情,只是用到了CPU的10%的时间片,而其他时间都白白浪费了,当然,实际上CPU的工作模式还是做完一件事以后再去做另一件事,只是CUP的处理速度非常快,很快就处理完成所请求的情事。 所以我们采用多线程主要就是为了提高CPU的使用率。)psp:(当多个线程同时运行的时候,不能控制运行的顺序,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。)2
0 0