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.线程的状态
创建:新创建了一个线程对象。
就绪:线程对象创建后,其他线程调用了该对象的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)
②同步函数的锁对象是固定的,不能由开发者来指定
推荐使用同步代码块
①同步代码块的锁对象可以由开发者指定,方便控制。而同步方法是固定的。
②同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步
例:模拟取款,
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(" 余额为:" + 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 }
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(" 余额为:" + balance); 72 } else { 73 System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!"); 74 } 75 } 76 77 }
成功的情况:
失败的情况:
5.死锁
由于多个线程之间的锁形成了嵌套导致程序无法继续运行的现象。
避免死锁:减少线程数量,统一锁对象,减少锁嵌套。
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 }
死锁:
正常:
6.等待唤醒机制
wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll:唤醒持有同一监视器中调用wait的所有的线程。
①notify
通过等待唤醒机制调节了线程之间的执行顺序
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 }
②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。
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 }
sleep与wait的区别:
①sleep在使用的时候需要指定休眠时间,到点自然醒。释放执行权,不释放锁。是一个静态方法,设计在了Thread类上
②wait在使用的时候可以指定等待时间,也可以不指定,如果不指定等待时间就需要唤醒。释放执行权,释放锁。是一个非静态方法,设计在了Object类上
例:生产消费模型
一个线程表示生产者Producer,一个线程表示消费者Consumer,商品的总数量不超过1000。
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 }
7.守护线程
守护别的线程。当被守护的线程结束,守护线程无论执行完成与否都得随之结束。
一个线程要么是守护线程要么是被守护的线程。守护线程是随着最后一个被守护线程的结束而结束,例如GC。
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 }
8.线程的优先级
线程的优先级分为1-10,理论上数字越大等级越高,这个线程抢到资源的几率就越大。相邻的两个线程的优先级的差异性不明显。至少要相差5个等级才能体现的相对明显一点点。
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 }