Java并发编程(四)线程的基本使用2

Thread类的基本使用(二)

线程的join(等待线程结束)和yield(谦让线程)

线程的join方法的意思是,如果有对象在该线程中执行,但是又有别的线程要调用这个对象,join方法会让别的线程阻塞,直到该线程执行完毕为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test implements Runnable{
public static int i;
public static void main(String[] args) throws InterruptedException {
test r = new test();
Thread t = new Thread(r);
t.start();
t.join();//如果没有,打印出来的就会为0
System.out.println("i="+i);
}
@Override
public void run() {
for (i = 0; i<10000 ; i++) {}
}
}
//i=10000

这里可以看到的是,t线程一直在执行run方法,如果此时没有join方法的话,就直接执行了之后的print方法,这样打印出来的数值就是0因为join方法阻塞了主线程调用print方法去打印i。

再看看join的内部构造

1
2
3
public final void join() throws InterruptedException {
join(0);
}

这个内部表示可以向join传入一个参数,表示等待的时间,看看我们传入了100ms之后,会出现什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class test implements Runnable{
public static int i;
public static void main(String[] args) throws InterruptedException {
test r = new test();
Thread t = new Thread(r);
t.start();
t.join(100);
System.out.println("i="+i);

}
@Override
public void run() {
for (i = 0; i<1000000000 ; i++) {}
}
}
//i=155033164

看,线程t在100ms后便不再阻塞主线程,任由其打印了。

那么再看看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

取重点

1
2
3
4
if (millis == 0) {
while (isAlive()) {
wait(0);
}

这个while循环会一直判断,是否一直存活,若存活则一直继续,若不为存活,则直接跳出循环并使用wait(delay)方法,它会传入参数,在调用notifyAll()方法,唤醒全部线程。

线程谦让:

1
public static native void yield();

线程谦让表示该线程愿意让出CPU资源,给其他线程去调用,但这并不表示该线程就不执行了,它仅仅是表明了,“我愿意让出这个资源,大家再度公平竞争”,这通常会在某些运算中运用到,比如有一个线程调用了大量的cpu资源去给线程a运算一个一千万循环的for语句,和一百循环的for语句,这样相比cpu会给极大的资源给线程a运算第一个for,但当其运算完成后,第二个for想必不用调用这么多cpu资源去运算,那么便可以使用线程谦让,使得它们再次平衡下来。

线程的suspend(线程挂起)和resume(继续执行)

线程的suspend方法和resume方法早已被废弃,不过这里还是简单讲一下:

线程挂起的意思就是将当前的线程阻塞,不让其继续使用,等待线程收到resume命令之后才会继续执行

1
2
3
4
5
6
7
8
9
10
11
12
 t.start();
t.suspend();
Thread.sleep(1000);
i=1;
t.resume();

public void run() {
while (true){
if(i==1){
System.out.println("ok!!!");
break;
}

但是这样方法在某些情况的使用是致命的,因为被挂起的线程是不会释放任何资源的,如果线程此时被上了锁,那将会有非常多的线程去等待一个线程释放资源,从而导致长时间的阻塞,而且,在另一个线程去调用resume的同时,你也不能确定resume方法一定会在suspend之后执行,因为在并行计算中,这一切不一定完全有序,这样会导致死锁的产生,所以这两个方法被废弃了。

那有什么可以代替的呢?

线程的wait(等待)和notify(通知)

这里可以引入到两个新的方法wait和notify,wait方法和suspend方法一样,会让线程阻塞,然后等待通知了才会继续。

1
2
3
4
5
6
7
8
9
10
11
12
13
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}

但是注意,wait方法的使用是被锁包围的,首先wait在执行之前会被锁给锁住,不让其它线程获取本线程(线程a)的对象资源,而使用了wait后,他会自动且暂时的释放当前的锁,然后阻塞该线程,等待需要访问该对象的线程b获得对象锁,再使用notify方法后,执行完成线程b的synchronize代码块后,再唤醒线程a,继续执行线程a。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class test {
final static Object object = new Object();
public static class test1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println("test1");
try {
System.out.println("test1 wait");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1 end");
}
}
}public static class test2 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println("test2");
object.notify();
System.out.println("test2 end");
}
}
}
public static void main(String[] args) {
Thread t1=new test1();
Thread t2=new test2();
t1.start();
t2.start();
}
}


test1
test1 wait
test2
test2 end
test1 end

区别的核心在于:
1、suspend()在引起当前所在线程阻塞后,不会释放线程占用的锁(如果占用了的话)
2、wait() 引起当前所在线程阻塞后,会释放占用的锁,并且必须 wait对象的锁必须被当前线程持有
3、wait与notify 需要 synchronized 锁来包裹

4、suspend会产生死锁问题,而wait不会

线程的stop(终止)和interrupted(中断)

线程可以在执行过程中被强行结束吗?当然可以,但是,强行被结束是会出现很多问题的,最初使用的方法叫stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Deprecated
public final void
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
if (threadStatus != 0) {
resume();
}
stop0(new ThreadDeath());
}

想必大家也很容易的猜得到,stop方法被弃用的原因是会破坏对象原子性,即使在被synchronize包围时也是如此,因为stop方法是先去释放锁,再去停止整个线程的,这样在释放锁的过程中,可能会出现一释放锁,该锁立马被别的线程锁获取,并发生了修改对象操作后,便停止了线程,这种数据错误不容易在众多代码出找出来,可以说是非常致命,那么对应的又有什么方式去代替它呢,当然有,那就是interrupt()方法,线程中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();

synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}

那么这个interrupt要如何使用呢,直接像stop一样使用吗? 并不对,这样其实并不会停止线程。interrupt仅仅是产生了一个中断标记,表示该线程即将要中断了,至于什么时候中断,则是由设计者决定的了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new test());
t.start();
t.interrupt();
}
@Override
public void run() {
while (true)
{
if(Thread.currentThread().isInterrupted())
{
break;
} } }
}

Thread.interrupt() //中断线程
Thread.isInterrupted()判断是否被中断
Thread.interrupted()判断是否被中断,并清除当前中断状态