Java 多线程与并发编程

进程和线程

就像CSAPP中我们学习的,计算机只有一个CPU,但有很多个进程,共享一个CPU,计算机将若干个时间切片分配给一个进程使用。

而一个进程有若干个线程,这些线程共享一个进程的资源。

多线程

Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。

创造多线程的方法

  1. 继承Thread类,重写该类的run()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Abc {
    public static void main(String[] args) {
    for (int i = 1; i <= 100; i++) {
    System.out.println(Thread.currentThread().getName() + " " + i);
    if(i == 50) {
    Thread t1 = new MyThread();
    t1.start();
    Thread t2 = new MyThread();
    t2.start();

    }
    }
    }
    }

    class MyThread extends Thread {
    @Override
    public void run() {
    for (int i = 1; i <= 100; i++) {
    System.out.println(Thread.currentThread().getName() + " " + i);
    }
    }
    }

    输出

    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
    ...
    main 46
    main 47
    main 48
    main 49
    main 50
    main 51
    main 52
    main 53
    main 54
    main 55
    Thread-1 1
    ...
    Thread-0 89
    main 96
    Thread-0 90
    Thread-1 58
    Thread-0 91
    main 97
    Thread-0 92
    Thread-1 59
    Thread-0 93
    main 98
    Thread-0 94
    Thread-1 60
    ...
  1. 实现Runnable接口,并重写该接口的run()方法

    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
    package test;

    public class Abc {
    public static void main(String[] args) {
    for(int i = 1; i <= 100; i++) {
    System.out.println(Thread.currentThread().getName() + " " + i);
    if(i == 50) {
    Thread t1 = new MyThread();
    t1.start();
    Thread t2 = new MyThread();
    t2.start();
    Thread t3 = new Thread(new MyRunnable());
    t3.start();
    Thread t4 = new Thread(new MyRunnable());
    t4.start();
    }
    }
    }
    }

    class MyThread extends Thread {
    @Override
    public void run() {
    for(int i = 1; i <= 100; i++) {
    System.out.println(Thread.currentThread().getName() + " " + i);
    }
    }
    }

    class MyRunnable implements Runnable {
    @Override
    public void run() {
    for(int i = 1; i <= 100; i++) {
    System.out.println(Thread.currentThread().getName() + " " + i);
    }
    }
    }

    输出和上一个类似。

Thread和Runnable的关系

Q&A

  1. t1.start()在干嘛?

    start()JDK文档如下

    Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.

    The result is that two threads are running concurrently: the current thread (which returns from the call to the start method) and the other thread (which executes its run method).

    It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.

    但是查看源码发现start()方法并没有调用run()方法,而是将这个进程加入到进程组group中。

    当调用线程对象的start()方法,线程即进入就绪状态(Runnable State)。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行run()方法,并不是说执行了t.start()此线程立即就会执行;

  2. Runnable接口?

    1
    2
    3
    public interface Runnable {
    public abstract void run();
    }

    Thread类是实现了Runnable接口。

  3. 如何构造Thread对象?

    • Thread类中有一个Runnable对象

      1
      private Runnable target;
    • 构造函数

      1
      2
      3
      4
      5
      6
      7
      public Thread();
      public Thread(Runnable target);
      public Thread(ThreadGroup group, Runnable target); //进程组group
      public Thread(String name);
      public Thread(ThreadGroup group, String name);
      public Thread(Runnable target, String name);
      public Thread(ThreadGroup group, Runnable target, String name);

      所有构造方法都依赖于init()。可以看出默认进程名字是Thread-x

      1
      2
      3
      4
      5
      6
      public Thread(ThreadGroup group, Runnable target) {
      init(group, target, "Thread-" + nextThreadNum(), 0);
      }

      private void init(ThreadGroup g, Runnable target, String name,
      long stackSize)
    • run()方法

      1
      2
      3
      4
      5
      6
      @Override
      public void run() {
      if (target != null) {
      target.run();
      }
      }
  4. 分析2.1两份多线程的程序

    • MyThread extends Thread

      我们在MyThread中重写了run()方法,当线程处于运行状态时,调用的时我们自己重写的run()方法,从而得以完成我们想要他完成的任务。

    • MyRunnable extends Runnable

      我们构造thread时传入一个target对象,当线程处于运行状态时,调用Threadrun()方法,这个方法中调用了target.run()

其他方法

  1. Thread.yield()

    A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

    线程让步。就像官方文档说的,yield()提供一个这样一个机制:通知调度者当前的线程愿意放弃当前的处理器资源,但是如果可能,过段时间,它愿意回来继续运行。

    但是调度者可以自由地遵守或忽略该信息,事实上,不同的操作系统表现不尽相同。

    注意:

    • 线程yield后,如果有很多个相同优先级的在等待运行,我们无法指定哪个线程运行。
    • yield的线程会进入Runnable State。
    • 线程一旦yield,我们无法知晓什么时候接着运行。
    • 依赖操作系统。
  2. Thread.sleep(long millis) / Thread.sleep(long millis, int nanos)

    ms / ms + ns

    Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.

    • 线程进入Sleeping State
    • sleep()会导致线程停止运行一段时间。如果没有其他的线程,那么CPU将会处于空闲状态。
  3. t.wait() / t.wait(long timeout) / t.wait(long timeout, int nanos)

    Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.

    wait()没有or后边的选项。

    • wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify()notifyAll()方法来唤醒线程
    • wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify()notifyAll()方法的唤醒,也会自动唤醒。

    • wait(0)wait()等价

  4. t.notify()/ t.notifyAll()

    Wakes up a single thread that is waiting on this object’s monitor.

    Wakes up all threads that are waiting on this object’s monitor.

  5. t.join()

    t.join()

    Waits for this thread to die.

    t.join(long millis)

    Waits at most millis milliseconds for this thread to die. A timeout of 0 means to wait forever.

    t.join(long millis, int nanos)

    Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.

    t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。

synchronized关键字

概述

在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在(也就是所谓实例锁。后面可以看到,其实还有可以对整个类上锁的全局锁)。而synchronized就是对象的一个同步锁。这个关键字的作用是,被这个关键字修饰的部分代码,称为互斥区。当某个线程在访问这段代码的时候,其他线程对这段代码的访问是互斥的,被称为临界区。通俗点讲,临界区就是某个对象的一个加了锁的一段代码。假设当前只有两个线程t1和t2。当线程t1在访问某个对象的这段代码的时候,如果没访问完,突然线程t1的时间片被操作系统剥夺了,时间片给了t2。t2也想访问某个对象的这段代码。这个时候,t2是不被允许访问这段代码的。t2就会被阻塞,然后时间片交还给t1,直到t1执行完这段代码了,t1才释放锁,t2才有权访问这段代码。这就很好地避免了临界区内代码被其他线程破坏的可能性。记住,这段临界区不一定在程序上看起来是连续的。一个对象,使用synchronized关键字,有且仅可以划定一个临界区。

synchronized关键字有三种使用方法:修饰代码段、修饰方法、修饰一个对象。

实例(错误)

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
public class Abc {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
System.out.println(MyRunnable.getCnt());
}
}

class MyRunnable implements Runnable {
private static int cnt = 0;

@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
cnt++;
}
}

public static int getCnt() {
return cnt;
}
}

分析:t1、t2、main三个线程并发,main很早结束,输出结果为0。

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
public class Abc {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyRunnable.getCnt());
}
}

class MyRunnable implements Runnable {
private static int cnt = 0;

@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
cnt++;
}
}

public static int getCnt() {
return cnt;
}
}

分析:t1.join()t2.join()等待两个线程程结束再输出,但因为t1、t2并行,输出值小于2000,并且每次也不尽相同。

synchronized修饰代码段

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
public class Abc {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyRunnable.getCnt());
}
}

class MyRunnable implements Runnable {
private static int cnt = 0;

@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
synchronized (this) {
cnt++;
}
}
}

public static int getCnt() {
return cnt;
}
}

分析:输出2000,因为cnt++;被加了锁。

但是,

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
public class Abc {
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyRunnable.getCnt());
}
}

class MyRunnable implements Runnable {
private static int cnt = 0;

@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
synchronized (this) {
cnt++;
}
}
}

public static int getCnt() {
return cnt;
}
}

分析:输出小于2000,synchronized只锁定对象。

synchronized修饰方法

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
public class Abc {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyRunnable.getCnt());
}
}

class MyRunnable implements Runnable {
private static int cnt = 0;

@Override
public synchronized void run() {
for (int i = 1; i <= 1000; i++) {
cnt++;
}
}

public static int getCnt() {
return cnt;
}
}

synchronized修饰对象

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
public class Abc {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MyRunnable.getCnt());
}
}

class MyRunnable implements Runnable {
private static Cnt cnt = new Cnt();

@Override
public synchronized void run() {
for (int i = 1; i <= 1000; i++) {
synchronized (cnt) {
cnt.add();
}
}
}

public static int getCnt() {
return cnt.getCnt();
}
}

class Cnt {
private int cnt = 0;

void add() {
cnt++;
}

int getCnt() {
return cnt;
}
}