进程和线程的关系
进程的概念:进程是计算机中的程序关于某数据集合的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
进程和线程的关系:简单的来说,进程就是线程的容器,而线程就是轻量级进程,是程序执行的最小单位; 使用多线程而不是多进程来进行并发程序的设计,是因为线程间的切换和调度的成本要远远小于进程
线程的创建
两种基本方式:继承Thread类和实现Runnable接口,下面通过代码来说下这两种实现方式
class thread1 extends Thread {
public void run(){
}
}
class thread2 implements Runnable {
public void run(){
}
}
对于继承Thread实现的多线程,它的实质还是通过实现Runnable接口来实现的,如下是Thread部分源代码
public class Thread implements Runnable {
...
}
java是单继承多实现的面向对象的语言,所以如果使用Thread继承的话也会限制该类将不能在继承其他类了
而实现Runnable就相对Thread来说没有限制,不过它有两个硬性要求:必须实现自己的run方法;必须借助于Thread实例来启动线程。 因此对于上面的实现Runnable接口创建的多线程就需要使用下面的代码进行启动
new Thread (new thread2()). start();
因为在Runnable接口中本身是没有start方法启动线程的,所以要想启动线程,就必须借助Thread的构造器构造一个Thread对象从而调用其start进行启动
线程的终止
一般来说,当线程执行完毕后就会自动结束,无须手工关闭,不过也有特殊情况,比如线程在执行的过程中遇到了死循环导致线程永远执行下去时,这时候就需要我们手动终止线程了。
Thread类中提供了一个stop实例方法,用于手工终止线程,不过该方法现在已经被声明为废弃的方法了,原因是:stop方法停止线程太过暴力,强行把当前线程终止,不管是否还为执行完成,这样会引起数据完整性和一致性的问题
对于在run中循环进行的线程,我们可以使用代码手动的进行线程终止,可以通过一个flag标识判断当前线程是否需要终止,看如下测试代码:
class thread1 extends Thread {
public volatile boolean flag = false ;
public void stopMe(){
flag = true ;
}
public void run(){
if (flag){
System.out.println("thread is stop..." );
return ;
}
System.out.println("thread is running..." );
}
}
我们将上面的线程启动之后,会一直输出“thread is running。。。”标识当前线程一直在执行,此时如果我们想要关闭该线程,可以调用该线程对象的stopMe方法,设置flag为true,从而退出了while循环
线程的中断
线程中断就是让当前线程暂停执行,我们可以使用响应的方法来继续该线程的执行。与线程中断有关的有三个方法,声明如下:
public void Thread.interrupt ();
public boolean Thread.isInterrupted ();
public static boolean Thread.interrupted ();
线程休眠
通过调用Thread的sleep()方法可以使当前线程休眠,sleep的方法签名如下:
public static native void sleep (long millis) throws InterruptedException;
public
通过该方法,可以指定当前线程休眠多长时间,它会抛出一个InterruptedException异常:当线程调用了sleep方法休眠时,如果被中断,就会抛出该异常;
线程等待wait和唤醒notify/notifyAll
这三个方法都是属于Object类的,而不是Thread类的,他们的签名如下:
public class Object {
...
public final void wait () throws Interrupted;
public final void wait (long timeout) throws Interrupted;
public final native void notify ();
public final native void notifyAll ();
...
}
Object.class.wait()和Thread.sleep()方法都可以让目标线程等待指定时间,他们之间的区别在于:
1.wait是属于Object的,sleep是属于Thread的
2.sleep方法只是让当前线程休眠,不是释放目标对象的锁;而wait方法会让当前线程进行休眠(准确来说是等待阻塞)状态并释放目标对象的锁,知道调用相应对象的notify/notifyAll之后才会重新进入就绪状态(此时不会立刻进入运行状态,除了随机之外,还需要等待当前持有对象锁的线程执行完毕之后释放对象锁才能重新获得)
线程挂起(suspend)和继续执行(resume)状态
这两个方法和前面的等待和唤醒一样,也是一对相反的操作,被挂起的线程必须等到resume操作之后才能继续指定。他们的方法签名如下:
public final void suspend ();
public final void resume ();
不过这两个方法和stop方法一样现在也被标注为废弃方法了,原因是:调用suspend方法进行挂起的线程在导致线程暂停执行时,不会去释放该线程占用的任何锁资源,这样就会导致其他需要获得该锁才能执行的线程在该线程resume之前不能获得该锁从而获得执行机会; 如果resume在suspend执行之前执行了,那么被挂起的线程就可能很难有机会被继续执行
线程直通车(join)
有时候,一个线程的输入可能非常依赖于其他线程的输出,此时这个线程就需要等到依赖线程执行完毕才能获取到相应的执行所需的资源,因此jdk提供了join方法用于实现这个功能,如下是join的两个方法签名:
public final void join () throws Interrupted;
public final void join (long timeout) throws Interrupted;
join的作用:执行join的当前线程会一直执行到结束,期间所有线程不可抢占资源; 第一个join方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕;第二个join方法可以指定一个最大的等待时间,如果超过给定时间目标线程还未执行,那么当前线程也会因为“等不及了”而继续往下执行
可以看如下测试代码:
public class Main {
public volatile static int i = 0 ;
public static class Test extends Thread {
@Override
public void run (){
for (; i<10000 ; i++)
}
}
public static void main (String[] args) throws Interrupted{
Test test = new Main.Test();
test.start();
test.join();
System.out.println(i);
}
}
虽然他是多线程,但是不管运行多少次,最终的结果也都是10000;这是因为调用了join方法指定了test线程hi一直运行到结束,所以这里也就类似为单线程了
线程谦让(yield)
yield方法的签名如下:
public static native void yield ();
这是一个静态方法,yield意味着放弃,调用yield。我们可以通过在run中相应位置调用yield方法来实现任务的交替执行
public class Main {
public static class thread extends Thread {
public void run (){
for (int i=0 ; i<5 ; i++){
System.out.println("thread: " +i);
Thread.yield();
}
}
}
public static class threadd extends Thread {
public void run (){
for (int i=0 ; i<5 ; i++){
System.out.println("thredd:" +i);
Thread.yield();
}
}
}
public static void main (String[] args){
thread t1 = new thread();
threadd t2 = new threadd();
t1.start();
t2.start();
}
}
I am Consumer : Consumed Item 0
I am Consumer : Consumed Item 1
I am Consumer : Consumed Item 2
I am Consumer : Consumed Item 3
I am Consumer : Consumed Item 4
I am Producer : Produced Item 0
I am Producer : Produced Item 1
I am Producer : Produced Item 2
I am Producer : Produced Item 3
I am Producer : Produced Item 4
I am Producer : Produced Item 0
I am Consumer : Consumed Item 0
I am Producer : Produced Item 1
I am Consumer : Consumed Item 1
I am Producer : Produced Item 2
I am Consumer : Consumed Item 2
I am Producer : Produced Item 3
I am Consumer : Consumed Item 3
I am Producer : Produced Item 4
I am Consumer : Consumed Item 4
volatile关键字
使用volatile关键字修饰变量用于确保该变量的可见性和他的可见性
分类管理: 线程组
线程组是用来将一类线程组合起来,线程组的两个相关方法的签名如下:
public ThreadGroup (String name);
public Thread (ThreadGroup tg, String name);
public Thread (ThreadGroup tg, Runnable runnable, String name);
如下是使用线程组的实例代码:
public class Main {
public static class thread extends Thread {
public thread (ThreadGroup tg, String name){
super (tg,name);
}
public void run (){
System.out.println("thread: " +Thread.currentThread().getName());
}
}
public static class threadd extends Thread {
public threadd (ThreadGroup tg, String name){
super (tg,name);
}
public void run (){
System.out.println("thread: " +Thread.currentThread().getName());
}
}
public static void main (String[] args){
ThreadGroup tg = new ThreadGroup("chengxi" );
thread t1 = new thread(tg, "t1" );
threadd t2 = new threadd(tg, "t2" );
t1.setPriority(6 );
t1.start();
t2.start();
tg.list();
}
}
守护线程
守护线程是一种特殊的线程,他是系统的守护者,在后台默默的完成一些系统性的服务,比如gc线程、jit线程等就可以理解为守护线程;与之相对应的是用户线程,用户线程可以被认为是系统的工作线程,它用于完成这个程序应该要完成的业务操作。当一个java应用内,只有守护线程时,java虚拟机就会自然退出
指定一个线程为守护线程的方式是通过setDaemon方法 ,该方法的签名如下:
public final void setDaemon (boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
Thread t = new Thread();
t.setDaemon(true );
线程优先级
每个线程都是有各自的线程优先级的,优先级高的线程在竞争资源时会有更多的优势,更可能去抢占资源(这里是更可能,不是说一定)。默认创建的线程优先级为5.
在java中内置了三个静态标量:
public final static int MIN_PRIORITY = 1 ;
public final static int MAX_PRIORITY = 10 ;
public final static int NORM_PRIORITY = 5 ;
synchronized同步
使用synchronized关键字可以用于同步代码块和方法,之前说的使用volatile关键字只能确保一个线程修改了数据后,其他线程能立马看到修改后的信息,而不能确保两个或多个线程修改数据后的完整性和一致性,而使用synchronized关键字可以确保一段代码或者一个方法的代码的数据完整性和同步性
如下使用synchronized关键字的测试代码:
public synchronized void add (){
++i;
}
public void add (){
synchronized (this ){
++i;
}
}
同步代码块相比同步方法来说,更加灵活,它可以指定方法中的哪些代码需要进行同步,而且在一个方法中可以多次使用同步代码块,增加了同步代码的灵活性
public void test (){
synchronized (this ){
++i;
}
System.out.println(i);
synchronized (this ){
++i;
}
}
synchronized关键字有三种使用情况:
1. 指定加锁对象:实现同步代码块 -> synchronized(this or other object)
2. 直接作用于实例方法:用于对当前实例进行加锁 -> public synchronized void add(){... }
3. 直接作用于静态方法:用于对当前类进行加锁 -> public static synchronized void test(){... }
参考文献
java高并发程序设计