1、并发与并行

当1个以上线程在操作的时候,若计算机只有一个cpu,根本不可能同时进行一个以上的处理,而是这样进行:

work1稍微操作一下暂停-->work2稍微操作一下暂停-->work1稍微操作一下暂停.....

当程序的处理像这样不断切换着操作的线程时候就被称为并发(concurrent)。


如果有一个以上cpu执行Java程序,线程操作可能就是并行的(parallel)而不是并发(concurrent)的。当一个以上线程并行操作的时候就可以同时进行一个以上的处理。

并发与并行的差异图如下:

java多线程独立执行 java多线程并行执行_多线程

二、线程的启动

线程的启动有两种方法:

1、利用java.lang.Thread类的子类实例,启动线程。

public class PrintThread extends Thread{
    private String message;
    public PrintThread(String message){
        this.message = message;
    }
    public void run(){
        for(int i = 0; i<100; i++){
           System.out.print(message); 
       }
    }
}
public class Main{
    public static void main(String[] args){
        new PrintThread("Good").start();//匿名实例启动线程
        new PrintThread("Nice").start();
    }
}


整个处理过程是这样:先创建PrintThread类的实例,然后调用实例的start()方法。调用start()方法的时候,会启动新的线程,然后调用实例的run()方法。上面的main()方法里只有一个语句去启动线程,不过创建线程类的实例和启动实例的线程是两个不同的处理。

当所有线程都结束时,程序才会结束。不过在判断是否结束时不包括守护线程,换句话说即使daemon thread还在,其他不是daemon thread的线程结束了,程序也就结束了。守护线程通过调用setDaemon()方法设置。


2、利用Runnable接口的实现类的实例,启动线程。

public class Printer implements Runnable{
    private String message;
    public Printer(Sting message){
        this.message = message;
    }
    public void run(){
        for(int i = 0; i<100;i++){
            System.out.print(message);
        }
    }
}
public class Main{
    public static void main(String[] args){
        new Thread(new Printer("Good")).start();
        new Thread(new Printer("Nice")).start();
    }
}



在创建Thread类的实例的时候,输出Printer类的实例作为构造器的参数(Runnable target),然后利用start()方法启动线程。


无论是那种方法,启动线程的永远都是Thread类的start()方法。


三、线程的暂停

Thread.sleep(1000);

这行代码可以让当前的线程(执行这条语句的线程暂停约1000ms),这个方法要放在try-catch里面,因为sleep方法可能会抛出一个InterruptedException的异常,这个异常用在取消线程处理时的异常。

Thread.sleep(ms,ns);可以将时间控制得更精细,达到ns(10^(-9)s)。


四、线程的共享互斥

当多个线程访问同一个资源的时候,会出现一些和预想情况不符合的现象,这时候就需要“共享互斥”或者说“互斥控制”,Java在处理线程的共享互斥的时候,通常会使用关键字synchronized。

1、使用synchronized关键字修饰类的方法。

public class Test{
    public synchronized void test(){
        //to do
    }
}



当一个线程执行Test实例的test方法时,其他线程就不能执行同一个实例test方法,对于同一个类里的非synchronized方法没有这个限制,对于同一个实例,多个线程可以同时访问。对于某个正在执行test方法的线程,当执行完test方法后,锁就会被释放(release)。当锁被释放之后,其他在等待的多个线程便开始抢锁,一定有且只有一个线程会获得锁,没有抢到就继续等待。

线程共享互斥的架构被称为监视(monitor),而获取锁有时也被称为持有(own)监视。

2、使用synchronized关键字修饰方法里的一部分(synchronized阻挡),格式如下:



synchronized(表达式){
    //to do
}



对于synchronized实例方法和synchronized阻挡:

synchronized void method(){
    //to do
}



和下面这个synchronized阻挡效果是一样的:



void method(){
    synchronized(this){
        //to do
    }
}




换句话说,synchronized实例方法是使用this锁去做线程的互斥共享。

对于synchronized类静态方法和synchronized阻挡:

class Something{
    static synchronized void method(){
         //to do
    }
}


和下面这个synchronized阻挡效果是是一样:

class Something{
    static void method(){
        synchronized(Something.class){
            //to do
        }
    }
}


换句话说,synchronized类静态方法和使用该类的类对象的锁去做线程的共享互斥。



五、线程的协调

wait set----线程休息室

wait set是一个在执行该实例的wait方法时,操作停止时的线程集合,每个实例都有。

wait set实际上是一个虚拟的概念,不是实例的字段,也不是可以获取在实例上wait中线程的列表的方法。

一旦执行wait方法,线程便暂停操作,进入wait set,除非发生下面任何一种情况,否则线程永远留在这个wait set里:

1、有其他线程使用notify方法唤醒该线程;

2、有其他线程使用notifyAll方法唤醒该线程;

3、有其他线程使用interrupt方法唤醒该线程;

4、wait方法已到期。

wait方法----把线程放入wait set

使用wait方法后,线程进入wait set,如obj.wait();

当在实例内的时候直接执行wait()即可,等同于this.wait();

执行wait()方法的时候,线程需要获得锁,当线程进入wait set之后,已经释放了该实例的锁。

java多线程独立执行 java多线程并行执行_java_02

当线程A执行wait()方法时线程B仍然无法获得锁

java多线程独立执行 java多线程并行执行_java_03

当线程A执行了wait()方法之后,线程A进入了wait set,然后释放了锁。

java多线程独立执行 java多线程并行执行_多线程_04

线程B获得了锁,开始执行synchronized修饰的方法。

notify方法----从wait set中拿出线程

使用notify方法的时候,可以从wait set中抓取一个线程,唤醒这个线程,被唤醒的线程便退出wait set。

java多线程独立执行 java多线程并行执行_java多线程独立执行_05

线程B执行notify方法唤醒线程A。

java多线程独立执行 java多线程并行执行_java_06

线程A退出wait set,打算执行自己wait()的下面的操作,但是刚才执行notify的线程B仍然握着锁不放手。

java多线程独立执行 java多线程并行执行_java多线程独立执行_07

刚才执行过notify的线程B开始释放锁。

java多线程独立执行 java多线程并行执行_多线程_08

已经退出wait set的线程A获得锁,开始执行wait下面的操作。

线程需要有遇到用实例的锁,才能执行notify方法。

tips:

1、从上面的图也可以看出来,被notify唤醒的线程不是在notify的一瞬间重新开始执行,因为在notify的那一刻,执行notify的线程还握着锁不妨,所以其他线程无法获取该实例的锁。

2、假设执行notify方法的时候,wait set里正在等待的线程不止1个,规格里并没有注明应该选择哪个线程,依据Java处理系统而异,因此,最好不要将程序写成因所选线程而有所改动。

notifyAll方法----从wait set中拿出所有线程

使用notifyAll方法时,会将所有在wait set里的线程拿出来。

java多线程独立执行 java多线程并行执行_并发_09

线程同样需要有欲调用实例的锁,才能调用notifyAll方法,否则会抛出IlleagalMonitorStateException。

tips:选择notify还是notifyAll?

选择notify的话,要唤醒的线程少,速度比notifyAll要好,但是选择notify若处理不慎有可能导致程序挂掉,因此notifyAll方法更可靠。当然能处理好的话,notify更好。


另外,wait,notify,notifyAll方法都是Object类下的方法,不是Thread下的。


六、线程的状态转移

java多线程独立执行 java多线程并行执行_多线程_10