多线程-Multithreading


多线程-Multithreading

  • 多线程的创建
    • 方式一:继承于Thread类
      • 创建一个继承于Thread类的子类
      • 重写Thread类的run()
      • 创建Thread类的子类的对象
      • 通过此对象调用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
package Intermediate.Thread;

/**
* 遍历100以内的所有偶数
*/
//1.创建一个继承于Thread的子类
class MyThread extends Thread{
//2.重写Thread类的run()方法

@Override
public void run() {
for (int i = 0; i < 100; i++){
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}

public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类对象
MyThread t1 = new MyThread();
//4.通过此代码调用start():启动当前线程-调用当前线程的run()方法
t1.start();
/**
* 问题一:我们能不能通过直接调用main()方法启动线程?
* 不可以
* 问题二:在启动一个线程遍历100以内的偶数
* 不可以让已经start的线程去执行,报错:IllegalThreadStateException()
* 可以再创建新对象调用
*/
MyThread t2 = new MyThread();
t2.start();

for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName()+" ********************************");
}
}
}
}

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
package Intermediate.Thread.exer;

/**
* 练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*/

class MyThread extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName()+" 偶数"+i);
}
}
}
}

class MyThread2 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
if (i % 2 ==1){
System.out.println(Thread.currentThread().getName()+" 奇数"+i);
}
}
}
}

public class ThreadDemo {
public static void main(String[] args) {
// MyThread t1 = new MyThread();
// MyThread2 t2 = new MyThread2();
//
// t1.start();
// t2.start();
//创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName()+" 偶数"+i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName()+" 偶数"+i);
}
}
}
}.start();
}
}

Thread类的有关基本方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- void start()//启动线程,并执行对象的run()方法

- run()//线程在被调度时执行的操作,通常需要重写Thread类中的此方法,将创建的线程要执行的操作的声明在此方法中

- String getName()//返回线程的名称

- void setName(String name)//设置该线程的名称

- static Thread currentThread()//返回当前线程。在Thread子类中是this,通常用于主线程和Runnable实现类
- static void yield()//线程让步,暂停当前正在执行的线程,把执行的机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
- join()//当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止,低优先级的线程也可以获得执行
- static void sleep(long millis)//(指定时间:毫秒)令当前活动线程在指定时间段内放弃对cpu控制使其他线程有机会被执行,时间到后重新派对,抛出InterruptedException异常
- stop()//强制线程生命期结束,不推荐使用
- boolean isAlive()//返回boolean,判断线程是否还活着
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
package Intermediate.Thread;

/**
* 测试Thread中的常用方法
*1.start():启动当前线程,调用当前线程的run()
*2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
*3.currentThread():静态方法,返回执行当前代码的线程
*4.getName():获取当前线程的名字
*5.setName():设置当前线程的名字
*6.yield():释放当前cpu的执行权,有可能在下一刻又分配到此线程
*7.join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态等待cpu重新分配资源
*8.stop():已过时。当执行此方法时,强制结束当前线程
*9.sleep(long millis):让当前线程“睡眠”指定的millis毫秒。在指定的millis时间内当前线程是阻塞状态
*10.isAlive():判断当前线程是否存活
*/

class MyThreadMethodTest extends Thread{
public void run(){
//分线程
for (int i = 0; i <= 100; i++){
if (i % 2 == 0){
try {
sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
if (i % 20 == 0){
this.yield();
}
}
}

//通过构造方法命名线程
public MyThreadMethodTest(String name){
super(name);
}

}
public class ThreadMethodTest {
public static void main(String[] args) {

MyThreadMethodTest myThreadMethodTest = new MyThreadMethodTest("Thread:1");

//通过setName方法命名线程
//myThreadMethodTest.setName("线程一");

myThreadMethodTest.start();

//主线程,给主线程命名
Thread.currentThread().setName("主线程");

for (int i = 0; i <= 100; i++){
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
if (i == 20){
try {
myThreadMethodTest.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

System.out.println(myThreadMethodTest.isAlive());

}
}

线程的优先级

  • 线程的优先等级
    • MAX_PRIORITY:10
    • MIN_PRIORITY:1
    • NORM_PRIORITY:5
  • 涉及的方法
1
2
3
getPriority()//返回线程优先值

setPriority(int newPriority)//改变线程的优先级
  • 说明

    • 线程创建时继承父线程的优先级
    • 低优先级只是获得调度的概率低,并非一定是在高优先级线程只有才被调用
  • 高优先级的线程要抢占低优先级线程cpu的执行权。优先级比较高的是高概率被cpu执行到,但不是优先级高的就一定先被执行完毕后再执行低优先级

继承Thread的方式

  • 例子:创建三个窗口卖票,总票数为100张
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 Intermediate.Thread;

/**
* 例子:创建三个窗口卖票,总票数为100张
*/

class Window extends Thread{
private static int ticket = 100;

@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(getName()+":卖了一张票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}

}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();

w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");

w1.start();
w2.start();
w3.start();



}
}

  • 存在线程安全问题待解决

创建多线程的方式二:实现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
package Intermediate.Thread;

/**
* 创建多线程的方式二:实现Runnable接口
* 1.创建一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽线方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
*/
//1.创建一个实现了Runnable接口的类
class MyThreadRunnable implements Runnable{
//2.实现类去实现Runnable中的抽线方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0 ){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadExtendsSecond {
public static void main(String[] args) {
//3.创建实现类的对象
MyThreadRunnable myThreadRunnable = new MyThreadRunnable();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread thread = new Thread(myThreadRunnable);
thread.setName("线程一");
//5.通过Thread类的对象调用start(),启动线程,调用当前线程的run()-->调用了Runnable类型的target的run()
thread.start();

//在启动一个线程,遍历100以内的偶数
Thread thread2 = new Thread(myThreadRunnable);
thread2.setName("线程二");
thread2.start();
}
}

  • 用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
package Intermediate.Thread;
/**
* 例子:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式
*/

class MyThreadWindowRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}

public class WindowTest2 {
public static void main(String[] args) {
MyThreadWindowRunnable myThreadWindowRunnable = new MyThreadWindowRunnable();

Thread t1 = new Thread(myThreadWindowRunnable);
Thread t2 = new Thread(myThreadWindowRunnable);
Thread t3 = new Thread(myThreadWindowRunnable);

t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");

t1.start();
t2.start();
t3.start();

}
}

  • 存在线程安全问题待解决

比较创建线程的两种方式

  • 开发中优先选择实现Runnable接口的方式

  • 原因

    • 实现的方式没有类的单继承的局限性
    • 实现的方式更适合来处理多个线程有共享数据的情况
  • 联系:

1
public class Thread implements Runnable
  • 相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中

线程的分类

  • 守护线程
  • 非守护线程(用户线程)

线程的生命周期

image-20210908173919165

说明:

  1. 声明周期关注两个概念:状态、相应的方法

  2. 关注:状态a–>状态b:那些方法执行了(回调方法)

    ​ 某个方法主动调用:状态a–>状态b

  3. 阻塞:临时状态,不可以作为最终状态

  4. 最终状态:线程死亡

线程的同步

  • 问题的提出
    • 多个线程的执行的不确定性引起结果的不稳定
    • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据
  • 问题:卖票过程中,出现了重票、错票–>出现了现成的安全问题

  • 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票

  • 如何解决:当一个线程a在操作共享数据的时候,其他线程不能参与进来。直到线程a操作完之后其他线程才能参与进来,这种情况即使线程a出现了阻塞,也不能被改变

  • 在Java中,我们通过同步机制来解决线程的安全问题

    • 最初的方式:

      • 方式一:同步代码块

        • 说明:

          • 操作共享数据的代码,即为需要被同步的代码。不能包含代码多了,也不能包含代码少
          • 共享数据:多个线程共同操作的变量。比如:ticket
          • 同步监视器:俗称“锁”,任何一个类的对象都可以充当锁,要求多个线程必须要共用同一把锁.
          • 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
          • 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器
          • ```java
            synchronized(同步监视器){
                      //需要被同步的代码
                 }
            

            class MyThreadWindowRunnable implements Runnable{
            private   int ticket = 100;
            Object obj = new Object();
            @Override
            public void run() {
            
                while (true){
                    //synchronized(this)
                    synchronized(obj){
                        if (ticket>0){
                            System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket);
                            ticket--;
                        }else {
                            break;
                        }
                    }
                }
            }
            
            }
            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

            * 方式二:同步方式

            * 使用同步代码块解决继承Thread类的方式的线程安全问题

            * 同步监视器可以使用当前类充当,如:windows.class

            * ```java
            class Window3 extends Thread{
            private static int ticket = 100;
            private static Object obj = new Object();
            @Override
            public void run() {
            while (true){
            //synchronized(obj){
            synchronized(WindowTest3.class){//Class clazz = Window.class();
            if (ticket>0){
            System.out.println(getName()+":卖了一张票,票号为:"+ticket);
            ticket--;
            }else {
            break;
            }
            }

            }
            }

            }
            public class WindowTest3 {
            public static void main(String[] args) {
            Window3 w1 = new Window3();
            Window3 w2 = new Window3();
            Window3 w3 = new Window3();

            w1.setName("窗口一");
            w2.setName("窗口二");
            w3.setName("窗口三");

            w1.start();
            w2.start();
            w3.start();
            }
            }
        • ```java
          package Intermediate.Thread;

          /**

          • 使用同步代码块解决继承Thread类的方式的线程安全问题

          • /
            class Window5 extends Thread{
            private static int ticket = 100;
            private static Object obj = new Object();
            @Override
            public void run() {

              while (true){
                  show();
            
              }
            

            }
            public static synchronized void show(){//同步监视器:window5.class

              if (ticket>0){
                  System.out.println(Thread.currentThread().getName()+":卖了一张票,票号为:"+ticket);
                  ticket--;
              }
            

            }

          }

          public class WindowTest5 {

          public static void main(String[] args) {
              Window5 w1 = new Window5();
              Window5 w2 = new Window5();
              Window5 w3 = new Window5();
          
              w1.setName("窗口一");
              w2.setName("窗口二");
              w3.setName("窗口三");
          
              w1.start();
              w2.start();
              w3.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

          * 使用同步方式解决实现Runnable接口的线程安全问题

          * ```java
          class MyThreadWindowRunnable implements Runnable{
          private int ticket = 100;

          @Override
          public void run() {

          while (true){
          synchronized(this){
          if (ticket>0){
          System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket);
          ticket--;
          }else {
          break;
          }
          }
          }
          }
          }

          public class WindowTest2 {
          public static void main(String[] args) {
          MyThreadWindowRunnable myThreadWindowRunnable = new MyThreadWindowRunnable();

          Thread t1 = new Thread(myThreadWindowRunnable);
          Thread t2 = new Thread(myThreadWindowRunnable);
          Thread t3 = new Thread(myThreadWindowRunnable);

          t1.setName("窗口一");
          t2.setName("窗口二");
          t3.setName("窗口三");

          t1.start();
          t2.start();
          t3.start();

          }
          }

        • ```java
          class MyThreadWindowTest4 implements Runnable{

          private  int ticket = 100;
          
          @Override
          public void run() {
          
              show();
          }
          public synchronized void show(){//同步监视器:this
              while (true){
                  if (ticket>0){
                      System.out.println(Thread.currentThread().getName()+":买了一张票,票号为:"+ticket);
                      ticket--;
                  }else {
                      break;
                  }
              }
          }
          

          }

          public class WindowTest4 {

          public static void main(String[] args) {
              MyThreadWindowTest4 myThreadWindowTest4 = new MyThreadWindowTest4();
          
              Thread t1 = new Thread(myThreadWindowTest4);
              Thread t2 = new Thread(myThreadWindowTest4);
              Thread t3 = new Thread(myThreadWindowTest4);
          
              t1.setName("窗口一");
              t2.setName("窗口二");
              t3.setName("窗口三");
          
              t1.start();
              t2.start();
              t3.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

          * 好处:同步的方式解决了线程的安全问题

          * 局限性:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

          * 关于同步方法的总结:

          1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
          2. 非静态的同步方法,同步监视器是:this
          3. 静态的同步方法,同步监视器是:当前类本身



          ## 使用同步机制将单例模式中的懒汉式改写为线程安全的

          ```java
          public class BankTest {

          }
          class Bank{

          private Bank(){

          }
          private static Bank instance = null;
          public static synchronized Bank getBank(){
          if (instance == null){
          instance = new Bank();
          return instance;
          }
          return instance;
          }
          }

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 BankTest {

}
class Bank{

private Bank(){

}
private static Bank instance = null;
public static Bank getBank(){
----------------------------------------------------------
//效率较差,方式一
synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
return instance;
}
return instance;
}
----------------------------------------------------------
//效率稍高,方拾二
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
return instance;
}
}
return instance;
---------------------------------------------------------
}
}

线程死锁的问题

  • 死锁
    • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对象放弃自己需要的同步资源,就形成了线程的死锁
    • 出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
  • 解决方法
    • 专门的算法、原则
    • 尽量减少同步资源的定义
    • 尽量避免嵌套同步
  • 死锁的演示
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
public class ThreadDeadLock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();

//线程a先占用s1睡眠过程中s2被另一个线程b占用,线程a睡眠过后想获取s2但是s2已经被线程b占用,想要线程b结束就需要线程b获取s1,但是s1已经被线程a占用,所以两线程僵持不下就产生了死锁
new Thread(){
@Override
public void run() {
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}

}
}.start();

new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}

LOCK(锁)

  • 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized想用的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
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

/**
* 解决线程安全问题的方式三:Lock锁 ---JDK5.0新增
*
* 1.面试题:synchronized 与 Lock 的异同?
* 相同:都可以解决线程的安全问题
* 不同:Synchronized机制在执行完相应的代码逻辑之后,自动的释放同步监视器
* Lock需要手动的启动同步(lock()),同时也需要手动的结束同步(unlock())
*
* 优先使用顺序:
* Lock -> 同步代码块(已经进入方法体,分配了相应资源) -> 同步方法(在方法体之外)
*
*2.面试题:如何解决线程安全问题?有几种方式
* 三种:Lock,同步代码块,同步方法。
* 两种:Lock,synchronized
*
*/

class WindowLockTest implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用lock()方法
lock.lock();

if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":售票,票号为:"+ticket);
ticket--;
}else {
break;
}
}finally{
//3.调用解锁方法unlock()
lock.unlock();
}
}
}
}

public class LockTest {
public static void main(String[] args) {
WindowLockTest w = new WindowLockTest();

Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);

t1.setName("Window1");
t2.setName("Window2");
t3.setName("Window3");

t1.start();
t2.start();
t3.start();

}
}

线程的通信

  • 涉及到三个方法
    • wait()方法:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    • notify()方法:一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
    • notifyAll()方法:一旦执行此方法,就会唤醒所有被wait的线程
  • 说明:
    • wait()、notify()、notifyAll()三个方法都必须使用在同步代码块或同步方法中
    • wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则会出现IllegalMonitorStateException异常。
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
class Number implements Runnable{
private int number = 1;

@Override
public void run() {
while (true){
//notify()和wait()省略了this.
//this 与 this.notify() 和 this.wait()
//当this改为别的Object时this.notify() 和 this.wait() 报错
synchronized (this) {
notify();
if (number <=100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" : "+number);
number++;

try {
//使得低矮用如下wait()方法的线程进入阻塞状态
//一旦执行wait()就会释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}

public class CommunicationTest {
public static void main(String[] args) {
Number n = new Number();

Thread t1 = new Thread(n);
Thread t2 = new Thread(n);

t1.setName("Thread-1 ");
t2.setName("Thread-2 ");

t1.start();
t2.start();

}
}
  • 面试题:
    • sleep()和wait()的异同?
      • 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
      • 不同点:1.两个方法声明的位置不同:Thread类中声明sleep()方法,Object类中声明wait()
    • 调用的范围不同:sleep()可以在任何需要的场景下调用。wait()只能用在同步代码块或同步方法中
    • 关于是否释放同步监视器的问题:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,而wait()会释放锁
  • 小结
    • 会释放锁的操作:
      • 当前线程的同步方法、同步代码块执行结束
      • 当前线程在同步代码块、同步方法中遇到break、return终止了该代码
      • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
      • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
    • 不会释放锁的操作:
      • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
      • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
        • 应尽量避免使用suspend()和resume()来控制线程

JDK5.0新增线程创建方式

  • 新增方式一:实现Callable接口

    • 与Rannable相比,Callable功能更强大些
      • 相比run()方法,可以有返回值
      • 方法可以抛出异常
      • 支持泛型的返回值
      • 需要借助FutureTask类,比如获取返回结果
      • Future接口
        • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
        • FutureTask是Future接口的唯一的实现类
        • FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
    • 如何理解实现Cabllable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
      • call()可以有返回值
      • call()可以抛出异常,被外面的操作捕获,获取异常的信息
      • Callable是支持泛型的
  • 新增方式二:使用线程池

    • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
    • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具
    • 好处:
      • 提高响应速度(减少了创建新线程的时间)
      • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
      • 便于线程管理
        • corePoolSize:核心池的大小
        • maximumPoolSize:最大线程数
        • keepAliveTime:线程没有任务时最多保持多长时间后会终止
        • ……..
    • 线程池相关API
      • JDK5.0起提供了线程池的相关API:ExecutorService和Executors
      • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
        • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
        • Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
        • void shutdown():关闭连接池
      • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
        • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
        • Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
        • Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
        • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
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
/**
* 创建线程的方式四:使用线程池
*
*
*/

class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName()+": "+i);
}
}
}
}


public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//想设置线程池的属性需要记进行强转
ThreadPoolExecutor service = (ThreadPoolExecutor) executorService;
//设置线程池的属性
// service.setCorePoolSize(15);
// service.setKeepAliveTime();


//2.执行指定线程的操作。需要提供实现Runnable或Callable接口实现类的对象
executorService.execute(new NumberThread());//适合使用与Runnable
executorService.execute(new NumberThread1());//适合使用与Runnable
// executorService.submit();//适合使用与Callable
//3.关闭连接池
executorService.shutdown();
}
}

  • 面试题:创建多线程有几种方式?四种!
    • 继承Thread类
    • 实现Runnable接口
    • 实现Callable接口,可以借助FutureTask类返回结果
    • 线程池

Author: Sans Zhu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Sans Zhu !
  TOC