上一篇介绍Java提供synchronized
关键字来实现多
线程同步。如下例所示:
代码:
class ThreadA implements
Runnable {
private Parcel7 p;
public ThreadA(Parcel7 p) {
this.p = p;
}
public void run() {
while (true) {
p.input();
}
}
}
class ThreadB implements Runnable {
private Parcel7 p;
public ThreadB(Parcel7 p) {
this.p = p;
}
public void run() {
while (true) {
p.output();
}
}
}
public class Parcel7 {
private int count = 0;
synchronized void input() {
count ++;
System.out.println(Thread.currentThread().getName() + "+++ " + count);
}
synchronized void output() {
count --;
System.out.println(Thread.currentThread().getName() + "--- " + count);
}
public static void main(String[] args) {
Parcel7 test = new Parcel7();
ThreadA a = new ThreadA(test);
ThreadB b = new ThreadB(test);
new Thread(a).start();
new Thread(b).start();
}
}
使用了两个synchronized 方法来使的对count的操作对于线程a和b是一个互斥的过程。但是如果我们我们想要使得a,b线程交替执行的话,我们需要使用到多线程之间的通信方式:wait(),notify(),notifyAll()。这三个方法是属于java.lang.Object类中的,即所有对象都包含这三个方法。wait()是使当前线程放弃CPU执行权限,并等待(会抛出一个 InterruptedException的
异常)。void notify()和void notifyAll()用来唤醒持有该对象锁线程,前者唤醒单个,后者唤醒所有。当然,这三者所操作的都是持有对象锁的线程,所以需要跟synchronized一起使用。
代码:
class ThreadA implements Runnable {
private Parcel7 p;
public ThreadA(Parcel7 p) {
this.p = p;
}
public void run() {
while (true) {
p.input();
}
}
}
class ThreadB implements Runnable {
private Parcel7 p;
public ThreadB(Parcel7 p) {
this.p = p;
}
public void run() {
while (true) {
p.output();
}
}
}
public class Parcel7 {
private int count = 0;
private boolean flag = false;
synchronized void input() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count ++;
System.out.println(Thread.currentThread().getName() + "+++ " + count);
flag = true;
this.notify();
}
synchronized void output() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count --;
System.out.println(Thread.currentThread().getName() + "--- " + count);
flag = false;
this.notify();
}
public static void main(String[] args) {
Parcel7 test = new Parcel7();
ThreadA a = new ThreadA(test);
ThreadB b = new ThreadB(test);
new Thread(a).start();
new Thread(b).start();
// new Thread(a).start();
// new Thread(b).start();
}
}
可以正确使线程a,b交替执行,因为每次a执行完一次之后都会,把b唤醒并自己进入wait状态,b被唤醒后做a同样的操作。但当每个任务都不止一个线程操作时,(把上例的
注释部分去掉)会出现如下情况:
【运行结果】
Thread-3--- 0
Thread-0+++ 1
Thread-1--- 0
Thread-0+++ 1
Thread-3--- 0
没有无限执行而是停住了。因为四个线程都陷入wait状态了。
解决办法:
将notify改成notifyAll,每次都把所有wait中的线程唤醒,主要是为了确保把执行不同操作的线程唤醒。而不是唤醒执行相同操作的线程。问题:使用notifyAll不仅把对方线程唤醒,还把本方线程也都唤醒了。如何只唤醒本方线程。
java5.0之前只能通过synchronized提供锁机制,而java5.0之后提供了一个
接口:Lock里面包含了lock()获取锁的方法,和unlock()主动
解锁的方法。并且将对象锁封装成一个接口Condition,通过Condition的实例对象来取代原本的对象锁。
代码:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ThreadA implements Runnable {
private Parcel7 p;
public ThreadA(Parcel7 p) {
this.p = p;
}
public void run() {
while (true) {
p.input();
}
}
}
class ThreadB implements Runnable {
private Parcel7 p;
public ThreadB(Parcel7 p) {
this.p = p;
}
public void run() {
while (true) {
p.output();
}
}
}
public class Parcel7 {
private int count = 0;
private boolean flag = false;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition2 = lock.newCondition();
void input() {
lock.lock();
try{
while (flag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count ++;
System.out.println(Thread.currentThread().getName() + "+++ " + count);
flag = true;
condition2.signal();
}finally{
lock.unlock();
}
}
void output() {
lock.lock();
try{
while (!flag) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count --;
System.out.println(Thread.currentThread().getName() + "--- " + count);
flag = false;
condition.signal();
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
Parcel7 test = new Parcel7();
ThreadA a = new ThreadA(test);
ThreadB b = new ThreadB(test);
new Thread(a).start();
new Thread(a).start();
new Thread(b).start();
new Thread(b).start();
}
}
对于synchronized每次只能持有一把锁,而对于Lock可以通过newCondition方法来获取好几个不同的对象锁。
总结:
synchronized与lock的区别:
1,ReentrantLock 拥有Synchronized相同的并发性和
内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事
2,synchronized是在
JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动 释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3,在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
停止线程:interrupt(),java.lang.Thread中的一个方法,用于将wait()、join()、sleep的线程中断状态清除并返回一个InterruptedException。
代码:
class ThreadA implements Runnable {
public synchronized void run() {
try {
this.wait();
} catch (InterruptedException e) {
System.out.println("从中断中恢复!");
}
}
}
public class Parcel7 {
public static void main(String[] args) {
ThreadA a = new ThreadA();
Thread b= new Thread(a);
b.start();
b.interrupt();
}
}
【运行结果】
从中断中恢复!
守护线程:是为其他线程的运行提供便利的线程。守护线程不会阻止程序的终止。Java的垃圾收集机制的某些实现就使用了守护线程。
非守护线程:包括常规的用户线程或诸如用于处理GUI事件的事件调度线程。
java.lang.Thread中的一个方法setDaemon(),用于将某线程设置为守护线程,必须在调用它的start方法之前进行设置,否则,抛出IllegalThreadStateException异常。程序只有守护线程时,该程序便可以结束运行。
代码:
class ThreadA implements Runnable {
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"守护线程!");
}
}
}
public class Parcel7 {
public static void main(String[] args) {
ThreadA a = new ThreadA();
Thread b= new Thread(a);
Thread c= new Thread(a);
b.setDaemon(true);
c.setDaemon(true);
b.start();
c.start();
}
}
当主线程结束时,两个守护线程也同时结束,虽然执行时while(true)的无限
循环;
等待线程结束:java.lang.Thread中的方法join();如果被interrupt()中断时抛出InterruptedException。也可以用来实现线程之间的同步。
代码:
class ThreadA implements Runnable {
private Parcel7 p;
public ThreadA(Parcel7 p) {
this.p = p;
}
public void run() {
for (int i = 0; i < 10; i++) {
p.inc();
}
}
}
public class Parcel7 {
private static int a =0;
void inc(){
a++;
}
public static void main(String[] args) {
ThreadA A = new ThreadA(new Parcel7());
Thread b= new Thread(A);
b.start();
System.out.println(a);
try {
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a);
}
}
【输出结果】
0
10
释放cpu执行权:yield()。该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。而sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。sleep和yield都不会释放对象锁。
实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。
sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。