赞
踩
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
程序:程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程:线程是轻量级进程,是程序执行的最小单位。
进程是线程的容器。
使用多线程而不是多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。
线程生命周期的状态图:
NEW状态表示刚刚创建的线程,这种线程还没开始执行。等到线程的start()方法调用时,才表示线程开始执行。当线程执行时,处于RUNNABLE状态,表示线程所需的一切资源都已经准备好了。如果线程在执行过程中遇到了synchronized同步块,就会进入BLOCKED阻塞状态,这时线程就会暂停执行,直到获得请求的锁。WAITING和TIMED_WAITING都表示等待状态,它们的区别是WAITING会进入一个无时间限制的等待,TIMED_WAITING会进行一个有时限的等待。比如,通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等到了期望的事件,线程就会再次执行,进入RUNNABLE状态。当线程执行完毕后,则进入TERMINATED状态,表示结束。
注意:从NEW状态出发后,线程不能再回到NEW状态,同理,处于TERMINATED的线程也不能再回到RUNNABLE状态。
新建线程很简单,只需new一个线程对象,并将它start()起来即可。start()后,就会新建一个线程并让这个线程执行run()方法。
注意:不要用run()来开启新线程。它只会再当前线程中,串行执行run()中的代码。
Thread类中有一个非常重要的构造方法:
public Thread(Runnable target)
注意:默认的Thread.run()就是直接调用内部的Runnable接口。因此,使用Runnable接口告诉线程该做什么,更为合理。
public class CreateThread implements Runnable{
public static void main(String[] args){
Thread t1 = new Thread(new CreateThread());
t1.start();
}
@Override
public void run(){
System.out.printIn("Oh, I am Runnable");
}
}
上述代码实现了Runnable接口,并将该实例传入Thread。这样避免重载Thread.run(),单纯使用接口来定义Thread,也是最常用的做法。
一般来说,线程在执行完毕后就会结束,无须手动关闭。但是,一些服务端的后台线程可能会常驻系统,它们通常不会正常终结。
那如何正常的关闭一个线程呢?查阅JDK,你不难发现Thread提供了一个stop()方法。stop()可以立即将一个线程终止,非常方便,但你会发现stop()方法是一个被标注为废弃的方法。为什么stop()被废弃而不推荐使用呢?原因是stop()方法太过于暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。
在Java中,线程中断是一种重要的线程协作机制。从表面上理解,中断就是让目标线程停止执行的意思,实际上并非完全如此。
严格地讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦!至于目标线程接到通知后如何处理,则完全由目标线程自行决定。这点很重要,如果中断后,线程立即无条件退出,我们就又会遇到stop()方法的老问题。
与线程中断有关的,有三个方法,这三个方法看起来很像,要注意不要混淆和误用:
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
了解下Thread.sleeo()函数,它的签名如下:
public static native void sleep(long millis) throws InterruptedException
Thread.sleep()方法会让当前线程休眠若干时间,他会抛出一个InterruptedException中断异常。InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep()休眠时,如果被中断,这个异常就会产生。
注意:Tread.sleep()方法由于中断而抛出异常,此时,它会清除中断标记,如果不加处理,那么在下一次循环开始时,就无法捕获这个中断,故在异常处理中,再次设置中断标记位。
为了支持多线程之间的协作,JDK提供了两个非常重要的接口线程等待wait()方法和通知notify()方法。这两个方法并不是在Thread类中的,而是输出Object类。这也意味着任何对象都可以调用这两个方法。
这两个方法的签名如下:
public final void wait() throws InterruptedException
public final native void notify()
注意:Object.wait()和Thread.sleep()方法都可以让线程等待若干时间。除了wait()可以被唤醒外,另外一个主要区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。
通过阅读JDK有关Thread类的API文档,会发现线程挂起(suspend)和继续执行(resume)这两个接口,这两个操作是一对相反的操作,被挂起的线程,必须要等到resume()操作后,才能继续指定。但是它们早已被标注为废弃方法,并不推荐使用。
不推荐使用suspend()去挂起线程的原因,是因为suspend()在导致线程暂停的同时,并不会去释放任何锁资源。此时,其他任何线程想要访问被它暂用的锁时,都会被牵连,导致无法正常继续运行。直到对应的线程上进行了resume()操作,被挂起的线程才能继续,从而其他所有阻塞在相关锁上的线程也可以继续执行。但是,如果resume()操作意外地在suspend()前就执行了,那么被挂起的线程可能很难有机会被继续执行。并且,更严重的是:它所占用的锁不会被释放,因此可能会导致整个系统工作不正常。而且,对于被挂起的线程,从它的线程状态上看。居然还是Runnable,这也会严重影响我们对系统当前状态的判断。
下图为演示suspend()问题的代码:
package com.fwx.test; /** * @author fwx * @date 2022/12/17 */ public class BadSuspend { public static Object u = new Object(); static ChangObjectThread t1 = new ChangObjectThread("t1"); static ChangObjectThread t2 = new ChangObjectThread("t2"); public static class ChangObjectThread extends Thread{ public ChangObjectThread(String name) { super(name); } @Override public void run() { synchronized (u){ System.out.println("in "+getName()); Thread.currentThread().suspend(); } } } public static void main(String[] args) throws InterruptedException { t1.start(); Thread.sleep(100); t2.start(); t1.resume(); t2.resume(); t1.join(); t2.join(); } }
获得的输出:
in t1
in t2
这表明线程先后进入临界区,但是程序不会退出。而是挂起。使用jstack命令打印系统的线程信息可以看到:
"t2@483" prio=5 tid=0xf nid=NA runnable
java.lang.Thread.State: RUNNABLE
at java.lang.Thread.suspend0(Thread.java:-1)
at java.lang.Thread.suspend(Thread.java:1037)
at com.fwx.test.BadSuspend$ChangObjectThread.run(BadSuspend.java:20)
- 锁定 <0x1e8> (java.lang.Object)
下面利用wait()和notify()方法,在应用层面实现suspend()和resume()功能:
package com.fwx.test.suspendandresume; /** * @author fwx * @date 2022/12/17 */ public class GoodSuspend { public static Object u = new Object(); public static class ChangeObjectThread extends Thread{ volatile boolean suspendMe = false; public void suspendMe(){ suspendMe = true; } public void resumeMe(){ suspendMe = false; synchronized (this){ notify(); } } @Override public void run() { while (true){ synchronized (this){ while (suspendMe){ try { wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } } synchronized (u){ System.out.println("in changeObjectThread"); } Thread.yield(); } } } public static class ReadObjectThread extends Thread{ @Override public void run() { while (true){ synchronized (u){ System.out.println("in ReadObjectThread"); } Thread.yield(); } } } public static void main(String[] args) throws InterruptedException { ChangeObjectThread t1 = new ChangeObjectThread(); ReadObjectThread t2 = new ReadObjectThread(); t1.start(); t2.start(); Thread.sleep(5000); t1.suspendMe(); System.out.println("suspend t1 2 sec"); Thread.sleep(2000); System.out.println("resume t1"); t1.resumeMe(); } }
截取部分输出:
很多时候,一个线程的输入可能非常依赖于另外一个或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。JDK提供了join()操作来实现这个功能,如下所示,显示了两个join()方法:
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
第一个join()方法表示无限等待,它会一直阻塞当前线程,直至目标线程执行完毕。
第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
join()的本质是让调用线程wait()在当前线程对象实例上。下面是JDK中join()实现的核心代码片段:
while (isAlive()) {
wait(0);
}
可以看到,它让调用线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会在退出前调用notifyAll()通知所有的等待线程继续执行。因此,值得注意的一点是:不要在应用程序中,在Thread对象实例上使用类似wait()或者notify()等方法,因为这很有可能会影响系统API的工作,或者被系统API所影响。
另一个比较有趣的方法,是Thread.yield(),它的定义如下:
public static native void yield();
这是一个静态方法,一旦执行,它会使当前线程让出CPU。但要注意,让出CPU并不代表当前线程不执行了。
如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的CPU资源,那么可以在适当的时候调用Thread.yield(),给予其他重要线程更多的工作机会。
volatile:易变的,不稳定的。
volatile对于保证操作的原子性是有非常大的帮助的。但是需要注意的是,volatile并不能代替锁,它也无法保证一些复合操作的原子性。
此外,volatile也能保证数据的可见性和有序性。如下例子:
package com.fwx.test.volatileTest; /** * @author fwx * @date 2022/12/17 */ public class NoVisibility { private static boolean ready; private static int num; private static class ReaderThread extends Thread{ public void run(){ while (!ready); System.out.println(num); } } public static void main(String[] args) throws InterruptedException { new ReaderThread().start(); Thread.sleep(1000); num = 42; ready = true; Thread.sleep(10000); } }
num和ready都没被volatile修饰,程序进入死循环,不会输出num。
在一个系统中,如果线程数量很多,而且功能分配比较明确,就可以将相同功能的线程放置在一个线程组里。
注意:线程组也有stop()方法,它会停止线程组中所有的线程。这看起来是一个很方便的功能,但是它会遇到和Thread.stop()相同的问题,因此使用时也需要格外谨慎。
守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。与之相对应的是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序应该要完成的业务操作。如果用户线程全部结束,这也意味着这个程序实际上无事可做了。守护线程要守护的对象已经不存在了,那么整个应用程序就自然应该结束。因此,当一个Java应用内,只有守护线程时,Java虚拟机就会自然退出。
Java中的线程可以有自己的优先级。优先级高的线程在竞争资源时会更有优势,更可能抢占资源,当然,这只是一个概率问题。
在Java中,使用1到10表示线程优先级。一般可以使用内置的三个静态标量表示:
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
并行程序开发的一大关注重点就是线程安全。一般来说,程序并行化是为了获得更高的执行效率,但前提是,高效率不能以牺牲正确性为代价。如果程序并行化后,连基本的执行结果的正确性都无法保证,那么并行程序本身也就没任何意义了。
关键字synchronized可以有多种用法。这里做一个简单的整理。
int v1 = 1073741827;
int v2 = 1431655768;
System.out.println("v1="+v1);
System.out.println("v2="+v2)
int ave = (v1+v2)/2;
System.out.println("ave="+ave);
结果:
v1=1073741827
v2=1431655768
ave=-894784850
显然,v1+v2的结果导致了int的溢出。
ArrayList是一个线程不安全的容器。如果再多线程中使用ArrayList,可能会导致程序出错。
例:t1和t2两个线程同时向一个ArrayList容器中添加容器。他们各添加1000000个元素,因此我们期望最后可以有2000000个元素在ArrayList中。但如果你用ArrayList去存储可能会得到三种结果。
Exception in thread "Thread-0" java.lang ArrayIndexOutOfBoundsException: 22
at java.util.ArrayList.add(ArrayList.java:441)
at geym.conc.ch2.notsafe.ArrayListMultiThread$AddThread.run(ArrayListMultiThread.java:12)
at java.lang.Thread.run(Thread.java:724)
1000015
1793758
显然,这是由于多线程访问冲突,使得保存容器大小的变量被多线程不正常的访问,同时两个线程也同时对ArrayList中的同一个位置进行赋值导致的。如果出现这种问题,那么很不幸,你就得到了一个没用错误提示的错误。并且,他们未必是可以复现的。
注意:改进的方法很简单,使用线程安全的Vector代替ArrayList即可。
HashMap同样不是线程安全的。当你使用多线程访问HashMap时,也会遇到意想不到的错误。(详细看2.8.3)
多线程环境下推荐使用ConcurrentHashMap代替HashMap。
(详细看2.8.4)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。