这是一道比较经典的面试题:
三个线程打印ABC的问题,对(),()应用的最好理解。题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:
1、多线程if和where的区别:
就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
2、
一.概述 wait()方法可以使线程进入等待状态,而notify()可以使等待的状态唤醒
wait,notify和notifyAll方法是Object类的成员函数,所以Java的任何一个对象都能够调用这三个方法。这三个方法主要是用于线程间通信,协调多个线程的运行。
大家要知道,在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步都是通过synchronized来实现的,一般来说多个线程互斥访问某个资源,用synchronized就够了,但如果需要在线程间相互唤醒的话就需要借助(), ()了。
二.wait函数
打个比方:wait函数表示:我累了,想休息一会儿(同时本线程休眠),对象的锁你们拿去用吧,CPU也给你们。
需要注意一下几点:
1.调用了wait函数的线程会一直等待(本线程休眠),直到有其他线程调用了同一个对象的notify或者notifyAll方法才能被唤醒。如果没有其他线程调用该对象的notify或者notifyAll方法,则该线程将会永远等下去…
2.被唤醒并不代表立即获得对象的锁。
也就是说
函数必须在同步代码块中调用(也就是当前线程必须持有对象的锁)
注意 wait和sleep不同,sleep是不会释放对象锁的,但是wait是会释放对象锁的。
三.notify和notifyAll方法
还是先打个比方:女士们,先生们请注意了,这个锁的对象我即将用完,请大家醒醒(这是在唤醒该加锁对象的线程),准备一下,马上你们就能使用锁了。
需要注意一下几点:
/notifyAll方法也必须在同步代码块中调用(也就是调用线程必须持有对象的锁)
方法只会唤醒一个正在等待的线程(至于唤醒谁,不确定!),而notifyAll方法会唤醒所有正在等待的线程
3.调用notify和notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块执行完才会让出锁。
4.如果一个对象之前没有调用wait方法,那么调用notify方法是没有任何影响的。
四、lock Condition
一般而言,java中锁是在多线程环境下访问共享资源的一种手段。
一般的锁可以防止多个线程同时访问共享资源,但是也有些锁可以允许多个线程并发的访问共享资源,比如读写锁。
在java5之前,要实现锁,只有依靠synchronized
关键字来隐式的获取和释放锁。
在java5中,提供了另一种显式地获取和释放锁的技术。可以通过接口来实现。
是一种重入锁。也就是说该锁支持一个线程对资源的重复加锁
。在调用lock()
方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
五、信号量
在Java的并发包中,Semaphore类表示信号量。Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理。Semaphore有两个构造函数,参数permits表示许可数,它最后传递给了AQS的state值。线程在运行时首先获取许可,如果成功,许可数就减1,线程运行,当线程运行结束就释放许可,许可数就加1。
当调用new Semaphore(2) 方法时,默认会创建一个非公平的锁的同步阻塞队列。
public class MyThread implements Runnable {
private String name;
private String prev;
private String self;
public MyThread(String name, String prev, String self) {
= name;
= prev;
= self;
}
// 先取得两个锁,打印name,打印完 唤醒下一个需要自己的线程,并释放锁。本次打印完毕,释放prev锁,并进入等待状态。
// 每个线程最开始的 notify是无用的,后面每次对应一个wait 相当于第一组的notify没用,第二组的notify激活第一组的wait,第三组的notify激活第二组的wait、最后已一组没有wait。
@Override
public void run() {
int count = 3;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
(name);
count--;
(" " + self + " notify"); //用完了 释放self锁
();
}
try {
if (count != 0) { // 最后一次 不需要wait了 如果wait会导致卡在这
(" " + prev + " wait");
();
}
} catch (InterruptedException e) {
();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
String a = new String("a");
String b = new String("b");
String c = new String("c");
MyThread t1 = new MyThread("A", c, a);
MyThread t2 = new MyThread("B", a, b);
MyThread t3 = new MyThread("C", b, c);
// 依赖线程启动的顺序
new Thread(t1).start();
(50);
new Thread(t2).start();
(50);
new Thread(t3).start();
(50);
}
}
A a notify
B b notify
c wait
C c notify
a wait
A a notify
b wait
B b notify
c wait
C c notify
a wait
A a notify
b wait
B b notify
C c notify
我们的首要目的是要求:按照ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。
那好:
ThreadA打印的条件:获取a,b
ThreadB打印的条件:获取b,c
ThreadC打印的条件:获取c,a
第一步:一开始ThreadA先获取a,然后获取b,打印完毕后,通知TheadB:“b资源我要用完了,你快醒醒吧!”。先释放b,再释放a.
第二步:因为b优先a被释放,所以ThreadB先执行(a被占用,ThreadC无法执行),先获取b,再获取c,打印b,先释放c,再释放b.
第三步:因为c优先b被释放,所以ThreadC先执行(b被占用,ThreadA无法执行),先获取c,再获取a,打印c,先释放a,再释放c.
就这样一步一步的按照顺序执行。// 先取得两个锁,打印name,打印完 唤醒下一个需要自己的线程,并释放锁。本次打印完毕,释放prev锁,并进入等待状态。 // 每个线程最开始的 notify是无用的,后面每次对应一个wait 相当于第一组的notify没用,第二组的notify激活第一组的wait,第三组的notify激活第二组的wait、最后已一组没有wait。
上述方式会依赖线程的启动顺序。(借助于两个Synchronize/wait/notify实现)
方法2: 借助一个Synchronize锁 和 一个count 变量实现
public class MyThread implements Runnable {
Object obLock;
static int count = 3 * 2;
String name;
int threadId;
public MyThread(Object o, String na, int id) {
obLock = o;
name = na;
threadId = id;
}
public void run() {
while (count > 0) {
synchronized (obLock) {
//(count + " " + threadId + " ");
while (count % 3 == threadId) {
(name);
//(count);
count--;
();
}
try {
if (count != 0)
();
} catch (InterruptedException e) {
();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(new MyThread(o, "B", 2)).start(); //如果是0开始逐渐++ 这里就是1
new Thread(new MyThread(o, "C", 1)).start();
(500);
new Thread(new MyThread(o, "A", 0)).start();
}
}
方法3:借助一个lock 和一个count变量实现。(不太推荐,会进行大量的无用尝试,拿到锁不符合解锁无限尝试)
public class MyThread implements Runnable { Lock lock; static int count = 0; String name; int threadId; public MyThread(Lock locko, String na, int id) { lock = locko; name = na; threadId = id; } public void run() { for (int i = 0; i < 3; ){ (); //(count); while (count % 3 == threadId) { (name); count++; i++; } (); } } public static void main(String[] args) throws InterruptedException { Lock o = new ReentrantLock(); new Thread(new MyThread(o, "B", 1)).start(); //如果是0开始逐渐++ 这里就是1 new Thread(new MyThread(o, "C", 2)).start(); (500); new Thread(new MyThread(o, "A", 0)).start(); } }
方法3:(借助一个lock和condition实现,原理同方法二)
public class MyThread implements Runnable {
static Lock lock = new ReentrantLock();
static Condition condition = ();
static int count = 3 * 2;
String name;
int threadId;
public MyThread(String na, int id) {
name = na;
threadId = id;
}
public void run() {
while (count > 0) {
();
while (count % 3 == threadId) {
(name);
count--;
();
}
try {
if (count != 0)
();
} catch (InterruptedException e) {
();
}
();
}
}
public static void main(String[] args) throws InterruptedException {
Lock o = new ReentrantLock();
new Thread(new MyThread("B", 2)).start(); //如果是0开始逐渐++ 这里就是1
new Thread(new MyThread("C", 1)).start();
(500);
new Thread(new MyThread("A", 0)).start();
}
}
方法四,信号量(有点像方法1,很容易理解)
public class MyThread implements Runnable {
Semaphore pre, self;
String name;
public MyThread(Semaphore pre, Semaphore self, String name) {
= pre;
= self;
= name;
}
public void run() {
for (int i = 0; i < 2; i++) {
try {
();
(name);
();
} catch (InterruptedException e) {
();
}
}
}
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore1 = new Semaphore(0);
Semaphore semaphore2 = new Semaphore(0);
Semaphore semaphore3 = new Semaphore(1);
new Thread(new MyThread(semaphore1, semaphore2, "B")).start();
new Thread(new MyThread(semaphore2, semaphore3, "C")).start();
(500);
new Thread(new MyThread(semaphore3, semaphore1, "A")).start();
}
}