第二章 java并行程序基础

2.1 线程必须要知道的事

进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

线程:是轻量级进程,是程序执行的最小单位。

一个线程的生命周期

线程生命周期

2.2初始线程

2.2.1 新建线程

Thread t1 = new Thread();

t1.start();

线程Thread,有一个方法run()方法。start()方法就会新建一个线程并让这个线程执行run()方法。

Thread t1 = new Thread();

t1.run();

以上代码不能新建一个线程,而是在当前线程中调用run()方法,只是一个普通的方法调用。

Thread t1 = new Thread(){
       @Override
       public void run(){
           System.out.println("hello ,I am t1");
       }
   };
t1.start();

上述代码使用了内部匿名类,重写了run()方法;也可以使用Runnable接口来实现同样的操作。

public interface Runnable(){
       public abstract void run();
}

Thread类中的构造方法:

public Thread(Runnable target)

它传入一个Runnable接口的实例,在start()方法调用时,新的线程就会执行Runnable.run()方法,默认的Thread.run()方法如下:

public void run(){
        if(target != null){
        target.run();
    }
}

具体实现如下:

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.println("i am run");
    }
}

2.2.2 终止线程

不要随便使用stop()方法来终止一个线程。
原因:stop()方法会直接终止线程,并立即释放这个线程所持有的锁,而锁恰恰是用来维护对象的一致性的。
例如:写线程写入数据写到一半,强行stop(),那么对象就会被写坏,但由于锁已经释放,其他读线程就会读出错误的数据。

我们可以使线程在合适的地方退出

public class ChangeObjectThread extends Thread {
    volatile boolean stopme = false;
    
    public void stopme(){
        stopme = true;
    }
    
    public void run(){
        while(true){
            //判断线程是否退出,在此阶段退出
            if(stopme){
                System.out.println("exit by stop me");
                break;
            }
            //修改对象数据
            synchronized (u){
                u.setId();
                u.setName();
            }
            Thread.yield();
        }
    }
}

上述代码标记了一个变量stopme,用于指示线程是否需要退出。使用这种退出方式,线程就没有机会在写坏数据了,因为它总是在一个合适的时间终止进程。

2.2.3 线程中断

线程中断不会使线程立即退出,而是给线程发一个通知,告知目标线程,至于何时退出,则由线程自己选择一个合适的时间。
3个线程中断有关的方法

public void Thread.interrupt()        //中断线程
public boolean Thread.isInterrupted()         //判断线程是否中断
public static boolean Thread.interrupted()           //判断是否被中断,并判断当前中断状态

中断示例:

public class Test  {
    public static void main(String args[]) throws InterruptedException {
        Thread t1 = new Thread(){
          public void run(){
              while(true){
                  //判断线程是否中断
                  if(Thread.currentThread().isInterrupted()){
                      System.out.println("Interrupted!");
                      break;
                  }
                  Thread.yield();
              }
          }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

2.2.4 等待(wait)和通知(notify)

wait()方法和notify方法是在Object类中的,意味这任何类都可以调用这两个方法。
这两个方法的声明如下:

public final void wait() throws InterruptedException
public final native void notify()

在线程中,如果调用了obj.wait()方法,线程就会停止执行,进入一个等待队列,直到其他线程调用了obj.notify()方法,从等待队列中随机选择一个线程唤醒。
Object.wait()必须包含在对应的synchronized语句中,无论是wait()还是notify()都必须获得目标对象的监视器。

wait和notify的工作流程

一个小实例:

package chapter2;

public class SimpleWN {
    final static Object object = new Object();
    public static class T1 extends Thread{
        public void run(){
            {
                synchronized (object){
                    System.out.println(System.currentTimeMillis()+":T1 start!");
                    try {
                        System.out.println(System.currentTimeMillis()+":T1 wait for object");
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(System.currentTimeMillis()+":T1 end!");
                }
            }
        }
    }

    public static class T2 extends Thread{
        public void run(){
            synchronized (object){
                System.out.println(System.currentTimeMillis()+":T2 start,notify one thread");
                object.notify();
                System.out.println(System.currentTimeMillis()+":T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String args[]){
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

运行结果

2.2.5 挂起(suspend)和继续执行(resume)

现已被标注为废弃方法,不推荐使用。
原因:suspend方法在导致线程暂停的同时,不会释放任何锁资源,使得其他需要访问此资源的线程都无法正常运行。

2.2.6 等待线程结束(join)和谦让(yeild)

一个线程的输入可能非常依赖于另一个或多个线程的输出,这时就要用到join()。
join的两个方法:

public final void join() throws InterruptedException
public final synchronized void join(long mills) throws InterruptedException

其中第一个方法表示无限等待,会一直阻塞当前线程,直到目标线程执行完毕。
第二个方法给出了一个最大等待时间,如果超出时间,那么线程将不再等待。
一个小实例:

package chapter2;

public class JoinMain {
    public volatile static int i = 0;
    public static class AddThread extends Thread{
        @Override
        public void run() {
            for(i = 0;i < 100000000; i++);
        }
    }
    
    public static void main(String args[]) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

在执行at.join后,i总是等于100000000,说明主线程等待at线程执行完成后,再继续执行。

2.3 volatile关键字

volatile声明一个变量,就等于告诉了虚拟机,这个变量极有可能会被某些程序或线程修改。
根据编译器的优化规则,如果不使用volatile关键字,那么变量修改后,其他线程可能并不会通知到。

2.4 线程组

在一个系统中,可以将相同功能的线程放置在同一个线程组中。
一个小实例:

package chapter2;

public class ThreadGroupName implements Runnable{
    public static void main(String args[]){
        ThreadGroup tg = new ThreadGroup("PrintGroup");
        Thread t1 = new Thread (tg,new ThreadGroupName(),"T1");
        Thread t2 = new Thread (tg,new ThreadGroupName(),"T2");
        t1.start();
        t2.start();
        //获得当前组活动线程总数
        System.out.println(tg.activeCount());
        tg.list();
    }

    @Override
    public void run() {
        String groupAndName = Thread.currentThread().getThreadGroup().getName()+"-"+Thread.currentThread().getName();
        while(true){
            System.out.println("I am "+groupAndName);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
运行结果

2.5 守护线程(Daemon)

守护线程提供一些系统性的服务,比如垃圾回收器线程和JIT线程。当用户线程全部结束,只有守护线程时,应用程序应该结束,此时java虚拟机将会退出。
一个小实例:

package chapter2;

public class DaemonDemo {
    public static class DaemonT extends Thread{
        @Override
        public void run() {
            while(true){
                System.out.println("I am alive");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
        Thread t = new DaemonT();
        t.setDaemon(true);//设置t为守护线程
        t.start();

        Thread.sleep(3000);
    }
}

当主线程mian休眠3秒后退出时,t也退出了。

2.6 线程优先级

在java中,使用1到10表示线程优先级。一般可以使用三个内置静态变量表示

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

数字越大,优先级越高
通过setPriority()方法来设定线程的优先级。

2.7 synchronized关键字

关键字synchronized的作用是实现线程间的同步。它的工作是对同步代码的加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全。
synchronized的各种用法:
指定对象加锁:进入同步代码前要获得给定对象的锁。
直接作用于实例方法:进入同步代码前获得当前实例的锁。
直接作用于静态方法:进入同步代码前要获得当前类的锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容