【java学习笔记】线程

时间:2023-02-26 21:20:33

1.线程的定义

①继承Thread类,将执行的任务逻辑放到run方法中,调用start方法来开启线程

 1 public class ThreadDemo {
 2     public static void main(String[] args) {
 3         TDemo t = new TDemo();
 4         // 开启线程
 5         t.start();
 6         for (int i = 0; i < 20; i++) {
 7             System.out.println("main:" + i);
 8         }
 9     }
10 }
11 
12 class TDemo extends Thread {
13     @Override
14     public void run() {
15         for (int i = 0; i < 9; i++) {
16             System.out.println("Thread:" + i);
17         }
18     }
19 }

②实现Runnable,重写run方法,需要利用Runnable对象来构建一个Thread对象从而启动线程

    由于java是单继承的,因此当一个类已经继承了父类时,便不能继承Thread类。而又希望启用线程,此时实现Runnable接口即可达到目的。

 1 public class RunnableDemo {
 2     public static void main(String[] args) {
 3         RDemo r = new RDemo();
 4         // 通过Runnable对象来构建一个Thread对象
 5         Thread t = new Thread(r);
 6         t.start();
 7         for (int i = 0; i < 20; i++) {
 8             System.out.println("main:" + i);
 9         }
10     }
11 }
12 
13 class RDemo implements Runnable {
14     @Override
15     public void run() {
16         for (int i = 0; i < 10; i++) {
17             System.out.println("Thread:" + i);
18         }
19     }
20 }

③实现Callable<T>,重写call方法

 1 import java.util.concurrent.Callable;
 2 import java.util.concurrent.ExecutionException;
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 import java.util.concurrent.Future;
 6 
 7 public class CallableDemo {
 8     public static void main(String[] args) throws InterruptedException, ExecutionException {
 9         ExecutorService es = Executors.newCachedThreadPool();
10         Future<String> f = es.submit(new CDemo());
11         System.out.println(f.get());
12     }
13 }
14 
15 class CDemo implements Callable<String> {
16     @Override
17     public String call() throws Exception {
18         return "hahah~~~";
19     }
20 }

 

2.线程的状态

【java学习笔记】线程

    创建:新创建了一个线程对象。

    就绪:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的执行权。

    运行:就绪状态的线程获取了CPU执行权,执行程序代码。

    阻塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

    死亡:线程执行完它的任务时。

 

3.常见的线程方法

    ① Thread(String name)        初始化线程的名字

    ② getName()                        返回线程的名字

    ③ setName(String name)     设置线程对象名

    ④ sleep()                              线程睡眠指定的毫秒数。

    ⑤ getPriority()                      返回当前线程对象的优先级   默认线程的优先级是5

    ⑥ setPriority(int newPriority)     设置线程的优先级。(最大的优先级是10,最小的1,默认是5)虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现。

    ⑦ currentThread()               返回CPU正在执行的线程的对象。

 

4.多线程的并发安全问题

    线程的执行不存在先后,相互抢占执行,抢占并不是只发生在线程执行的开始,而是发生在线程执行的每一步过程中。由于多个线程并发导致出现了一些不符合常理的数据的现象,即线程安全问题。

4.1出现线程安全的根本原因

        ①存在两个或者两个以上的线程对象共享同一个资源
        ②多线程操作共享资源的代码有多句

4.2线程安全问题的解决方案
1.可以使用同步代码块去解决。

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

     注意事项:
            ①锁对象可以是任意一个对象
            ②一个线程在同步块中sleep,并不会释放锁对象
            ③如果不存在线程安全问题,千万不要使用同步代码块,因为会降低效率
            ④锁对象必须是多线程共享的一个资源,否则锁不住


2.同步函数 就是使用synchronized修饰的方法


     注意事项:
            ①如果是一个非静态的同步函数,锁对象是this;如果是静态的同步函数,锁对象是当前函数所属的类的字节码文件(class)
            ②同步函数的锁对象是固定的,不能由开发者来指定

推荐使用同步代码块
    ①同步代码块的锁对象可以由开发者指定,方便控制。而同步方法是固定的。
    ②同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步

 

例:模拟取款,

【java学习笔记】线程【java学习笔记】线程
 1 public class Bank {
 2     public static void main(String[] args) {
 3         //创建一个账户
 4         Account account = new Account("10086", 1000);
 5         //模拟两个线程对同一个账户取钱
 6         new DrawThread("张三",account,800).start();
 7         new DrawThread("李四", account, 900).start();
 8     }
 9 }
10 
11 class DrawThread extends Thread{
12     //模拟用户账户
13     private Account account;
14     //当前取钱线程所希望取得钱数
15     private double drawMoney;
16 
17     public DrawThread(String name, Account account, double drawMoney) {
18         super(name);
19         this.account = account;
20         this.drawMoney = drawMoney;
21     }
22 
23     @Override
24     public void run() {
25         /*
26          * 虽然java程序允许任何对象作为同步监视器,但是同步监视器的目的:
27          * 阻止两个线程对同一个共享资源进行并发访问,因此推荐使用可能被并发访问的共享资源
28          * 当做同步监视器
29          * 
30          * 加锁-修改-释放锁
31          * 
32          * 字节码文件
33          * 静态变量
34          * "锁对象"
35          * 
36          */
37         synchronized (account) {
38                 //synchronized (this) {              //非共享,失败
39         //synchronized (new String("")) {  //非共享,失败
40         //synchronized ("") {              
41             //账户余额大于取钱数目
42             if(account.getBalance() >= drawMoney) {
43                 //吐出钞票
44                 System.out.println(getName() + "取钱成功!吐出钞票:" + drawMoney);
45 
46 //                try {
47 //                    Thread.sleep(5000);
48 //                } catch (Exception e) {
49 //                    e.printStackTrace();
50 //                }
51 
52                 //修改余额
53                 account.setBalance(account.getBalance() - drawMoney);
54                 System.out.println("\t余额为:" + account.getBalance());
55             } else {
56                 System.out.println(getName() + "取钱失败!余额不足!");
57             }    
58         }
59     }
60 }
61 
62 class Account{
63     //封装账户编号、账户余额两个成员变量
64     private String accountNo;
65     private double balance;
66     
67     public Account(String accountNo, double balance) {
68         super();
69         this.accountNo = accountNo;
70         this.balance = balance;
71     }
72 
73     public String getAccountNo() {
74         return accountNo;
75     }
76 
77     public void setAccountNo(String accountNo) {
78         this.accountNo = accountNo;
79     }
80 
81     public double getBalance() {
82         return balance;
83     }
84 
85     public void setBalance(double balance) {
86         this.balance = balance;
87     }
88 }
synchronized代码块Demo
【java学习笔记】线程【java学习笔记】线程
 1 public class Bank2 {
 2     public static void main(String[] args) {        
 3         //创建一个账户
 4         Account2 account = new Account2("10086", 1000);
 5         //模拟两个线程对同一个账户取钱
 6         new DrawThread2("张三",account,800).start();
 7         new DrawThread2("李四", account, 900).start();
 8     }
 9 }
10 
11 class DrawThread2 extends Thread{
12     //模拟用户账户
13     private Account2 account;
14     //当前取钱线程所希望取得钱数
15     private double drawMoney;
16     
17     public DrawThread2(String name, Account2 account, double drawMoney) {
18         super(name);
19         this.account = account;
20         this.drawMoney = drawMoney;
21     }
22 
23     @Override
24     public void run() {
25         account.draw(drawMoney);
26         
27     }
28 }
29 
30 class Account2{
31     //封装账户编号、账户余额两个成员变量
32     private String accountNo;
33     private double balance;
34     
35     public Account2(String accountNo, double balance) {
36         super();
37         this.accountNo = accountNo;
38         this.balance = balance;
39     }
40 
41     public String getAccountNo() {
42         return accountNo;
43     }
44 
45     public void setAccountNo(String accountNo) {
46         this.accountNo = accountNo;
47     }
48 
49     public double getBalance() {
50         return balance;
51     }
52     
53     /*
54      * 锁对象是this
55      * 对于同一个Account账户而言,任意时刻只能有一个线程获得对account对象的锁定
56      */
57     public synchronized void draw(double drawMoney) {
58         //账户余额大于取钱数目
59         if(balance >= drawMoney) {
60             //吐出钞票
61             System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawMoney);
62             
63         try {
64             Thread.sleep(5000);
65         } catch (Exception e) {
66             e.printStackTrace();
67         }
68             
69             //修改余额
70             balance -= drawMoney;
71             System.out.println("\t余额为:" + balance);
72         } else {
73             System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
74         }    
75     }
76 
77 }
synchronized方法Demo

成功的情况:                                             

  【java学习笔记】线程

失败的情况:

  【java学习笔记】线程

 

5.死锁

    由于多个线程之间的锁形成了嵌套导致程序无法继续运行的现象。

    避免死锁:减少线程数量,统一锁对象,减少锁嵌套。

【java学习笔记】线程【java学习笔记】线程
 1 public class DeadLockDemo {
 2     public static void main(String[] args) {
 3         new Thread(new Runnable() { // 创建线程
 4             public void run() {
 5                 synchronized ("资源1") { 
 6                     System.out.println(Thread.currentThread().getName() + ":得不到资源2,就不释放资源1");
 7                     try {
 8                         Thread.sleep(10);
 9                     } catch (InterruptedException e) {
10                         e.printStackTrace();
11                     }
12                     synchronized ("资源2") {
13                         System.out.println(Thread.currentThread().getName() + ":得到资源2,释放资源1");
14                     }
15                 }
16             }
17         }, "线程A").start();
18         new Thread(new Runnable() { // 美国人
19             public void run() {
20                 synchronized ("资源2") { // 美国人拿到了筷子
21                     System.out.println(Thread.currentThread().getName() + ":得不到资源1,就不释放资源2");
22                     try {
23                         Thread.sleep(10);
24                     } catch (InterruptedException e) {
25                         e.printStackTrace();
26                     }
27                     synchronized ("资源1") {
28                         System.out.println(Thread.currentThread().getName() + ":得到资源1,释放资源2");
29                     }
30                 }
31             }
32         }, "线程B").start();
33     }
34 }
DeadLockDemo

死锁:

  【java学习笔记】线程

正常:

  【java学习笔记】线程

 

 6.等待唤醒机制

  wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。

  notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。

  notifyAll:唤醒持有同一监视器中调用wait的所有的线程。

 

①notify

通过等待唤醒机制调节了线程之间的执行顺序

【java学习笔记】线程【java学习笔记】线程
 1 public class WaitNotifyDemo {
 2     public static void main(String[] args) {
 3         Student s = new Student();
 4         s.setName("Tom");
 5         s.setGender('男');
 6         new Thread(new Ask(s)).start();
 7         new Thread(new Change(s)).start();
 8     }
 9 }
10 
11 class Change implements Runnable {
12     private Student s;
13     public Change(Student s) {
14         this.s = s;
15     }
16     
17     @Override
18     public void run() {
19         while (true) {
20             synchronized (s) {
21                 if (s.flag)
22                     try {
23                         s.wait();
24                     } catch (InterruptedException e) {
25                         e.printStackTrace();
26                     }
27 
28                 if (s.getGender() == '男') {
29                     s.setName("Amy");
30                     s.setGender('女');
31                 } else {
32                     s.setName("Tom");
33                     s.setGender('男');
34                 }
35                 s.flag = true;
36                 // 唤醒在等待的线程
37                 s.notify();
38             }
39         }
40     }
41 }
42 
43 class Ask implements Runnable {
44     private Student s;
45     public Ask(Student s) {
46         this.s = s;
47     }
48 
49     @Override
50     public void run() {
51         while (true) {
52             synchronized (s) {
53                 if (!s.flag)
54                     try {
55                         s.wait();
56                     } catch (InterruptedException e) {
57                         e.printStackTrace();
58                     }
59                 System.out.println("我是" + s.getName() + ",我是" + s.getGender());
60                 s.flag = false;
61                 s.notify();
62             }
63         }
64     }
65 }
66 
67 class Student {
68 
69     private String name;
70     private char gender;
71     // 标记位---规定flag为true,执行ask线程,如果flag为false执行change
72     public boolean flag = true;
73 
74     public String getName() {
75         return name;
76     }
77 
78     public void setName(String name) {
79         this.name = name;
80     }
81 
82     public char getGender() {
83         return gender;
84     }
85 
86     public void setGender(char gender) {
87         this.gender = gender;
88     }
89 
90 }
WaitNotifyDemo

 

②notifyAll

线程在等待期间是在这个锁所对应的线程池中等待的。线程池本质上是一个存储线程的队列。

若是用notify,唤醒队列中的第一个线程,执行方式如下:

第一个括号代表就绪的线程,第二个括号代表线程池中的线程。

(a1,a2,c1,c2)() -> a1 running-> (a1,a2,c1,c2)() -> a1 running -> (a2,c1,c2)(a1) -> a2 running -> (c1,c2)(a1,a2) -> c1 running -> (a1,c1,c2)(a2)  -> c1 running -> (a1,c2)(a2,c1) -> c2 running -> (a1)(a2,c1,c2) -> a1 runnig -> (a1,a2)(c1,c2) -> a1 running -> (a2)(c1,c2,a1) -> a2 running -> ()(c1,c2,a1,a2)

最后,所有线程都在线程池中阻塞,发送死锁。此时需要用到notifyAll。

【java学习笔记】线程【java学习笔记】线程
 1 public class WaitNotifyAllDemo {
 2     public static void main(String[] args) {
 3         Student s = new Student();
 4         s.setName("Tom");
 5         s.setGender('男');
 6         new Thread(new Ask2(s)).start();
 7         new Thread(new Ask2(s)).start();
 8         new Thread(new Change2(s)).start();
 9         new Thread(new Change2(s)).start();
10     }
11 }
12 class Change2 implements Runnable {
13     private Student s;
14     public Change2(Student s) {
15         this.s = s;
16     }
17 
18     @Override
19     public void run() {
20         while (true) {
21             synchronized (s) {
22                 while (s.flag)
23                     try {
24                         s.wait();
25                     } catch (InterruptedException e) {
26                         e.printStackTrace();
27                     }
28 
29                 if (s.getGender() == '男') {
30                     s.setName("Amy");
31                     s.setGender('女');
32                 } else {
33                     s.setName("Tom");
34                     s.setGender('男');
35                 }
36                 s.flag = true;
37                 // 唤醒在等待的线程
38                 s.notifyAll();
39             }
40         }
41     }
42 }
43 
44 class Ask2 implements Runnable {
45     private Student s;
46     public Ask2(Student s) {
47         this.s = s;
48     }
49     @Override
50     public void run() {
51 
52         while (true) {
53             synchronized (s) {
54 
55                 while (!s.flag)
56                     try {
57                         s.wait();
58                     } catch (InterruptedException e) {
59                         e.printStackTrace();
60                     }
61 
62                 System.out.println("我是" + s.getName() + ",我是" + s.getGender());
63                 s.flag = false;
64                 s.notifyAll();
65             }
66         }
67     }
68 }
WaitNotifyAllDemo

 

sleep与wait的区别:

    ①sleep在使用的时候需要指定休眠时间,到点自然醒。释放执行权,不释放锁。是一个静态方法,设计在了Thread类上

    ②wait在使用的时候可以指定等待时间,也可以不指定,如果不指定等待时间就需要唤醒。释放执行权,释放锁。是一个非静态方法,设计在了Object类上

   

例:生产消费模型

    一个线程表示生产者Producer,一个线程表示消费者Consumer,商品的总数量不超过1000。

【java学习笔记】线程【java学习笔记】线程
  1 public class ProductAndConsumer {
  2 
  3     public static void main(String[] args) {
  4 
  5         Product p = new Product();
  6 
  7         new Thread(new Producer(p)).start();
  8         new Thread(new Consumer(p)).start();
  9 
 10     }
 11 
 12 }
 13 
 14 class Producer implements Runnable {
 15 
 16     private Product p;
 17 
 18     public Producer(Product p) {
 19         this.p = p;
 20     }
 21 
 22     @Override
 23     public void run() {
 24 
 25         while (true) {
 26 
 27             synchronized (p) {
 28 
 29                 while (p.flag) {
 30                     try {
 31                         p.wait();
 32                     } catch (InterruptedException e) {
 33                         e.printStackTrace();
 34                     }
 35                 }
 36 
 37                 // 计算本次商品所能生产的最大数量
 38                 int max = 1000 - p.getCount();
 39 
 40                 // 计算本次生产的商品数量
 41                 int count = (int) (Math.random() * (max+1));
 42 
 43                 // 本次提供的商品的总数量
 44                 p.setCount(p.getCount() + count);
 45 
 46                 System.out.println("本次生产数量:" + count + ", 本次提供商品数量为:" + p.getCount());
 47 
 48                 p.flag = true;
 49                 p.notify();
 50 
 51             }
 52 
 53         }
 54     }
 55 
 56 }
 57 
 58 class Consumer implements Runnable {
 59 
 60     private Product p;
 61 
 62     public Consumer(Product p) {
 63         this.p = p;
 64     }
 65 
 66     @Override
 67     public void run() {
 68 
 69         while (true) {
 70             synchronized (p) {
 71 
 72                 while (!p.flag) {
 73                     try {
 74                         p.wait();
 75                     } catch (InterruptedException e) {
 76                         e.printStackTrace();
 77                     }
 78                 }
 79 
 80                 // 计算本次消费的数量
 81                 int count = (int) (Math.random() * (p.getCount() + 1));
 82 
 83                 // 计算本次的剩余数量
 84                 p.setCount(p.getCount() - count);
 85 
 86                 System.out.println("本次消费数量:" + count + ", 本次剩余商品数量:" + p.getCount());
 87 
 88                 p.flag = false;
 89                 p.notify();
 90             }
 91         }
 92 
 93     }
 94 
 95 }
 96 
 97 class Product {
 98 
 99     private int count;
100     public boolean flag = false;
101 
102     public int getCount() {
103         return count;
104     }
105 
106     public void setCount(int count) {
107         this.count = count;
108     }
109 
110 }
ProductAndConsumer

 

7.守护线程

    守护别的线程。当被守护的线程结束,守护线程无论执行完成与否都得随之结束。

    一个线程要么是守护线程要么是被守护的线程。守护线程是随着最后一个被守护线程的结束而结束,例如GC。

【java学习笔记】线程【java学习笔记】线程
 1 public class DaemonDemo {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4 
 5         Thread t1 = new Thread(new Soilder(), "小兵1号");
 6         Thread t2 = new Thread(new Soilder(), "小兵2号");
 7         Thread t3 = new Thread(new Soilder(), "小兵3号");
 8         Thread t4 = new Thread(new Soilder(), "小兵4号");
 9 
10         // 设置为守护线程
11         t1.setDaemon(true);
12         t2.setDaemon(true);
13         t3.setDaemon(true);
14         t4.setDaemon(true);
15 
16         t1.start();
17         t2.start();
18         t3.start();
19         t4.start();
20 
21         for (int i = 10; i > 0; i--) {
22             System.out.println("Boss掉了一滴血,剩余" + i);
23             Thread.sleep(150);
24         }
25     }
26 
27 }
28 
29 class Soilder implements Runnable {
30 
31     @Override
32     public void run() {
33 
34         for (int i = 100; i > 0; i--) {
35             System.out.println(Thread.currentThread().getName() + "掉了一滴血,剩余" + i);
36             try {
37                 Thread.sleep(10);
38             } catch (InterruptedException e) {
39                 e.printStackTrace();
40             }
41         }
42 
43     }
44 
45 }
DaemonDemo

 

8.线程的优先级

    线程的优先级分为1-10,理论上数字越大等级越高,这个线程抢到资源的几率就越大。相邻的两个线程的优先级的差异性不明显。至少要相差5个等级才能体现的相对明显一点点。

【java学习笔记】线程【java学习笔记】线程
 1 public class PriorityDemo {
 2     public static void main(String[] args) {
 3 
 4         Thread t1 = new Thread(new PDemo(), "A");
 5         Thread t2 = new Thread(new PDemo(), "B");
 6 
 7         t1.setPriority(1);
 8         t2.setPriority(10);
 9 
10         t1.start();
11         t2.start();
12 
13         // 获取线程的优先级
14         // System.out.println(t1.getPriority());
15         // System.out.println(t2.getPriority());
16 
17     }
18 }
19 
20 class PDemo implements Runnable {
21     @Override
22     public void run() {
23         for (int i = 0; i < 10; i++) {
24             System.out.println(Thread.currentThread().getName() + ":" + i);
25         }
26     }
27 }
PriorityDemo