多进程与多线程的区别?

​ 本质的区别在于每个进程拥有自己的一整套变量,而线程则是共享数据。

线程的状态

  • New(新建)
  • Runnable(可运行)
  • Blocked(阻塞)
  • Waiting(等待)
  • Timed waiting(计时等待)
  • Terminated(终止)

​ 确定一个线程的当前状态,只需要调用getState方法。当用new关键字创建一个新线程时,如new Thread(r),这个线程还没有开始运行。那意味着它的状态是新建(new)。当一个线程处于新建状态的时候,程序还没有开始运行线程中的代码。一旦调用start方法,线程就处于可运行(runnable)状态。这个线程具体什么时间开始运行,这取决于操作系统的的调用而不是代码的顺序。抢占式调度系统给每一个可运行线程一个时间片来执行任务。当时间片用完时操作系统剥夺该线程的运行权,并给另一个线程一个机会来运行。当选择下一个线程时,操作系统会考虑线程的优先级。

竞态条件

​ 如上所述,线程间的数据是共享的,这也就引发了线程不安全的问题。有两个线程同时执行指令

1
account[to] += account;

这可能被拆分成三条机器指令

1、将account[to] 加载到寄存器。

2、增加account。

3、将结果写回account[to]

现在假设第一个线程执行完步骤1、2然后运行权被抢占。第二个线程执行完了3个步骤,此时线程一被唤醒,此时线程一将继续执行步骤3,即抹去线程二所做的更新。

锁对象

​ 为了防止线程不安全,最简单的方式就是给该资源加锁。为达到这一目的可以使用synchronized,或者JUC的ReetrantLock。ReetrantLock保护代码块的基本结构如下:

1
2
3
4
5
6
7
8
9
Lock lock = new ReentrantLock();
lock.lock();
try{
critical section
}
finally
{
lock.unlock();
}

这里要注意一定要把unlock操作放到finally子句中,因为一旦临界区抛出异常,锁必须释放,否则,其他线程将永远无法访问该资源。此外在try块中做判断时不允许使用if,必须使用while因为if是单次判断,假设if块中的代码被阻塞,然后唤醒,会直接向下执行而不是再次做判断,这会导致有些线程虽然被唤醒但此时并不满足if条件,但代码仍会继续执行。为了防止这种情况就必须使用while判断。

wait()、sleep()区别

wait()会放弃锁,而sleep()不会。

Callable与Runnable

获得线程的方式:

  • 继承thread类
  • 实现runnable接口
  • 实现callable接口
  • 通过线程池获得

Callable与Runnable的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyThread implements Runnable{
@Override
public void run() {

}
}

class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
  • 是否有返回值
  • 是否抛异常
  • 落地方法不一样,一个是run,一个是call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
* @Author:Gao
* @Date:2020-07-24 16:32
*/

class MyThread implements Callable<Integer>{

@Override
public Integer call() throws Exception {
System.out.println("_______MyThread");
return 1024;
}
}

public class CallableDemo {
public static void main(String[] args) throws Exception{
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask,"A").start();
System.out.println(futureTask.get());
}
}

控制线程调度顺序

CountDownLatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.CountDownLatch;

/**
* @Author:Gao
* @Date:2020-07-25 12:35
* CountDownLatch 主要有两个方法,当一个或多个线程调用await方法的时候,这些线程会阻塞
* 其他线程调用countDown方法时会将计数器减1(调用countDown方法的线程不会阻塞),
* 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
*/
public class CountDownDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await();
/*运行到这里的时候Main线程会因为被await而被阻塞,直到CountDownLatch的计数器归0是才被唤醒*/
System.out.println(Thread.currentThread().getName() + "Main");
}
}

CyclicBarrier

CountDownLatch是做减法,CyclicBarrier是做加法public CyclicBarrier(int parties, Runnable barrierAction)中的parties是临界点,阻塞的线程数量到达临界点的时候执行barrierAction定义的方法。

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
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
* @Author:Gao
* @Date:2020-07-25 12:59
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
// public CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
System.out.println("******");
});
for (int i = 1; i <= 7; i++) {
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException e){
e.printStackTrace();
} catch (BrokenBarrierException e){
e.printStackTrace();
}
},String.valueOf(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
29
30
31
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
* @Author:Gao
* @Date:2020-07-25 13:06
* Semaphore定义两种操作:
* acquire(获取)当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
* 要么一直等待,直到有线程释放信号量,或超时。
* release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
* 信号量主要用于两个目的,一个用于多个共享资源的互斥使用,另一个用于并发线程数的控制
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); //模拟资源类,有三个资源
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t得到资源");
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t释放资源");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}

读写锁ReadWriteLock

一个更细粒度的锁,在使用synchronized时会把所有同步方法都锁住,也就是所有操作都变成串行了。而ReadWriteLock提供读锁和写锁,读时可以并发的读,写时再独占资源。

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
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @Author:Gao
* @Date:2020-07-25 13:31
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t写入数据" + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}

public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t读取数据");
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t读取完成" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}

public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt + "", tempInt + "");
}, String.valueOf(i)).start();
}

for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(tempInt + "");
}, String.valueOf(i)).start();
}
}
}