线程

概念

线程是进程中的单个顺序执行流,是一条执行路径。一个进程如果只有一条执行路径,则成为单线程程序;而如果有多条执行路径,则成为多线程程序。

CPU分时调度

时间片,即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。

如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。

在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。

在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力。

单核CPU和多核CPU(了解):

单核CPU同时只能干一件事情,当启动多个程序时,CPU快速切换轮流处理每个程序,但如果CPU不够强劲,同时排队等待处理的任务太多,就会感觉系统会有延时、反应慢、卡顿等,甚至某一个程序在处理时出现错误,无法响应了,会造成后面排队的其他任务只能等待。

多核CPU是在基板上集成多个单核CPU+任务分配系统,两个核心同时处理两份任务,相比单核执行速度更快,有利于同时运行多个程序,不容易造成卡顿,更流畅!

Java程序的运行过程:

  1. 通过eclipse(java命令)运行一个Java程序,java命令会启动JVM(java虚拟机),等于启动了一个应用程序,也就是启动了一个进程。
  2. 该进程会自动启动一个“主线程”,然后主线程去调用某个类的 main 方法,所以 main 方法运行在主线程中。在此之前的所有程序代码都是按照顺序依次调用的,都是单线程的。

如果希望程序中实现多段程序代码交替执行的效果,则需要创建多线程程序。

为什么要使用多线程?

  • 单线程程序执行时都只有一条顺序执行流,如果程序执行某行代码时遇到了阻塞(比如:抛异常),那么程序的运行将会停滞在这一行,其他代码将会无法执行!

  • 这就像去银行办理业务,只有1个业务窗口(单线程),所有的客户都需要在一个窗口排队办理业务,如果业务员在为某一个客户办理业务时,花费了很长时间,那么将会导致后面的客户等待很长时间,这样处理业务的效率也是非常低的。

  • 但如果银行为了提高效率,同时开放了5个窗口(多线程),客户可以分布在这5个窗口分别办理业务,即使某一个窗口在为个别客户办理业务时花费了很长时间,但不影响其他窗口办理业务的进度。

多线程理解起来其实非常简单:

  • 单线程的程序只有一个顺序执行流。
  • 多线程的程序则可以包括多个顺序执行流,多个顺序流之间互不干扰。

[并行]和[并发]的区别: (这是两个概念)

  1. 并行执行指在同一时刻,有多条指令在多个处理器上同时执行;
  2. 并发执行指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

多线程的特性

随机性

多线程的程序在执行时,在某一时间点具体执行哪个线程是不确定的,可以确定的是某一个时间点只有一个线程在执行(单核CPU)。

虽然我们感觉这些线程像是在同时运行,实际上是因为CPU在快速切换轮流执行这些线程,由于切换速度是纳秒级别的,所以我们感觉不到。

如何实现多线程

由于线程是依赖进程存在的,因此首先需要创建一个进程,但进程是操作系统创建的,而Java程序是不能直接调用系统功能的。但Java可以去调用C或C++写好的程序去创建进程从而实现多线程程序。

在Java中要想实现多线程操作有两种方式,一种是继承Thread类,另一种是实现Runnable接口。接下来针对这两种创建多线程的方式分别进行讲解。

实现多线程1: 继承Thread类

Thread类概述

Thread类是在java.lang包中定义的类

JavaSE规范中规定,一个类只要继承了Thread类,此类就是多线程的子类。

在Thread子类中,必须重写该类的run()方法,此方法为线程的主体。

通过继承Thread类创建线程

接下来通过案例演示<通过继承Thread类的方式实现多线程>

下面创建线程,并测试主线程和创建的新线程(子线程)交替执行的效果

代码实现如下:

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

/**
* @author 老安
* @data 2022/6/21 20:18
* 多线程
* 多线程可以并发执行多个任务
* 线程的第一种创建方式:
* 1:创建一个线程类,继承Thread类
* 2:在线程类中重写run方法
* 3:在run方法中写线程要执行的任务
* 4:实例化线程类对象
* 5:线程类对象调用方法start()启动线程
* 优点:结构简单
* 缺点:由于java中是单继承,此种方法已经继承了Thread,就无法再继承别的类了,
* 对于今后的实际开发造成很多不便
*/
public class ThreadDemo1 {
   public static void main(String[] args) {
       MyThread1 t1 = new MyThread1();
       MyThread2 t2 = new MyThread2();
       /*
        * 注意:启动线程不能调用run方法,因为run方法就是普通一个方法,
        * 必须调用start方法,调用start方法后,线程便会纳入线程调度器,
        * 一旦它被分到CPU时间片,就会开始自动执行线程中的run方法
        */
       t1.start();
       t2.start();
  }
}
//Thread 线程的超类
class MyThread1 extends Thread{
   //run方法是Thread类中定义的
   @Override
   public void run() {
       for(int i=0;i<1000;i++){
           System.out.println("你是谁啊?");
      }
  }
}
class MyThread2 extends Thread{
   //run方法是Thread类中定义的
   @Override
   public void run() {
       for (int i = 0; i < 1000; i++) {
           System.out.println("开门,我是查水表的!");
      }
  }
}

从上面的运行结果可以看出,main方法(主线程)和run方法(子线程)中的两个for循环中的输出语句交替执行了,说明通过集成Thread类实现了多线程。

(如果没有测试出主线程和子线程交替执行的效果,可以多测试几次!)

实现多线程2: 实现Runnable接口

Runnable接口概述

通过继承Thread类实现了多线程,但是这种方式有一定的局限性。因为Java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类,例如猫类Cat继承了动物类Animal,就无法通过继承Thread类实现多线程。

为了克服这种弊端,在Thread类中提供了两个构造方法:

public Thread(Runnable target)

public Thread(Runnable target,String name)

这两个构造方法都可以接收Runnable的子类实例对象,这样创建的线程将调用实现了Runnable接口类中的run()方法作为运行代码,而不需要调用Thread类的run()方法,所以就可以依靠Runnable接口的实现类启动多线程。

通过实现Runnable接口实现多线程

接下来通过案例演示<通过实现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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package thread;

/**
* @author 老安
* @data 2022/6/21 20:53
* 第二种创建线程方式:
* 1:创建一个类,实现Runnable接口
* 2:重写Runnable中的run方法
* 3:run方法中写线程要执行的任务
* 4:实例化线程任务对象
* 5:实例化线程对象,并将任务对象作为参数传入
* 6:线程对象调用start方法启动线程
*/
public class ThreadDemo2 {
   public static void main(String[] args) {
       //实例化线程要执行的任务对象
       MyRunnable1 r1 = new MyRunnable1();
       MyRunnable2 r2 = new MyRunnable2();
       //实例化线程对象,并将任务对象传入其中
       Thread t1 = new Thread(r1);
       Thread t2 = new Thread(r2);
       //启动线程
       t1.start();
       t2.start();
  }
}
//Runnable不是线程,只是用来承载线程执行的内容的容器而已
class MyRunnable1 implements Runnable{
   @Override
   public void run() {
       for (int i = 0; i < 1000; i++) {
           System.out.println("hello~大姐姐~~");
      }
  }
}
class MyRunnable2 implements Runnable{
   @Override
   public void run() {
       for (int i = 0; i < 1000; i++) {
           System.out.println("来了~老六~~");
      }
  }
}

从上面的运行结果可以看出,main方法(主线程)和run方法(子线程)中的两个for循环中的输出语句交替执行了,说明实现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
30
31
32
33
34
35
36
37
package thread;

/**
* @author 老安
* @data 2022/6/21 21:05
* 使用匿名内部类创建线程的方式
*/
public class ThreadDemo3 {
   public static void main(String[] args) {
       //直接继承Thread类重写run方法的形式
       Thread t1 = new Thread() {
           @Override
           public void run() {
               for (int i = 0; i < 1000; i++) {
                   System.out.println("who are you~~");
              }
          }
      };
       t1.start();
       //实现Runnable接口重写run方法的形式
//       Runnable r2 = new Runnable() {
//           @Override
//           public void run() {
//               for (int i = 0; i < 1000; i++) {
//                   System.out.println("i'm 老六~");
//               }
//           }
//       };
       Runnable r1 = () -> {
           for (int i = 0; i < 1000; i++) {
               System.out.println("i'm 老六~");
          }
      };
       Thread t2 = new Thread(r1);
       t2.start();
  }
}

Thread的常用方法和总结

CurrentThreadDemo

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

/**
* @author 老安
* @data 2022/6/21 21:21
* java中所有的代码都是靠线程运行的,main方法也需要由线程执行,
* JVM启动后,会创建一个线程,专门执行main方法,这个线程的名字,
* 叫做main,因此我们通常称这条线程叫做主线程.
* 但是主线程与我们创建的线程并无区别
* Thread类中提供了一个静态方法:
* static Thread currentThread()
* 该方法可以返回运行这个方法的线程
*/
public class CurrentThreadDemo {
   public static void main(String[] args) {
       Thread main = Thread.currentThread();
       doSome();
       //主线程:Thread[main,5,main]
       // 第一个main:线程名 5:优先级 第二个main:线程组
       System.out.println("主线程:"+main);
       new Thread("无敌霸王鸡"){
           @Override
           public void run() {
               Thread t = Thread.currentThread();
               System.out.println(t);
               doSome();
          }
      }.start();
  }
   public static void doSome(){
       Thread t = Thread.currentThread();
       System.out.println("运行doSome方法的线程:"+t);
  }
}

ThreadInfoDemo

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 thread;

/**
* @author 老安
* @data 2022/6/23 19:37
* 获取线程相关的一组方法
*/
public class ThreadInfoDemo {
   public static void main(String[] args) {
       Thread main = Thread.currentThread();//获取到主线程
       String name = main.getName();//获取线程名
       System.out.println("名字:"+name);
       long id = main.getId();//获取线程的唯一标识(类似于人的身份证号)
       System.out.println("id:"+id);
       int priority = main.getPriority();//获取线程的优先级
       System.out.println("优先级:"+priority);

       boolean alive = main.isAlive();//线程是否还活着
       boolean daemon = main.isDaemon();//线程是否为守护线程
       boolean interrupted = main.isInterrupted();//线程是否被中断了
       System.out.println("是否活着:"+alive);
       System.out.println("是否为守护线程:"+daemon);
       System.out.println("是否被中断:"+interrupted);
  }
}

PriorityDemo

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

/**
* @author 老安
* @data 2022/6/23 19:46
* 线程的优先级
* 线程有10个优先级,分别对应整数1-10,其中1是最低的优先级,10为最高级的优先级,5是默认值
* 线程start执行后,会被纳入到线程调度器中,统一管理,此时线程只能被动等待被分配时间片,线程不能主动的索取时间片,调度器会尽可能的均匀的分配时间片
* 但是优先级在多核心的前提下,是不作数了
*/
public class PriorityDemo {
   public static void main(String[] args) {
       Thread min = new Thread(){
           public void run() {
               for (int i = 0; i < 10000; i++) {
                   System.out.println("皇帝来找min啦");
              }
          }
      };
       Thread norm = new Thread(){
           public void run() {
               for (int i = 0; i < 10000; i++) {
                   System.out.println("皇帝来找norm啦");
              }
          }
      };
       Thread max = new Thread(){
           public void run() {
               for (int i = 0; i < 10000; i++) {
                   System.out.println("皇帝来找max啦");
              }
          }
      };
       //min.setPriority(1);
       min.setPriority(Thread.MIN_PRIORITY);
       max.setPriority(10);
       min.start();
       norm.start();
       max.start();
  }
}

进程

什么是进程

进程是操作系统中运行得到一个任务(一个应用程序运行在一个进程中).

进程(process)是一块包含了某些资源的内存区域.操作同利用进程把它的工作划分为一些功能单元.

进程中所包含的一个或者多个执行单元称为线程(thread).进程拥有一个私有的虚拟地址空间,该空间仅能被他所包含的线程访问

线程只能归属于一个进程并且他只能访问该进程所拥有的资源.当操作系统创建一个进程后,该进程会自动申请一个主线程或者首要线程的线程

线程状态

概述
image

  1. 新建状态(New):当一个线程对象被创建后,线程就处于新建状态。在新建状态中的线程对象从严格意义上看还只是一个普通的对象,还不是一个独立的线程,不会被线程调度程序调度。新建状态是线程生命周期的第一个状态。

例如: Thread t = new MyThread();

  1. 就绪状态(Runnable):处于新建状态中的线程被调用start()方法就会进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,但并不是说执行了start()方法线程就会立即执行;

另外,在等待/阻塞状态中的线程,被解除等待和阻塞后将不直接进入运行状态,而是首先进入就绪状态

  1. 运行状态(Running):处于就绪状态中的线程一旦被系统选中,使线程获取了 CPU 时间,就会进入运行状态。
    线程在运行状态下随时都可能被调度程序调度回就绪状态。在运行状态下还可以让线程进入到等待/阻塞状态。
    在通常的单核CPU中,在同一时刻只有一个线程处于运行状态。在多核的CPU中,就可能两个线程或更多的线程同时处于运行状态,这也是多核CPU运行速度快的原因。

注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,必须先处于就绪状态中;

  1. 阻塞状态(Blocked):根据阻塞产生的原因不同,阻塞状态又可以分为三种
    a)等待阻塞:运行状态中的线程执行wait()方法,使当前线程进入到等待阻塞状态;
    b)锁阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),线程会进入同步阻塞状态;
    c)其他阻塞:通过调用线程的sleep(),suspend(), join(), 或发出了I/O请求时等,线程会进入到阻塞状态。当sleep()睡眠结束、调用resume(),?join()等待的线程终止或者超时、或I/O处理完毕时,线程重新转入就绪状态。

  2. 死亡状态(Dead):当线程中的run方法执行结束后,或者程序发生异常终止运行后,线程会进入死亡状态。处于死亡状态的线程不能再使用 start 方法启动线程。

SleepDemo

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

import javafx.scene.transform.Scale;

import java.util.Scanner;

/**
* @author 老安
* @data 2022/6/23 20:50
* 线程提供了一个静态方法:
* static void sleep(long ms)
* 当一个线程调用sleep后,会进入阻塞状态指定的毫秒,超时后,会进入到Runnable状态,再次等待获取CPU的使用权,才能继续运行
*/
public class SleepDemo {
   public static void main(String[] args) {
       System.out.println("程序开始了!");
       /*
        * 倒计时程序
        * 程序启动后,输入一个数字,然后从该数字开始每秒递减,
        * 到0的时候输出时间到!
        * */
       Scanner scanner = new Scanner(System.in);
       System.out.println("请输入一个数字:");
       try {
           for (int num = scanner.nextInt(); num > 0; num--) {
               System.out.println(num);
               Thread.sleep(1000);
          }
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       System.out.println("程序结束了!");
  }
}

**SleepDemo2**
```JAVA
package thread;

/**
* @author 老安
* @data 2022/6/23 21:09
* sleep方法要求必须处理中断异常
* 当一个线程调用sleep方法处于睡眠阻塞的过程中,此时该线程的interrupt()方法
* 被调用时,会被立即中断睡眠阻塞,并抛出该异常
*/
public class SleepDemo2 {
   public static void main(String[] args) {
       Thread lin = new Thread(){
           @Override
           public void run() {
               System.out.println("林:刚美完容~睡一会吧~~");
               try {
                   Thread.sleep(500000000);
                   System.out.println("林:睡醒了!");
              } catch (InterruptedException e) {
                   System.out.println("林:干嘛呢!干嘛呢!都破了相了!");
              }

          }
      };
       Thread huang = new Thread(){
           @Override
           public void run() {
               System.out.println("黄:大锤80,小锤40,开始砸墙!");
               for (int i = 0; i < 5; i++) {
                   System.out.println("黄:80!");
                   try {
                       Thread.sleep(1000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
               System.out.println("咣当!");
               System.out.println("黄:大哥!搞定!");
               lin.interrupt();//当huang线程执行到这里时,会让lin线程中断睡眠阻塞
          }
      };
       lin.start();
       huang.start();
  }
}

DaemonThreadDemo

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

/**
* @author 老安
* @data 2022/6/23 21:25
* 守护线程
* java中将线程分为两类:用户线程和守护线程.也称为前台线程和后台线程.
* 守护线程和用户线程创建和使用上并无区别,但是最主要的区别:
* 当进程结束(一个java进程中所有的用户线程结束),
* 此时会将所有的守护线程全部杀死
* 守护线程是通过普通的用户线程通过调用setDaemon(true)设置而转变的
*/
public class DaemonThreadDemo {
   public static void main(String[] args) {
       Thread rose = new Thread(){//用户线程
           @Override
           public void run() {
               for (int i = 0; i < 5; i++) {
                   System.out.println("rose:let me go!");
                   try {
                       Thread.sleep(1000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
               System.out.println("rose:aaaaaaaaaa~~~");
               System.out.println("扑通!!");
          }
      };
       Thread jack = new Thread(){
           @Override
           public void run() {
               while (true){
                   System.out.println("jack:you jump! i jump!");
                   try {
                       Thread.sleep(1000);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
          }
      };
       rose.start();
       jack.setDaemon(true);//将jack从用户线程设置为守护线程
       jack.start();
       while (true);
  }
}

线程的同步和异步

JoinDemo

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

/**
* @author 老安
* @data 2022/6/23 21:48
* 异步:线程各自执行各自的(多线程是并发运行的,本身就是异步的)
* 同步:多个线程执行是存在了先后顺序的
* 线程提供了一个方法:join,可以协调线程的同步运行
*/
public class JoinDemo {
   static boolean isFinish = false;//表示图片是否下载完毕
   public static void main(String[] args) {
       Thread download = new Thread() {
           @Override
           public void run() {
               System.out.println("down:开始下载图片...");
               for (int i = 0; i <= 100; i++) {
                   System.out.println("down:" + i + "%");
                   try {
                       Thread.sleep(50);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
               System.out.println("down:图片下载完毕!!!");
               isFinish = true; //true表示图片下载完毕
          }
      };
       Thread show = new Thread(){
           @Override
           public void run() {
               System.out.println("show:开始显示文字...");
               try {
                   Thread.sleep(2000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               System.out.println("show:显示文字完毕!!!");
               //将show线程阻塞,直到download线程执行完毕(图片下载完毕)
               try {
                   /*
                    * 当show线程调用download.join()后便会进入阻塞状态.
                    * 直到download线程执行完毕才会解除阻塞
                    * join 参加
                    */
                   download.join();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               System.out.println("show:开始显示图片...");
               if (!isFinish){//if(true){} !isFinish=true isFinish=false
                   throw new RuntimeException("图片加载失败!!!");
              }
               System.out.println("show:图片显示完毕!!!");
          }
      };
       download.start();
       show.start();
  }
}

同步锁

同步方法

同步方法概述

除了可以将需要的代码设置成同步代码块以外,也可以使用synchronized关键字将一个方法修饰成同步方法,它能实现和同步代码块同样的功能。

具体语法格式如下:

1
2
3
权限修饰符 synchronized 返回值类型/void 方法名([参数1,...]){
需要同步的代码;
}

被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其他线程才有机会执行该方法。

需要注意的是,同步方法的锁是当前调用该方法的对象,也就是this指向的对象。

同步方法的使用

下面使用同步方法解决上面的多线程售票程序中的线程安全问题

需要注意的是:

  1. 将有可能发生线程安全问题的方法使用synchronized修饰,同一时间只允许一个线程进入同步方法中

  2. synchronized方法的锁对象是当前调用该方法的对象,也就是this指向的对象。

如果当前方法是非静态方法,this表示的是调用当前方法的对象

如果当前方法的静态方法,this表示的是当前类。

示例1: SyncDemo1

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

/**
* 线程并发安全问题
* 当多个线程并发操作同一个临界资源,由于线程切换实际不确定,导致操作顺序出现混乱而引起的各种逻辑错误
* 临界资源:操作该资源的完整过程同一时刻只能由单线程执行
*/
public class SyncDemo1 {
   public static void main(String[] args) {
       Table table = new Table();
       Thread t1 = new Thread("小红"){
           @Override
           public void run() {
               while (true){
                   int bean = table.getBean();
                   Thread.yield();
                   System.out.println(getName()+"抢了一颗豆子,还剩:"+bean);
              }
          }
      };
       Thread t2 = new Thread("小蓝"){
           @Override
           public void run() {
               while (true){
                   int bean = table.getBean();
                   Thread.yield();
                   System.out.println(getName()+"抢了一颗豆子,还剩:"+bean);
              }
          }
      };
       t1.start();
       t2.start();
  }
}
class Table{
   private int beans = 20;//桌子上有20个豆子

   /*
    * 当一个方法使用关键字 synchronized后,该方法成为同步方法:
    * 多个线程不能同时执行这个方法,变相的排队了
    * 将多线程并发操作改为同步有先后顺序的排队执行可以有效的解决并发安全问题
    */
   public synchronized int getBean(){//每次调用该方法,可以取桌子上的一颗豆子
       if (beans <= 0){
           throw new RuntimeException("没有豆子了!!!");
      }
       Thread.yield();//让出时间片
       return beans--;
  }
}

同步代码块

同步代码块概述

同步是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。

Java为线程的同步操作提供了synchronized关键字,使用该关键字修饰的代码块被称为同步代码块。

其语法格式如下:

1
2
3
synchronized( 同步对象 ){
需要同步的代码;
}

注意: 在使用同步代码块时必须指定一个需要同步的对象,也称为锁对象,这里的锁对象可以是任意对象。但多个线程必须使用同一个锁对象。

同步代码块的使用

下面使用同步代码块解决上面的多线程售票程序中的线程安全问题

需要注意的是:

  1. 将有可能发生线程安全问题的代码包含在同步代码块中,同一时间只允许一个线程进入同步代码块中

  2. synchronized代码块中的锁对象可以是任意对象,但必须只能是一个锁。

若使用this作为锁对象,需保证多个线程执行时,this指向的是同一个对象

**示例1: **

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

/**
* 同步块的应用
* 有效的缩小同步范围可以在保证并发安全问题的前提下尽可能的提高并发效率
* 语法:
* synchronized(同步监视器对象){
*     需要多个线程同步执行的代码片段
* }
*/
public class SyncDemo2 {
   public static void main(String[] args) {
       Shop shop1 = new Shop();
       Shop shop2 = new Shop();
       Thread t1 = new Thread("康荐文"){
           public void run() {
                shop1.buy();
          }
      };
       Thread t2 = new Thread("薛亚杰"){
           public void run() {
               shop2.buy();
          }
      };
       t1.start();
       t2.start();
  }
}
class Shop{
   //public synchronized void buy(){//买一件衣服,一般需要10s
   public void buy(){
       Thread t = Thread.currentThread();//获取运行buy方法的线程
       try {
           System.out.println(t.getName()+":正在挑衣服...");
           Thread.sleep(5000);
           /*
            * 同步块在使用时,需要在"()"中指定同步监视器对象,该对象可以是任何引用类型的实例.
            * 只要保证多个需要同步执行该代码块的线程看到的这个对象是"同一个"即可.
            * 实际开发中课结合具体情况选取
            */
           synchronized (this){
               System.out.println(t.getName()+":正在试衣服...");
               Thread.sleep(5000);
          }
           System.out.println(t.getName()+":正在结账离开...");
      } catch (InterruptedException e) {
      }
  }
}

静态同步方法

示例1: SyncDemo3

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

/**
* 静态方法锁
* 如果我们在静态方法上使用synchronized,那么该方法一定是具有同步效果
*/
public class SyncDemo3 {
   public static void main(String[] args) {
       Boo b1 = new Boo();
       Boo b2 = new Boo();
       Thread t1 = new Thread(){
           @Override
           public void run() {
               b1.doSome();
          }
      };
       Thread t2 = new Thread(){
           @Override
           public void run() {
               b2.doSome();
          }
      };
       t1.start();
       t2.start();
  }
}
class Boo {
   /**
    * 静态方法锁对象指的是当前类的类对象
    * 一个类的Class的实例全局唯一
    */
   //public synchronized static void doSome(){
   public static void doSome(){
       synchronized (Boo.class){
           Thread t = Thread.currentThread();
           System.out.println(t.getName()+":正在执行doSome方法...");
           try {
               Thread.sleep(5000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           System.out.println(t.getName()+":执行doSome方法完毕!!!");
      }
  }
}

互斥锁

示例:SyncDemo4

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

/**
* 互斥锁
* 当使用多个synchronized锁定多个代码片段,并且指定的锁对象相同时,那么这些代码片段之间就是互斥的,多个线程不能同时访问他们
*/
public class SyncDemo4 {
   public static void main(String[] args) {
       Foo foo = new Foo();
       Thread t1 = new Thread(){
           @Override
           public void run() {
               foo.methodA();
          }
      };
       Thread t2 = new Thread(){
           @Override
           public void run() {
               foo.methodB();
          }
      };
       t1.start();
       t2.start();
  }
}
class Foo {
   public synchronized void methodA(){
       Thread t = Thread.currentThread();
       System.out.println(t.getName()+":正在执行A方法...");
       try {
           Thread.sleep(5000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(t.getName()+":执行A方法完毕...");
  }
   public synchronized void methodB(){
       Thread t = Thread.currentThread();
       System.out.println(t.getName()+":正在执行B方法...");
       try {
           Thread.sleep(5000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(t.getName()+":执行B方法完毕...");
  }
}

死锁

示例:DeadLockDemo

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

/**
* 死锁
* 当多个线程都各自持有一个锁的同时,还等待对方先释放锁,就会形成一种僵持状态,这个就是死锁
*/
public class DeadLockDemo {
   static Object chopsticks = new Object();//筷子
   static Object spoon = new Object();//勺 调羹
   public static void main(String[] args) {
       Thread bf = new Thread() {
           @Override
           public void run() {
               try {//crtl+alt+L快速整理代码格式
                   System.out.println("北方人准备吃饭...");
                   synchronized (chopsticks) {//抢筷子,不释放筷子,别的线程拿不到筷子
                       System.out.println("北方人拿筷子,开始吃饭");
                       Thread.sleep(5000);
                       System.out.println("北方人吃完了饭,去拿勺");
                       synchronized (spoon){//抢勺
                           System.out.println("北方人拿勺,开始喝汤");
                           Thread.sleep(5000);
                           System.out.println("北方人喝完了汤");
                      }
                       System.out.println("北方人放下勺");
                  }
                   System.out.println("北方人放下筷子,吃饭完毕!");
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      };
       Thread nf = new Thread() {
           @Override
           public void run() {
               try {//crtl+alt+L快速整理代码格式
                   System.out.println("南方人准备吃饭...");
                   synchronized (spoon) {//抢勺
                       System.out.println("南方人拿勺,开始喝汤");
                       Thread.sleep(5000);
                       System.out.println("南方人喝完了汤,去拿筷子");
                       synchronized (chopsticks){//抢筷子
                           System.out.println("南方人拿起筷子,开始吃饭");
                           Thread.sleep(5000);
                           System.out.println("南方人吃完了饭");
                      }
                       System.out.println("南方人放下筷子");
                  }
                   System.out.println("南方人放下勺,吃饭完毕!");
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      };
       nf.start();
       bf.start();
  }
}

**DeadLockDemo1**
```JAVA
package thread;

/**
* 死锁
* 当多个线程都各自持有一个锁的同时,还等待对方先释放锁,就会形成一种僵持状态,这个就是死锁
*/
public class DeadLockDemo1 {
   static Object chopsticks = new Object();//筷子
   static Object spoon = new Object();//勺 调羹

   public static void main(String[] args) {
       Thread bf = new Thread() {
           @Override
           public void run() {
               try {//crtl+alt+L快速整理代码格式
                   System.out.println("北方人准备吃饭...");
                   synchronized (chopsticks) {//抢筷子,不释放筷子,别的线程拿不到筷子
                       System.out.println("北方人拿筷子,开始吃饭");
                       Thread.sleep(5000);
                       System.out.println("北方人吃完了饭");
                  }
                   System.out.println("北方人放下筷子,去拿勺");
                   synchronized (spoon) {//抢勺
                       System.out.println("北方人拿勺,开始喝汤");
                       Thread.sleep(5000);
                       System.out.println("北方人喝完了汤");
                  }
                   System.out.println("北方人放下勺");
                   System.out.println("吃饭完毕!");
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      };
       Thread nf = new Thread() {
           @Override
           public void run() {
               try {//crtl+alt+L快速整理代码格式
                   System.out.println("南方人准备吃饭...");
                   synchronized (spoon) {//抢勺
                       System.out.println("南方人拿勺,开始喝汤");
                       Thread.sleep(5000);
                       System.out.println("南方人喝完了汤");
                  }
                   System.out.println("南方人放下勺,去拿筷子");
                   synchronized (chopsticks) {//抢筷子
                       System.out.println("南方人拿起筷子,开始吃饭");
                       Thread.sleep(5000);
                       System.out.println("南方人吃完了饭");
                  }
                   System.out.println("南方人放下筷子");

                   System.out.println("吃饭完毕!");
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      };
       nf.start();
       bf.start();
  }
}