# 多线程编程

Java多线程可以提升单位时间的程序处理性能,即在同样的时间能做更多的事。

# 进程和线程

进程是程序的一次动态执行过程,它经历了代码加载,执行,执行完毕的过程,这个过程中进程产生,发展,消亡。多进程操作系统能同时运行多个进程,因为CPU的分时机制,每个进程都能循环获得自己的CPU时间片,由于CPU执行速度很快,使得所有程序好像是在"同时"运行一样。

多进程可以提高硬件资源的利用率,但是进程启动与销毁会消耗大量系统性能,导致程序执行性能下降,为了进一步提高并发操作的能力,在进程的基础上又划分出多线程概念,这些线程依附于指定的进程,它们的区别:

multiThread

# Java多线程

Java中要实现多线程,需要依靠一个主体类,这类可以继承Thread类,实现Runnable接口和Callable接口。

# Thread类

class MyThreadClass extends Thread{
    private String infoString;
    public MyThreadClass(String str) {
        this.infoString=str;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i =0;i<10;i++) {
            System.out.println(this.infoString+".x="+i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new MyThreadClass("线程A").start();
        new MyThreadClass("线程B").start();
        new MyThreadClass("线程C").start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

为什么多线程一定要调用Thread类的start方法,而不是直接调用run方法?看Thread中Start源码:

public synchronized void start() {
    /**
        * This method is not invoked for the main method thread or "system"
        * group threads created/set up by the VM. Any new functionality added
        * to this method in the future may have to also be added to the VM.
        *
        * A zero status value corresponds to state "NEW".
        */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
        * so that it can be added to the group's list of threads
        * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                it will be passed up the call stack */
        }
    }
}

private native void start0();
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

上面中的start0被native关键字定义,native关键字指的是Java本地接口调用(Java Native Interface),使用Java调用本机操作系统的函数功能来完成一些特殊操作,这在Java中很少出现,因为它会影响可移植性,如果因为使用这些和系统相关的函数而只能在该系统上运行,将丢失可移植性。这样看多线程是需要操作系统支持的,start0和抽象方法一样,没有方法体,具体实现在JVM中,在windows下是用A方法,在Linux下可能是B方法。

在start方法中会抛出IllegalThreadStateException异常,这个异常没有捕获,它属于RuntimeException子类,一个线程只能被启动一次,如果被重复启动就会抛出此异常。

# Runnable接口

从JDK1.8开始提供Runnable函数式接口,可利用Lambda表达式来实现主体代码,该接口中有run()方法进行线程执行的功能定义。

class MyRunnableClass implements Runnable{
    private String infoString;
    public MyRunnableClass(String str) {
        this.infoString=str;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i =0;i<10;i++) {
            System.out.println(this.infoString+".x="+i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new Thread(new MyRunnableClass("线程A")).start();
        new Thread(new MyRunnableClass("线程B")).start();
        new Thread(new MyRunnableClass("线程C")).start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

可使用Lambda表达式来代替MyRunnableClass:

public class Main {
    public static void main(String[] args) {
        for(int i=0;i<3;i++) {
            String objName = "线程"+i;
            new Thread(()->{
                for (int j=0;j<10;j++) {
                    System.out.println(objName+"运行:"+j);
                }
            }).start();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# Thread与Runnable区别

Thread定义如下:

public
class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private Runnable target;
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Runnable定义如下:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Thread类中有target属性,用来保存Runnable对象,并调用它的run方法。它们都以同一种功能的方式来实现多线程,但是使用Runnable接口能避免单继承的局限,肯定是优先使用Runnable。

可以认为多线程本质上是多个线程对同一资源的抢占和处理,Thread描述的是线程对象,Runnable描述的是并发资源。

class MyRunnableClass implements Runnable{
    private int ticket=5;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i =0;i<10;i++) {
            if(this.ticket>0) {
                System.out.println(" sell ticket:"+this.ticket--);
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnableClass multiThreadResource = new MyRunnableClass();
        new Thread(multiThreadResource).start();
        new Thread(multiThreadResource).start();
        new Thread(multiThreadResource).start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

multiThreadInResource

为什么不使用Thread类做资源共享呢?如果Thread类做资源共享,确实也是可运行的,但从含义上看,它本身就能开启线程,这样依靠其他Thread类再开启在含义上会有点不合理。

# Callable

Runnable接口里面的run()方法不能返回操作结果,为了解决这个问题从JDK1.5开始对多线程的实现提供了一个新的接口:java.util.concurrent.Callable。

它的定义如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallableClass implements Callable<String>{

    @Override
    public String call() throws Exception {
        // TODO Auto-generated method stub
        for(int i=0;i<10;i++) {
            System.out.println("i="+i);
        }
        return "callableClass";
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<String> task = new FutureTask<String>(new MyCallableClass());
        new Thread(task).start();
        System.out.println("返回数据:"+task.get());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

java.util.concurrent.FutureTask实现Callable接口与Thread类之间的联系:

FutureTask

# 运行状态

Java中的任意线程具有五种基本状态:

  • 创建:刚创建的线程对象所处的状态。
  • 就绪:调用线程的start方法,进入就绪状态,进入线程队列排队,等待CPU调度。
  • 运行:自动调用线程对象的run方法。
  • 阻塞:比如需要等待IO,人为挂起,会让CPU暂停进入阻塞状态,可运行状态下调用sleep(),suspend(),wait()均可进入阻塞,当阻塞的原因被消除后,线程才进入就绪状态。
  • 终止:线程体中run方法运行结束后,处于终止状态。

# 线程常用操作

线程的常见状态如下:

ThreadState

# 线程命名

class MyRunnableClass implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName() + ":" + ticket--);
        for (int i = 0; i < 10; i++) {
        if (ticket>0)
            System.out.println(Thread.currentThread().getName() + ":" + ticket--);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        //ThreadResource.run();
        Thread t1 = new Thread(ThreadResource, "线程1");
        t1.start();
        Thread t2 = new Thread(ThreadResource, "线程2");
        t2.start();
        Thread t3 = new Thread(ThreadResource);
        t3.start();
        System.out.println("执行主程序任务!");
        t2.setName("线程22");
    }
}
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

注意:

  • 子线程进行耗时的业务处理,在子线程的执行过程中,主线程中的其他代码不受到该耗时任务的影响。
  • 每次使用java命令执行一个类的时候就表示启动了一个JVM进程,main是这个进程上的一个线程,当执行完毕会消失。
  • 假设某个子线程任务需要等另一个线程任务完成,需要引入等待机制,推荐JDK1.5之后引入的JUC机制。

# 线程休眠

class MyRunnableClass implements Runnable {
    private int ticket = 10;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName() + ":" + ticket--);
        for (int i = 0; i < 10; i++) {
            if (ticket > 0)
                System.out.println(Thread.currentThread().getName() + ":" + ticket--);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        // ThreadResource.run();
        for (int i = 0; i < 5; i++) {
            new Thread(ThreadResource, "线程" + i).start();
        }
    }
}
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

线程的休眠也是有先后顺序的。

# 线程中断

Thread类提供的很多方法会抛出InterruptedException中断异常,线程在执行过程中可被另一个线程中断执行。

class MyRunnableClass implements Runnable {
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println("线程休眠2秒");
        try {
            Thread.sleep(2000);
            System.out.println("线程休眠2秒然后醒来!");
        } catch (InterruptedException e) {
            // TODO: handle exception
            System.out.println("捕获中断异常!");
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        Thread t1 = new Thread(ThreadResource);
        t1.start();
        Thread.sleep(2000);
        if (!t1.isInterrupted()) {
            System.out.println("让t1进行中断!");
            t1.interrupt();
        }
    }
}
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

上面main休眠时间小于2000毫秒,会在子线程运行时进行中断,如果休眠时间大于等于2001毫秒,那么会在子线程运行完后进行中断,此时不会捕获中断异常。

# 线程强制执行

多线程并发的每个线程对象都会交替执行,如果某个线程对象需要优先执行,可设置为强制执行,其执行完后其他线程再执行:

class MyRunnableClass implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i=8;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if(Thread.currentThread().getName()=="m1") {
                if(i%3 == 1) {
                    try {
                        System.out.println("强制执行线程m2!");
                        returnThreadWithName("m2").join();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
        
    }
    
    public Thread returnThreadWithName(String name) {
        ThreadGroup tGroup = Thread.currentThread().getThreadGroup();
        int activeThreadCount = tGroup.activeCount();
        Thread[] allThreads = new Thread[activeThreadCount];
        tGroup.enumerate(allThreads);
        for(int i=0;i<activeThreadCount;i++) {
            if(allThreads[i].getName()==name) {
                return allThreads[i];
            }
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        new Thread(ThreadResource,"m1").start();;
        new Thread(ThreadResource,"m2").start();;
    }
}
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
41
42
43

在上面的程序中,当m1中的i%3==1时就让m2强制执行,在i=10时第一次强制执行,在i=13时第二次强制执行,此时m2已经运行完,会抛出异常。

# 线程礼让

class MyRunnableClass implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int i=8;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if(Thread.currentThread().getName()=="m1") {
                if(i%3 == 1) {
                    System.out.println("m1线程礼让");
                    Thread.yield();
                }
            }
        }
        
    }
    
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        new Thread(ThreadResource,"m1").start();;
        new Thread(ThreadResource,"m2").start();;
    }
}
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

线程礼让和强制执行不太一样,线程礼让是当前线程执行的东西并不是那么要紧,把时间调度让给其他线程,而强制执行是让某个线程先执行完再执行其他线程。

# 线程优先级

class MyRunnableClass implements Runnable {
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+" 线程 当前优先级:"+Thread.currentThread().getPriority());
        for(int i=0;i<30;i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main 线程优先级:"+Thread.currentThread().getPriority());
        MyRunnableClass ThreadResource = new MyRunnableClass();
        Thread m1 = new Thread(ThreadResource,"m1");
        m1.setPriority(2);
        m1.start();
        Thread m2 = new Thread(ThreadResource,"m2");
        m2.setPriority(6);
        m2.start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

优先级越高,越有可能抢占资源。

# 同步和死锁

# 同步

程序可利用多线程进行更高效的处理,但是当多个线程访问同一个资源时,会存在一些问题,比如下面这个:

class MyRunnableClass implements Runnable {
    private int ticket =3;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true) {
            if(this.ticket>0) {
                try {
                    System.out.println("网络延迟100ms");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 卖了一张票,剩余::"+(--ticket));
            }else {
                System.out.println("票已经卖光了!");
                break;
            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        Thread m1 = new Thread(ThreadResource,"m1");
        m1.start();
        Thread m2 = new Thread(ThreadResource,"m2");
        m2.start();
        Thread m3 = new Thread(ThreadResource,"m3");
        m3.start();
    }
}
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

我们想要卖3张票,会有三个线程,会出现:

  • 当一个线程卖票的时候,其他线程也会卖票,这个时候它们会同时在相同的ticket变量中扣值。
  • 我们使用ticket>0来判断是否卖完票,但是判断ticket>0和卖票之间可能会有网络延迟,在这段延迟中,其他人可能会依旧买票。

造成这些现象的根本原因是没有对一组逻辑操作进行锁定,即进行某些操作时同一时间只能有一个人进行操作,这就是线程同步问题。

Java中使用synchronized关键字实现同步处理,可同步方法块或者同步代码块。

对上面例子进行改造,当同步代码块时:

class MyRunnableClass implements Runnable {
    private int ticket =3;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true) {
            synchronized (this) {
                if(this.ticket>0) {
                    try {
                        System.out.println("网络延迟100ms");
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" 卖了一张票,剩余::"+(--ticket));
                }else {
                    System.out.println("票已经卖光了!");
                    break;
                }
            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        Thread m1 = new Thread(ThreadResource,"m1");
        m1.start();
        Thread m2 = new Thread(ThreadResource,"m2");
        m2.start();
        Thread m3 = new Thread(ThreadResource,"m3");
        m3.start();
    }
}
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

当同步方法时:

class MyRunnableClass implements Runnable {
    private int ticket = 3;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (saleTicket()) {
        }
    }

    public synchronized boolean saleTicket() {
        if (this.ticket > 0) {
            try {
                System.out.println("网络延迟100ms");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 卖了一张票,剩余::" + (--ticket));
            return true;
        } else {
            System.out.println("票已经卖光了!");
            return false;
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyRunnableClass ThreadResource = new MyRunnableClass();
        Thread m1 = new Thread(ThreadResource, "m1");
        m1.start();
        Thread m2 = new Thread(ThreadResource, "m2");
        m2.start();
        Thread m3 = new Thread(ThreadResource, "m3");
        m3.start();
    }
}
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

注意同步操作本质上是在同一个时间只允许有一个线程执行,当该线程没有执行完时,其他线程处于等待状态,这会造成程序性能下降,但是会保证数据的线程访问安全。

# 死锁

线程同步指的是在多个线程操作同一个资源的时候,一个线程要等待另一个线程执行完释放该资源后,才继续执行。但是会出现这种情况:两个线程A和B,分别需要资源C和D才能执行,但是A目前拥有C,B目前拥有D,它们都在为所需资源D和C等待,这就造成了死循环。

下面以一个场景为例,A和B两个人分别拥有矛和盾,他们都想要对方的盾和矛,但是双方互不信任,都想先得到对方武器后再交出自己的武器,这就造成了死循环。

class PersonA{
    public synchronized void tell(PersonB personB) {
        System.out.println("你给我盾,我给你矛");
        personB.get();
    }
    public synchronized void get() {
        System.out.println("personA 得到了盾");
    }
}

class PersonB{
    public synchronized void tell(PersonA personA) {
        System.out.println("你给我矛,我给你盾");
        personA.get();
    }
    public synchronized void get() {
        System.out.println("personB 得到了盾");
    }
}

class ChangeWeapon implements Runnable{
    private PersonA personA = new PersonA();
    private PersonB personB = new PersonB();
    public ChangeWeapon() {
        new Thread(this).start();
        personA.tell(personB);
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        personB.tell(personA);
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        new ChangeWeapon();
    }
}
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

也可以写成:

class PersonA{
    public synchronized void tell(PersonB personB) {
        System.out.println("你给我盾,我给你矛");
        personB.get();
    }
    public synchronized void get() {
        System.out.println("personA 得到了盾");
    }
}

class PersonB{
    public synchronized void tell(PersonA personA) {
        System.out.println("你给我矛,我给你盾");
        personA.get();
    }
    public synchronized void get() {
        System.out.println("personB 得到了盾");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        PersonA personA = new PersonA();
        PersonB personB = new PersonB();
        new Thread(()->{
            personB.tell(personA);
        }).start();;
        personA.tell(personB);
    }
}
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

# 生产者与消费者

在多线程中有个经典案例,生产者和消费者问题,有两类线程分别是生产者和消费者,生产者生产出东西后放入仓库,消费者从仓库中取东西,我们需要保证如果仓库满了,生产者就停止生产,如果仓库中的东西被取完,消费者就停止取东西。

class DataRepo {
    private final int length = 50;
    private int beginIndex = 0;
    private int endIndex = 0;
    private boolean isInSameOrder = true;
    private int data[];
    private int produceNum=0;
    
    public DataRepo() {
        data = new int[length];
    }

    public int getData() {
        int tempData=0;
        if (isInSameOrder && endIndex == beginIndex) {
            System.out.println(Thread.currentThread().getName()+" 消费者:数据仓库已经没有数据了!");
            return -1;
        } else {
            tempData = data[beginIndex];
            System.out.println(Thread.currentThread().getName()+" 消费者:---------------取出数据:" + beginIndex + ":" + data[beginIndex]);
            beginIndex++;
            if(beginIndex>=length) {
                beginIndex %= length;
                isInSameOrder=!isInSameOrder;
            }
            return tempData;
        }
    }
    
    public void produceData() {
        System.out.println(Thread.currentThread().getName()+" 生产者 ------生产出数据:" + produceNum);
        if(setData(this.produceNum)) {
            System.out.println(Thread.currentThread().getName()+" 生产者 已经放入数据:"+produceNum);
            produceNum++;
        }
    }

    public boolean setData(int targetData) {
        if (!isInSameOrder && endIndex+1 == beginIndex) {
            System.out.println(Thread.currentThread().getName()+" 生产者:数据仓库已经满了!");
            return false;
        } else {
            data[endIndex] = targetData;
            System.out.println(Thread.currentThread().getName()+" 生产者 写入数据:" + endIndex + ":" + data[endIndex]);
            endIndex++;
            if(endIndex >= length) {
                endIndex %= length;
                isInSameOrder=!isInSameOrder;
            }
            return true;
        }
    }
}

class Producer implements Runnable {

    private DataRepo dataRepo = null;

    public Producer(DataRepo tempData) {
        this.dataRepo = tempData;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true)
            try {
                Thread.sleep(50);
                dataRepo.produceData();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}

class Consumer implements Runnable {
    private DataRepo dataRepo = null;

    public Consumer(DataRepo tempData) {
        this.dataRepo = tempData;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true)
            try {
                Thread.sleep(200);
                dataRepo.getData();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        DataRepo dataRepo = new DataRepo();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

这里面会产生的问题是:生产者会生产同样的数字,消费者也会消费同样的数字,解决的办法是将生产数据,消费数据的方法变为同步方法,同一时间只有一个生产者和消费者。

# 使用同步方法

class DataRepo {
    private final int length = 50;
    private int beginIndex = 0;
    private int endIndex = 0;
    private boolean isInSameOrder = true;
    private int data[];
    private int produceNum=0;
    
    public DataRepo() {
        data = new int[length];
    }

    public synchronized int getData() {
        int tempData=0;
        if (isInSameOrder && endIndex == beginIndex) {
            System.out.println(Thread.currentThread().getName()+" 消费者:数据仓库已经没有数据了!");
            return -1;
        } else {
            tempData = data[beginIndex];
            System.out.println(Thread.currentThread().getName()+" 消费者:---------------取出数据:" + beginIndex + ":" + data[beginIndex]);
            beginIndex++;
            if(beginIndex>=length) {
                beginIndex %= length;
                isInSameOrder=!isInSameOrder;
            }
            return tempData;
        }
    }
    
    public synchronized void produceData() {
        System.out.println(Thread.currentThread().getName()+" 生产者 ------生产出数据:" + produceNum);
        if(setData(this.produceNum)) {
            System.out.println(Thread.currentThread().getName()+" 生产者 已经放入数据:"+produceNum);
            produceNum++;
        }
    }

    public boolean setData(int targetData) {
        if (!isInSameOrder && endIndex+1 == beginIndex) {
            System.out.println(Thread.currentThread().getName()+" 生产者:数据仓库已经满了!");
            return false;
        } else {
            data[endIndex] = targetData;
            System.out.println(Thread.currentThread().getName()+" 生产者 写入数据:" + endIndex + ":" + data[endIndex]);
            endIndex++;
            if(endIndex >= length) {
                endIndex %= length;
                isInSameOrder=!isInSameOrder;
            }
            return true;
        }
    }
}

class Producer implements Runnable {

    private DataRepo dataRepo = null;

    public Producer(DataRepo tempData) {
        this.dataRepo = tempData;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true)
            try {
                Thread.sleep(50);
                dataRepo.produceData();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}

class Consumer implements Runnable {
    private DataRepo dataRepo = null;

    public Consumer(DataRepo tempData) {
        this.dataRepo = tempData;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true)
            try {
                Thread.sleep(200);
                dataRepo.getData();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        DataRepo dataRepo = new DataRepo();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

# 阻塞和唤醒

我们上面的案例是,只要缓存区不被填满,生产者就生产,只要缓存区不空,我们就让消费者消费。现在我们规定,生产一个出来,需要立即被消费,只有消费完之后才能继续生产。

class DataRepo {
    private final int length = 50;
    private int beginIndex = 0;
    private int endIndex = 0;
    private boolean isInSameOrder = true;
    private int data[];
    private int produceNum = 0;

    private boolean blockConsume = true;

    public DataRepo() {
        data = new int[length];
    }

    public synchronized void consumeData() {
        while (blockConsume) {
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " consume " + blockConsume);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        getData();
        blockConsume = true;
        this.notifyAll();
    }

    public synchronized void produceData() {
        while (!blockConsume) {
            try {
                this.wait();
                System.out.println(Thread.currentThread().getName() + " produce " + blockConsume);

            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        System.out.println(Thread.currentThread().getName() + " 生产者 ------生产出数据:" + produceNum);
        if (setData(this.produceNum)) {
            System.out.println(Thread.currentThread().getName() + " 生产者 已经放入数据:" + produceNum);
            produceNum++;
            blockConsume = false;
            this.notifyAll();
        }
    }

    private int getData() {
        int tempData = 0;
        if (isInSameOrder && endIndex == beginIndex) {
            System.out.println(Thread.currentThread().getName() + " 消费者:数据仓库已经没有数据了!");
            return -1;
        } else {
            tempData = data[beginIndex];
            System.out.println(Thread.currentThread().getName() + " 消费者:---------------取出数据:" + beginIndex + ":"
                    + data[beginIndex]);
            beginIndex++;
            if (beginIndex >= length) {
                beginIndex %= length;
                isInSameOrder = !isInSameOrder;
            }
            return tempData;
        }
    }

    private boolean setData(int targetData) {
        if (!isInSameOrder && endIndex + 1 == beginIndex) {
            System.out.println(Thread.currentThread().getName() + " 生产者:数据仓库已经满了!");
            return false;
        } else {
            data[endIndex] = targetData;
            System.out.println(Thread.currentThread().getName() + " 生产者 写入数据:" + endIndex + ":" + data[endIndex]);
            endIndex++;
            if (endIndex >= length) {
                endIndex %= length;
                isInSameOrder = !isInSameOrder;
            }
            return true;
        }
    }
}

class Producer implements Runnable {

    private DataRepo dataRepo = null;

    public Producer(DataRepo tempData) {
        this.dataRepo = tempData;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true)
            try {
                Thread.sleep(50);
                dataRepo.produceData();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }

}

class Consumer implements Runnable {
    private DataRepo dataRepo = null;

    public Consumer(DataRepo tempData) {
        this.dataRepo = tempData;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true)
            try {
                Thread.sleep(50);
                dataRepo.consumeData();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        DataRepo dataRepo = new DataRepo();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Producer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
        new Thread(new Consumer(dataRepo)).start();
    }
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

# 深入理解多线程

两个基本概念:

  • 锁池:比如线程A拥有某个对象(不是类)的锁,其他线程要调用这个对象的一个synchronized方法或块,这些线程需要先获取该对象的拥有权,如果该对象被线程A拥有,其他线程进入该对象的锁池中。
  • 等待池:当一个线程A调用了某个对象的wait()方法,线程A会释放该对象的锁,进入该对象的等待池。

如果线程调用了某对象的wait()方法,该线程会处于该对象的等待池中,这里面的线程是不会去竞争对象的锁;唤醒锁池(notify,notifyAll)指的是将线程从等待池移到某个对象的锁池中,参与锁的竞争,如果竞争成功则执行,不成功则不执行。

notify和notifyAll的区别:

  • notify:只唤醒一个等待该对象的线程,如果多个线程等待该对象,该方法只会随机唤醒其中一个线程,也就是只有一个线程进入该对象的锁池,竞争对象锁。
  • notifyAll:会唤醒所有等待该对象的线程,也就是将所有等待该对象的线程移入该对象的锁池,去竞争该对象的锁。

锁池中优先级高的线程竞争到对象锁的概率大,如果没有竞争到对象锁,会留在锁池中,除非调用wait,重新返回等待池,否则它会一直参与竞争知道完成synchronized代码,释放对象锁。注意:

  • wait,notify/notifyAll都是Object的本地final方法,无法被重写。
  • 要是线程阻塞,唤醒,需要先获取对象锁,一般是在synchronized方法中使用。

# 线程状态

这里的三个方法基本都是对线程状态的操作,比如:

  • stop:停止多线程。
  • suspend:挂起线程。
  • resume:恢复挂起的线程。

从JDK1.2开始,不推荐使用上面的方法,因为容易产生死锁,可以通过标志变量来控制线程状态。

public class Main {
    public static boolean runProgram=true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            long n=0;
            while(runProgram) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" n="+n++);
            };
        },"线程A").start();
        Thread.sleep(3000);
        runProgram=false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 守护进程

Java中的线程基本分为用户线程和守护线程,守护线程(Daemon)是一种运行在后台的线程,当用户线程存在时,守护线程也存在,用户线程消失,守护线程也消失。

用户线程是用户自己开发或系统分配的主线程,守护线程就像是用户线程的保镖,用户线程一旦结束,守护线程就没有存在的必要。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            long n=0;
            for(int i=0;i<10;i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" n="+n++);
            };
        },"用户线程A").start();
        Thread daemonThread = new Thread(()->{
            long n=0;
            for(int i=0;i<100;i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" n="+n++);
            };
        },"守护线程");
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}
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

# volatile

在多线程编程中,几个线程为了实现公共资源操作,经常是复制相应变量作为一个副本,后面对该副本操作完成后将此副本变量数据与原始变量做对比进行同步处理,如果开发者不希望操作副本而是对原始变量进行操作,可以对变量使用volatile关键字。

注意volatile无法描述同步处理,它只是让开发者直接操作内存,避免复制副本,而synchronized是实现同步的关键字,volatile主要是在属性上使用,而synchronized则是在代码块与方法上使用的。