串行与并发

  1. 串行 当有多个任务需要执行时,是按照从前往后的顺序去执行的顺序就叫串行。
    类比的话就是一条单行道上的车辆肯定是前后顺序,不可能同时通行多辆车。

  2. 并发是让多个任务同时执行。
    一条马路上的两条单行车道上两辆车同时执行,这两辆车就是并发的状态。

程序的运行状态一般是串行与并发同步进行的。

进程

进程是对一个程序运行是各种资源的描述(cpu,内存等),进程实现多个任务并发运行。
进程资源不共享。

线程

线程是进程的最小单元。
如果把进程看成一座工厂,进程就是进程中的流水线。
一个进程中有多个线程,且一个进程中有多个线程。
线程资源共享(临界资源)。

并发是怎么做到的

计算机给某个进程(或线程)分配固定的cpu时间,让cpu快速的在不同进程(或线程)中快速切换,来造成同时运行的假象。
cpu的运行速度是非常快的,人反应不过来,这才导致的并发假象。 (是假象)

线程的生命周期

从被实例化完成到被销毁。
线程的状态:

  1. 新生态 New :一个线程被实例化完成,没有做任何操作。
  2. 就绪态 Ready:一个线程已经被开启,已经开始争抢cpu时间片(某段时间去运行这个线程)。
  3. 运行态 Run: 一个线程抢到了cpu时间片,已经开始执行现成的逻辑。
  4. 阻塞态 Interrupt:正在运行的过程中受到某些操作的影响,放弃了已经获取到的cpu时间片,并且不再参与cpu时间片的争抢,处于挂起状态。(scanf sleep join等)
  5. 死亡态 Dead:一个线程对象被销毁。

创建实例化线程

  1. 继承Thread类,自定义线程类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.p1.theard;

    public class ThreadCreate1 {
    public static void main(String[] args) {
    //实例化子线程
    MyThread myThread = new MyThread();
    //通过start方法开启线程,如果直接调用run方法他会把线程当作普通类来运行
    myThread.start();
    //在线程之后打印句话来感受下线程
    System.out.println("主线程逻辑");
    }
    }
    //继承Thread并重写run方法
    class MyThread extends Thread{
    @Override
    public void run() {
    for (int i=0; i<10; i++){
    System.out.println("子线程逻辑"+i);
    }
    }
    }

    执行后会发现先执行的主线程逻辑这句话,然后才是子线程逻辑。

  2. 通过Runnable接口

    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
    package com.p1.theard;

    public class ThreadCreate2 {
    public static void main(String[] args) {
    Runnable r = new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i < 9; i++) {
    System.out.println("子线程逻辑1:"+i);
    }
    }
    };
    //lambda表达式
    Runnable r1 = () -> {
    for (int i = 0; i < 9; i++) {
    System.out.println("子线程逻辑2:"+i);
    }
    };
    //通过有参来实例化线程
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r1);
    //启动
    t1.start();
    System.out.println("主线程逻辑1");
    t2.start();
    System.out.println("主线程逻辑2");

    }
    }

    通过多次运行能发现他们每次运行的每个线程运行时间都是不一样的,更好的感受下争抢cpu时间片这个概念。

线程的命名

  1. 实例化对象同时通过构造方法命名

    1
    2
    3
    4
    5
    6
    7
    8
    package com.p1.theard;

    public class Thread_name {
    public static void main(String[] args) {
    Thread t1 = new Thread("custom1");
    System.out.println(t1.getName());
    }
    }
  2. Runnable接口创建同时命名

    1
    2
    3
    4
    5
    6
    7
    8
    package com.p1.theard;

    public class Thread_name {
    public static void main(String[] args) {
    Thread t1 = new Thread(() -> {},"custom2");
    System.out.println(t1.getName());
    }
    }
  3. 自定义线程类,实例化同时进行名称赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.p1.theard;
    public class Thread_name {
    public static void main(String[] args) {
    MyThread2 myThread2 = new MyThread2("custom3");
    System.out.println(myThread2.getName());
    }
    }
    class MyThread2 extends Thread{
    public MyThread2(String name){
    this.setName(name);
    }
    }

线程的休眠

Thread.sleep()  

以毫秒为单位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.p1.theard;

public class Thread_sleep {
public static void main(String[] args) {
MyThread3 myThread3 = new MyThread3();
myThread3.start();
}
}
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

线程的优先级

设置线程的优先级只是去修改这个线程抢到cpu时间片的概率。
不代表优先级高的一定能抢到cpu时间片。
优先级的设置是一个(0,10]的整数,默认是5。
设置优先级必须要放到start之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.p1.theard;

public class Thread_first {
public static void main(String[] args) {
Runnable r1 = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+""+i);
}
};
Thread t1 = new Thread(r1,"Thread-1:");
Thread t2 = new Thread(r1,"Thread-2:");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}

线程的礼让

让当前运行状态的线程释放自己的cpu资源,由运行状态回到就绪状态,然后重新抢cpu时间片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.p1.theard;

public class Thread_yield {
public static void main(String[] args) {
Runnable r1 = () ->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+""+i);
if (i == 3){
Thread.yield();
}
}
};
Thread t1 = new Thread(r1,"thread-1:");
Thread t2 = new Thread(r1,"thread-2:");
t1.start();
t2.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
package com.p1.theard;

public class ScorceFilct {
public static void main(String[] args) {
//实例化四个线程来模拟四个售票员
Runnable r = () -> {
while (TicketClent.restCount > 0){
System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余"+ --TicketClent.restCount);
}
};
Thread t1 = new Thread(r,"thread-1");
Thread t2 = new Thread(r,"thread-2");
Thread t3 = new Thread(r,"thread-3");
Thread t4 = new Thread(r,"thread-4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//票库
class TicketClent{
//票库中的100张票
public static int restCount = 100;
}

按照正常逻辑,最后一条输出应该是Jon卖出1张票,剩余0,但事实并不是这样。

System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余"+ --TicketClent.restCount);

这行代码的运行是先去将TicketClent类中的restCount变量减1

从图上可以看出

先是由thread-1抢到了cpu时间片,thread-1刚去将restCount减一,还没来得及打印或者刚刚打印,就被thread-2抢走了cpu时间片(剩余99张)

thread-2刚执行完减一操作,没有来得及打印又被thread-4抢去了cpu时间片(剩余98张)(第97张在比较靠下)

thread-4也是减一完之后没有来得及打印,被thread-3抢去cpu时间片(剩余97张)

thread-3减一之后没有打印就被thread-1抢去cpu时间片(剩余96)

thread-1得到cpu时间片之后完成了减一打印一系列操作,进行了两次循环之后被thread-2抢去了cpu时间片

thread-2得到cpu时间片之后先把没有来得及打印的东西打印出来,然后正常执行减一打印操作,一路高歌猛进。

以此类推就能明白整个逻辑
(ps:thread-3,thread-4真水,前边就个抢到一次)

解决临界资源问题(线程锁)

临界资源问题的本质是多个线程同时访问同一个资源
解决方法:在某个线程访问临界资源时,在资源外边加一把锁,其他资源发现锁之后就等待,等到解锁之后再去操作资源。

线程锁:

多个同时访问临界资源的线程看到的锁需要时同一把锁

同步代码段(synchroized)

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
package com.p1.theard;

public class ScorceFilct {
public static void main(String[] args) {
//实例化四个线程来模拟四个售票员
Runnable r = () -> {
while (TicketClent.restCount > 0){
//对象锁,在括号中写对象
//类锁: 在括号中写类
synchronized (""){
System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余" + --TicketClent.restCount);
}
}
};
Thread t1 = new Thread(r,"thread-1");
Thread t2 = new Thread(r,"thread-2");
Thread t3 = new Thread(r,"thread-3");
Thread t4 = new Thread(r,"thread-4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//票库
class TicketClent{
//票库中的100张票
public static int restCount = 100;
}

运行之后我们会发现出现负数

原因是当restCount为1时,其他while循环成立,线程进去之后发现锁,就会在while里边锁外边等待正在执行的线程结束释放资源解锁,解锁之后刚运行完的线程发现不满足循环条件就不会再去争抢,而在循环里边的线程不回去管restCount的值继续去执行。

解决方法:在锁中加一个判断,判断restCount是否大于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
package com.p1.theard;

public class ScorceFilct {
public static void main(String[] args) {
//实例化四个线程来模拟四个售票员
Runnable r = () -> {
while (TicketClent.restCount > 0){
synchronized (""){
if (TicketClent.restCount>0) {
System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余" + --TicketClent.restCount);
}
}
}
};
Thread t1 = new Thread(r,"thread-1");
Thread t2 = new Thread(r,"thread-2");
Thread t3 = new Thread(r,"thread-3");
Thread t4 = new Thread(r,"thread-4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//票库
class TicketClent{
//票库中的100张票
public static int restCount = 100;
}

同步方法 (使用关键字synchronized修饰的方法)

静态方法:同步锁就是类锁 当前类.class
非静态方法:this

将买票(执行逻辑)放到一个单独的方法里边,并且用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
package com.p1.theard;

public class ScorceFilct {
public static void main(String[] args) {
//实例化四个线程来模拟四个售票员
Runnable r = () -> {
while (TicketClent.restCount > 0){
soidCket();
}
};
Thread t1 = new Thread(r,"thread-1");
Thread t2 = new Thread(r,"thread-2");
Thread t3 = new Thread(r,"thread-3");
Thread t4 = new Thread(r,"thread-4");
t1.start();
t2.start();
t3.start();
t4.start();
}
public synchronized static void soidCket(){
if (TicketClent.restCount>0) {
System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余" + --TicketClent.restCount);
}
}
}
//票库
class TicketClent{
//票库中的100张票
public static int restCount = 100;
}

显式(ReentrcntLock)

创建ReentecntLock对象 在代码段前后用
lock()方法(代码段前)
unlock()方法(代码段后)

死锁

多个线程彼此持有对方需要的锁,而不去释放自己的锁

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
package com.p1.theard;

public class DeadLock {
public static void main(String[] args) {
Runnable r1 = () ->{
synchronized ("A"){
System.out.println("r1线程持有a锁,等待b锁");
synchronized ("B"){
System.out.println("r1线程同时持有a,b锁");
}
}
};
Runnable r2 = () ->{
synchronized ("B"){
System.out.println("r2线程持有b锁,等待a锁");
synchronized ("A"){
System.out.println("r2线程同时持有a,b锁");
}
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}

没有任何一个线程能去同时持有a锁和b锁
并且程序没有结束,线程都在等待对方解锁
我们在程序中尽量要去避免死锁

解死锁

wait:等待,Object类中的一个方法,让当前的线程,并且让出cpu资源,并且让线程进入等待队列。

notify: 通知,Object类中的一个方法,唤醒等待队列中的一个线程,使这个线程进入锁池。

notifyAll:通知,Object类中的一个方法,唤醒等待队列的所有线程,使所以线程进入锁池。

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
package com.p1.theard;

public class DeadLock {
public static void main(String[] args) {
Runnable r1 = () ->{
synchronized ("A"){
System.out.println("r1线程持有a锁,等待b锁");
try {
//r1释放掉A线程,并且r1进入等待队列,等待唤醒
"A".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("B"){
System.out.println("r1线程同时持有a,b锁");
}
}
};
Runnable r2 = () ->{
synchronized ("B"){
System.out.println("r2线程持有b锁,等待a锁");
synchronized ("A"){
System.out.println("r2线程同时持有a,b锁
");
}
}
};
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}

使用wait之后发现r2抢到了a锁和b锁,释放了b锁
但r1没有继续进行,而此时程序也没有正常退出。
是因为r1线程进入了等待队列,还没有被唤醒。
在r2完成之后去唤醒r1就能使r1进入b锁


多线程就先到这里,初学可能有很多表达不清,如果看官您因为在这篇博客中有疑问而理解不了问题可以加本人qq:397712823。