多线程[Thread]
多线程是Java语言的重要特性,大量应用于网络编程、服务器端程序的开发,最常见的UI界面底层原理、操作系统底层原理都大量使用了多线程。
1、程序、进程与线程
程序Program
• 程序是一段静态的代码,指令的集合。
进程Process
• 进程是指一种正在运行的程序,有自己的地址空间,进程是 系统 进行资源分配的最小单位。多个进程之间不共享资源。
进程的特点: 动态性、并发性、独立性
并发和并行的区别
• 多个CPU同时执行多个任务
• 一个CPU(采用时间片)同时执行多个任务
线程Thread
• 进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。
• 线程又被称为轻量级进程(lightweight process)
• 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为 多线程
• 一个进程中至少要有一个线程。当然可以有多个线程
• 线程是cpu进行调度执行的最小单位
线程特点
• 轻量级进程
• 独立调度的基本单位
• 可并发执行
• 共享进程资源
线程和进程的区别
Thread类常用方法
2、线程的创建和启动
线程的创建
• 方式1:继承Java.lang.Thread类,重写run() 方法
• 方式2:实现Java.lang.Runnable接口,并实现run() 方法
• 方式3:JUC并发包下,实现Callable。JDK1.5 出现的。
• 方法run( )称为线程体。
线程的启动
• 新建的线程不会自动开始运行,必须通过start( )方法启动
• 不能直接调用run()来启动线程,这样run()将作为一个普通方法立即执行,执行完毕前其他线程无法并发法执行。
• Java程序启动时,会立刻创建主线程,main就是在这个线程上运行。当不再产生新线程时,程序是单线程的。
第一种方式创建线程:
1、创建:Thread + 重写run
2、启动:创建子类对象 + start()
1 // 继承Thread类 2 public class StartThread extends Thread { 3 // 重写了run方法 4 @Override 5 public void run() { 6 for (int i = 0; i < 5; i++) { 7 System.out.println("一边听音乐"); 8 } 9 } 10 public static void main(String[] args) { 11 // 创建子类对象 12 StartThread thread = new StartThread(); 13 // 启动线程 14 thread.start();// 不保证CPU立即调用 15 // thread.run();// 调用普通方法,不管怎么运行:必须听完音乐才能敲代码 16 for (int i = 0; i < 5; i++) { 17 System.out.println("一边敲代码"); 18 } 19 } 20 }
第二种方式创建线程:
1、创建:实现Runnable接口 + 重写run
2、启动:创建实现类对象 + Thread对象 + start()
1 // 实现Runnable接口 2 public class StartRun implements Runnable { 3 // 重写了run方法,线程的入口点 4 @Override 5 public void run() { 6 for (int i = 0; i < 5; i++) { 7 System.out.println("一边听音乐"); 8 } 9 } 10 public static void main(String[] args) { 11 /*// 创建实现类对象 12 StartRun sr = new StartRun(); 13 // 创建代理类对象 14 Thread t = new Thread(sr); 15 // 启动 16 t.start();*/ 17 18 // 可简写为 19 new Thread(new StartRun()).start(); 20 21 22 // thread.run();// 调用普通方法,不管怎么运行:必须听完音乐才能敲代码 23 for (int i = 0; i < 5; i++) { 24 System.out.println("一边敲代码"); 25 } 26 } 27 }
两种线程创建方式的比较
• 继承Thread类方式的多线程
• 优势:编写简单
• 劣势:无法继承其它父类
• 实现Runnable接口方式的多线程
• 优势:可以继承其它类,多线程可共享同一个Runnable对象
• 劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThread()方法
一般Runnable接口方式要优先使用。
example【模拟抢票】
1 package boom.thread; 2 /** 3 * 共享资源:模拟抢票[会出现线程安全] 4 * @author Administrator 5 * 6 */ 7 public class Web12306 implements Runnable{ 8 // 余票 9 private int ticketNums = 100; 10 11 @Override 12 public void run() { 13 while(true){ 14 if(ticketNums < 0){ 15 break; 16 } 17 try { 18 Thread.sleep(100); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 // Thread.currentThread().getName() 当前线程名称 23 System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--); 24 } 25 } 26 public static void main(String[] args) { 27 // 一份资源 28 Web12306 web12306 =new Web12306(); 29 System.out.println(Thread.currentThread().getName()); 30 // 多个代理 31 new Thread(web12306, "售票员-1").start(); 32 new Thread(web12306, "售票员-2").start(); 33 new Thread(web12306, "售票员-3").start(); 34 new Thread(web12306, "售票员-4").start(); 35 new Thread(web12306, "售票员-5").start(); 36 } 37 }
example【龟兔赛跑】
1 package boom.thread; 2 3 import java.io.Reader; 4 5 /** 6 * 模拟龟兔赛跑 7 * 8 * @author Administepsor 9 * 10 */ 11 public class Racer implements Runnable { 12 private static String winner;// 记录胜利者 13 14 @Override 15 public void run() { 16 for (int steps = 1; steps <= 100; steps++) { 17 // 模拟休息[兔子每走十步就休息100毫秒] 18 if (Thread.currentThread().getName().equals("兔子") && steps % 10 == 0) { 19 try { 20 Thread.sleep(100); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 } 25 System.out.println(Thread.currentThread().getName() + "-->" + steps); 26 // 比赛是否结束 27 boolean flag = gameOver(steps); 28 if (flag) { 29 break; 30 } 31 } 32 } 33 34 // 比赛是否结束 35 public boolean gameOver(int steps) { 36 if (winner != null) {// 存在胜利者 37 return true; 38 } else { 39 if (steps == 100) { 40 winner = Thread.currentThread().getName(); 41 System.out.println("winner => " + winner); 42 } 43 } 44 return false; 45 } 46 47 public static void main(String[] args) { 48 Racer reader = new Racer(); 49 new Thread(reader, "乌龟").start(); 50 new Thread(reader, "兔子").start(); 51 } 52 53 }
第三种方式:实现Callable接口【先了解】
• 与实行Runnable相比, Callable功能更强大些
• 方法不同
• 可以有返回值,支持泛型的返回值
• 可以抛出异常
• 需要借助FutureTask,比如获取返回结果
Future接口
• 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
• FutrueTask是Futrue接口的唯一的实现类
• FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
example【改进龟兔赛跑】
1 package boom.thread; 2 3 import java.io.Reader; 4 import java.lang.reflect.Executable; 5 import java.util.concurrent.Callable; 6 import java.util.concurrent.ExecutionException; 7 import java.util.concurrent.ExecutorService; 8 import java.util.concurrent.Executors; 9 import java.util.concurrent.Future; 10 11 /** 12 * 模拟龟兔赛跑 13 * 14 * @author Administepsor 15 * 16 */ 17 public class Racer2 implements Callable<Integer> { 18 private static String winner;// 记录胜利者 19 20 @Override 21 public Integer call() throws Exception { 22 for (int steps = 1; steps <= 100; steps++) { 23 // 模拟休息[兔子每走十步就休息100毫秒] 24 if (Thread.currentThread().getName().equals("pool-1-thread-1") && steps % 10 == 0) { 25 Thread.sleep(100); 26 } 27 System.out.println(Thread.currentThread().getName() + "-->" + steps); 28 // 比赛是否结束 29 boolean flag = gameOver(steps); 30 if (flag) { 31 return steps; 32 } 33 } 34 return null; 35 } 36 37 // 比赛是否结束 38 public boolean gameOver(int steps) { 39 if (winner != null) {// 存在胜利者 40 return true; 41 } else { 42 if (steps == 100) { 43 winner = Thread.currentThread().getName(); 44 System.out.println("winner => " + winner); 45 return true; 46 } 47 } 48 return false; 49 } 50 51 public static void main(String[] args) throws InterruptedException, ExecutionException { 52 Racer2 reader = new Racer2(); 53 54 // 创建执行服务 55 ExecutorService ser = Executors.newFixedThreadPool(2); 56 // 提交执行 57 Future<Integer> result1 = ser.submit(reader); 58 Future<Integer> result2 = ser.submit(reader); 59 // 获取结果 60 Integer integer1 = result1.get(); 61 Integer integer2 = result2.get(); 62 System.out.println(integer1 + "-->" + integer2); 63 // 关闭服务 64 ser.shutdownNow(); 65 } 66 67 }
简单的静态代理设计模式
1 package boom.thread; 2 3 public class StaticProxy { 4 public static void main(String[] args) { 5 // new 子类对象传入真是角色在.具体实现方法 6 new WeddingCompany(new You()).happyMarry(); 7 } 8 } 9 // 定义抽象类 10 interface Marry{ 11 // 实现抽象方法 12 void happyMarry(); 13 } 14 // 真实角色继承了抽象类 15 class You implements Marry{ 16 // 重写抽象类的抽象方法 17 @Override 18 public void happyMarry() { 19 System.out.println("我们终于结婚了!!"); 20 } 21 } 22 // 代理角色[婚庆公司] 23 class WeddingCompany implements Marry{ 24 // 真实角色 25 private Marry flag; 26 // 带参构造方法 27 public WeddingCompany(Marry flag) { 28 this.flag = flag; 29 } 30 31 @Override 32 public void happyMarry() { 33 ready(); 34 this.flag.happyMarry(); 35 affter(); 36 } 37 38 private void ready() { 39 System.out.println("在路上!"); 40 } 41 42 private void affter() { 43 System.out.println("民政局拿小本本!!!"); 44 } 45 }
3、线程的生命周期[线程的五大状态]
新生状态:
• 用new关键字建立一个线程对象后,该线程对象就处于新生状态。
• 处于新生状态的线程有自己的内存空间,通过调用start进入就绪状态
就绪状态:
• 处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU
• 当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称之为“cpu调度”。
运行状态:
• 在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任务而死亡。
• 如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。
阻塞状态:
• 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
• 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
死亡状态:
• 死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过执行stop方法来终止一个线程[不推荐使用】,三是线程抛出未捕获的异常.
4、线程的控制方法
·sleep()
·使线程停止运行一段时间,将处于阻塞状态
·如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!
·join()
·阻塞指定线程等到另一个线程完成以后再继续执行。
·yield()
·让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态;等待cpu调度器重新调度
·调用了yield方法之后,如果没有其他等待执行的线程,此时当前线程就会马上恢复执行!
·setDaemon()
·可以将指定的线程设置成后台线程,守护线程;
·创建用户线程的线程结束时,后台线程也随之消亡;
·只能在线程启动之前把它设为后台线程
·setPriority(int newPriority)getPriority()
·线程的优先级代表的是概率·范围从1到10,默认为5
·stop()停止线程 不推荐使用
详细介绍线程控制方法:
1:stop()终止线程方式:
* 1、线程正常执行完毕
* 2、外部加入标识,不使用stop,destroy【线程不安全】
1 package boom.thread.state; 2 3 /** 4 * 终止线程的两种方法: 1、线程正常执行完毕 2、外部加入标识,不使用stop,destroy。 5 * 6 * @author Administrator 7 * 8 */ 9 public class TerminateThread implements Runnable { 10 // 1、加入标识,标记线程体是否可以运行 11 private boolean flag = true; 12 private String name; 13 14 public TerminateThread(String name) { 15 this.name = name; 16 } 17 18 @Override 19 public void run() { 20 int i = 0; 21 // 2、关联标识 true--> 运行,false --> 终止 22 while (flag) { 23 System.out.println(name + "-->" + i++); 24 } 25 } 26 27 // 3、对外提供方法改变标识 28 public void terminate() { 29 this.flag = false; 30 } 31 32 public static void main(String[] args) { 33 TerminateThread thread = new TerminateThread("阿甘"); 34 new Thread(thread).start(); 35 36 for (int i = 0; i < 100; i++) { 37 if (i == 66) { 38 thread.terminate();// 线程终止 39 System.out.println("thread over"); 40 } 41 System.out.println("main=>" + i); 42 } 43 } 44 45 }
2:sleep()【火车票模拟延时】
sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时、倒计时等。
每一个对象都有一个锁,sleep不会释放锁;
1 package boom.thread.state; 2 3 public class BlockedSleep { 4 5 public static void main(String[] args) { 6 TicketSystem ts = new TicketSystem(); 7 new Thread(ts, "售票员 - 1").start(); 8 new Thread(ts, "售票员 - 2").start(); 9 new Thread(ts, "售票员 - 3").start(); 10 new Thread(ts, "售票员 - 4").start(); 11 new Thread(ts, "售票员 - 5").start(); 12 new Thread(ts, "售票员 - 6").start(); 13 } 14 15 } 16 class TicketSystem implements Runnable{ 17 private int ticketNums = 99; 18 19 @Override 20 public void run() { 21 while(true){ 22 if(ticketNums < 0){ 23 break; 24 } 25 // 模拟延时 26 try { 27 Thread.sleep(200); 28 } catch (InterruptedException e) { 29 e.printStackTrace(); 30 } 31 System.out.println(Thread.currentThread().getName() + "=>" + ticketNums--); 32 } 33 34 } 35 }
模拟倒计时:
1 public static void main(String[] args) throws InterruptedException { 2 int num = 10; 3 while(true) { 4 if(num == 0) { 5 System.out.println("点火"); 6 break; 7 } 8 Thread.sleep(1000); 9 System.out.println("倒计时 --> " + num--); 10 } 11 }
3:yield():让出CPU的调度,暂停线程,进入就绪状态,并不是阻塞状态.
1 MyYield myYield = new MyYield(); 2 new Thread(myYield, "国产001").start(); 3 new Thread(myYield, "国产002").start(); 4 5 /*---------------------------------------------------------------*/ 6 7 class MyYield implements Runnable { 8 @Override 9 public void run() { 10 System.out.println(Thread.currentThread().getName() + "--> start"); 11 Thread.yield();// 礼让【重回调度器里等待调度。可能礼让成功,可能调度到自己】 12 System.out.println(Thread.currentThread().getName() + "--> end"); 13 14 } 15 }
4:join():可理解为插队线程
1 package boom.thread.state; 2 3 public class JoinDemo { 4 5 public static void main(String[] args) { 6 System.out.println("/-------买酱油的的故事-------/"); 7 new Thread(new Father()).start(); 8 } 9 10 } 11 class Father extends Thread{ 12 @Override 13 public void run() { 14 System.out.println("炒菜呢,发现酱油没了,让儿子去买酱油"); 15 Thread thread = new Thread(new Son()); 16 thread.start(); 17 try { 18 thread.join();// father被阻塞 19 System.out.println("接过酱油,继续炒菜"); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 System.out.println("孩子走丢了"); 23 } 24 } 25 } 26 class Son extends Thread{ 27 @Override 28 public void run() { 29 System.out.println("接过钱去买酱油,发现路边有游戏厅去玩了一会"); 30 for(int i = 0;i<4;i++){ 31 System.out.println(i + "秒过去..."); 32 33 try { 34 Thread.sleep(1000); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 } 39 System.out.println("想起来买酱油,去买酱油"); 40 System.out.println("买好后回来了。"); 41 } 42 }
5、线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
线程的优先级用数字表示,范围从1到10
• Thread.MIN_PRIORITY = 1
• Thread.MAX_PRIORITY = 10
• Thread.NORM_PRIORITY = 5【默认】
使用下述方法获得或设置线程对象的优先级。
• int getPriority(); 返回线程的优先级。
• void setPriority(int newPriority); 更改线程的优先级。
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的,再调用优先级低的线程。
1 package boom.thread.state; 2 3 /** 4 * 线程的优先级:优先级低只是意味着获得调度的概率低,比不是优先级高的就决定的先调用 5 * 6 * @author Administrator 7 * 8 */ 9 public class PriorityDemo { 10 11 public static void main(String[] args) { 12 System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); 13 MyPriority priority = new MyPriority(); 14 15 Thread thread_1 = new Thread(priority, "百度"); 16 Thread thread_2 = new Thread(priority, "阿里"); 17 Thread thread_3 = new Thread(priority, "腾讯"); 18 Thread thread_4 = new Thread(priority, "Google"); 19 Thread thread_5 = new Thread(priority, "网易"); 20 Thread thread_6 = new Thread(priority, "网景"); 21 22 // 启动前设置优先级 23 thread_1.setPriority(Thread.MAX_PRIORITY); 24 thread_3.setPriority(Thread.MAX_PRIORITY); 25 thread_5.setPriority(Thread.MAX_PRIORITY); 26 thread_6.setPriority(Thread.MIN_PRIORITY); 27 28 thread_1.start(); 29 thread_2.start(); 30 thread_3.start(); 31 thread_4.start(); 32 thread_5.start(); 33 thread_6.start(); 34 35 } 36 37 } 38 39 class MyPriority implements Runnable { 40 @Override 41 public void run() { 42 System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); 43 } 44 }
守护线程:是为用户线程服务的;jvm停止不用等待守护线程执行完毕。默认的线程【用户线程】:JVM等待用户线程执行完毕才会停止。
·线程分为用户线程和守护线程;
·虚拟机必须确保用户线程执行完毕;
·虚拟机不用等待守护线程执行完毕;
·如后台记录操作日志、监控内存使用等。
1 package boom.thread.state; 2 3 /** 4 * 守护线程:是为用户线程服务的;jvm停止不用等待守护线程执行完毕 5 * 6 * @author Administrator 7 * 8 */ 9 public class DaemonTest { 10 11 public static void main(String[] args) { 12 God god = new God(); 13 Person Person = new Person(); 14 Thread thread = new Thread(god, "上帝"); 15 thread.setDaemon(true);// 默认为false,设置用户线程为守护线程,值true 16 thread.start(); 17 new Thread(Person, "Person").start(); 18 19 } 20 21 } 22 23 class Person implements Runnable { 24 // 人类的视角是普遍是一个世纪的年华 25 @Override 26 public void run() { 27 for (int i = 0; i < 365 * 100; i++) { 28 System.out.println(Thread.currentThread().getName() + ":" + "happy life……"); 29 } 30 System.out.println(Thread.currentThread().getName() + ":" + "Game Over……"); 31 } 32 } 33 34 class God implements Runnable { 35 // 上帝的视角是无生命周期的死循环 36 @Override 37 public void run() { 38 while (true) { 39 System.out.println(Thread.currentThread().getName() + ":" + "bless Person……"); 40 } 41 } 42 }
线程不安全的三大经典案例:【线程不安全会出现数据产生负数或者出现相同的值】
买票
1 package boom.thread.syn; 2 /** 3 * 线程不安全:数据出现负数或者出现相同的数据 4 * @author Administrator 5 * 6 */ 7 public class Unsafe { 8 9 public static void main(String[] args) { 10 Unsafe12306 safe = new Unsafe12306(); 11 new Thread(safe, "[售票员]-1").start(); 12 new Thread(safe, "[售票员]-2").start(); 13 new Thread(safe, "[售票员]-3").start(); 14 15 } 16 17 } 18 19 class Unsafe12306 implements Runnable { 20 private int ticketNums = 10; 21 private boolean flag = true; 22 23 @Override 24 public void run() { 25 while (flag) { 26 test(); 27 } 28 } 29 30 private void test() { 31 if (ticketNums < 0) { 32 flag = false; 33 return; 34 } 35 try { 36 Thread.sleep(100); 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--); 41 } 42 43 }
取钱
1 package boom.thread.syn; 2 3 /** 4 * 线程不 安全:取钱问题 5 * 6 * @author Administrator 7 * 8 */ 9 public class Unsafe2 { 10 11 public static void main(String[] args) { 12 // 账户 13 Account account = new Account(100, "小金库总额"); 14 Drawing you = new Drawing(account, 80, "可悲的你"); 15 Drawing wife = new Drawing(account, 90, "happy的她"); 16 you.start(); 17 wife.start(); 18 } 19 20 } 21 22 // 账户 23 class Account { 24 int money;// 金额 25 String name;// 名称 26 27 public Account(int money, String name) { 28 this.money = money; 29 this.name = name; 30 } 31 } 32 33 // 模拟取款 34 class Drawing extends Thread { 35 Account account;// 取钱的账户 36 int drawingMoney;// 取钱的金额 37 int packetTotal;// 口袋里的钞票 38 39 public Drawing(Account account, int drawingMoney, String name) { 40 super(name); 41 this.account = account; 42 this.drawingMoney = drawingMoney; 43 } 44 45 @Override 46 public void run() { 47 // 如果账户的钱 - 要取的钱< 0;直接结束 48 if(account.money - drawingMoney < 0){ 49 return; 50 } 51 try { 52 Thread.sleep(1000); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } 56 // 账户的里钱=账户总额- 要取的钱 57 account.money -= drawingMoney; 58 packetTotal += drawingMoney; 59 System.out.println(this.getName() + "-->口袋里的钱:" + packetTotal); 60 System.out.println(this.getName() + "-->账户余额:" + account.money); 61 } 62 63 }
操作容器
1 package boom.thread.syn; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * 线程不安全:操作容器 8 * @author Administrator 9 * 10 */ 11 public class Unsafe3 { 12 13 public static void main(String[] args) { 14 List<String> list = new ArrayList<String>(); 15 for(int i = 0; i <100;i++){ 16 new Thread(()->{ 17 list.add(Thread.currentThread().getName()); 18 }).start(); 19 } 20 System.out.println(list.size()); 21 } 22 23 }
6、多线程_并发_同步_队列与锁
并发:同一个对象被多个线程同时访问。
线程同步
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候,我们就需要用到“线程同步”。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全。当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用。
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可存在以下问题:·
一个线程持有锁会导致其它所有需要此锁的线程挂起;
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,副起性能问题。
线程同步的实现方案
同步1代码块
• synchronized (obj){ }
同步方法
• private synchronized void makeWithdrawal(int amt) {}
并发_同步_synchronized方法
售票员买票
1 package boom.thread.syn; 2 3 /** 4 * 线程安全:在并发时保证数据的正确性、效率尽可能的高 5 * synchronize 6 * [1、同步方法] 2、同步块 7 * 8 * @author Administrator 9 * 10 */ 11 public class SafeTest { 12 13 public static void main(String[] args) { 14 Ssafe12306 safe = new Ssafe12306(); 15 new Thread(safe, "[售票员]-1").start(); 16 new Thread(safe, "[售票员]-2").start(); 17 new Thread(safe, "[售票员]-3").start(); 18 19 } 20 21 } 22 23 class Ssafe12306 implements Runnable { 24 private int ticketNums = 100; 25 private boolean flag = true; 26 27 @Override 28 public void run() { 29 while (flag) { 30 test(); 31 try { 32 Thread.sleep(100); 33 } catch (InterruptedException e) { 34 e.printStackTrace(); 35 } 36 } 37 } 38 39 // 线程安全:synchronize同步方法,锁住的方法的对象资源 40 private synchronized void test() { 41 if (ticketNums <= 0) { 42 flag = false; 43 return; 44 } 45 try { 46 Thread.sleep(200); 47 } catch (InterruptedException e) { 48 e.printStackTrace(); 49 } 50 System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--); 51 } 52 53 }
error 取钱【锁定的目标出现也会出现线程的安全性问题】
1 package com.test; 2 3 /** 4 * 线程安全:在并发时保证数据的正确性、效率尽可能的高 synchronize 1、同步方法 2、同步块 5 * @author Administrator 6 * 7 */ 8 public class TestCode { 9 10 public static void main(String[] args) { 11 // 账户 12 Account account = new Account(100, "小金库总额"); 13 SafeDrawing you = new SafeDrawing(account, 80, "可悲的你"); 14 SafeDrawing wife = new SafeDrawing(account, 90, "happy的她"); 15 you.start(); 16 wife.start(); 17 } 18 19 } 20 21 // 账户 22 class Account { 23 int money;// 金额 24 String name;// 名称 25 26 public Account(int money, String name) { 27 this.money = money; 28 this.name = name; 29 } 30 } 31 32 // 模拟取款 33 class SafeDrawing extends Thread { 34 Account account;// 取钱的账户 35 int DrawingMoney;// 取钱的金额 36 int packetTotal;// 口袋里的钞票 37 38 public SafeDrawing(Account account, int DrawingMoney, String name) { 39 super(name); 40 this.account = account; 41 this.DrawingMoney = DrawingMoney; 42 } 43 44 @Override 45 public void run() { 46 test(); 47 } 48 49 // 锁定的目标不对,锁定的不是this,应该锁住account 50 private synchronized void test() { 51 // 如果账户的钱 - 要取的钱< 0;直接结束 52 if (account.money - DrawingMoney < 0) { 53 return; 54 } 55 try { 56 Thread.sleep(1000); 57 } catch (InterruptedException e) { 58 e.printStackTrace(); 59 } 60 // 账户的里钱=账户总额- 要取的钱 61 account.money -= DrawingMoney; 62 packetTotal += DrawingMoney; 63 System.out.println(this.getName() + "-->口袋里的钱:" + packetTotal); 64 System.out.println(this.getName() + "-->账户余额:" + account.money); 65 } 66 67 }
并发_同步_synchronized块
同步块:synchronized(obj){},obi称之为同步监视器
·obi可以是任何对象,但是推荐使用共享资源作为同步监视器
·同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或class即类的模子
同步监视器的执行过程
·第一个线程访问,锁定同步监视器,执行其中代码
·第二个线程访问,发现同步监视器被锁定,无法访问
·第一个线程访问完毕,解锁同步监视器
·第二个线程访问,发现同步监视器未锁,锁定并访问
同步代码块【取钱,操作容器】
取钱
1 package com.test; 2 /** 3 * 线程安全:在并发时保证数据的正确性、效率尽可能的高 synchronize 1、同步方法 [2、同步块] 4 * 5 * @author Administrator 6 * 7 */ 8 public class TestCode { 9 10 public static void main(String[] args) { 11 // 账户 12 Account account = new Account(1000, "小金库总额"); 13 SynDrawing you = new SynDrawing(account, 80, "可悲的你"); 14 SynDrawing wife = new SynDrawing(account, 90, "happy的她"); 15 you.start(); 16 wife.start(); 17 } 18 19 } 20 21 // 账户 22 class Account { 23 int money;// 金额 24 String name;// 名称 25 26 public Account(int money, String name) { 27 this.money = money; 28 this.name = name; 29 } 30 } 31 32 // 模拟取款 33 class SynDrawing extends Thread { 34 Account account;// 取钱的账户 35 int DrawingMoney;// 取钱的金额 36 int packetTotal;// 口袋里的钞票 37 38 public SynDrawing(Account account, int DrawingMoney, String name) { 39 super(name); 40 this.account = account; 41 this.DrawingMoney = DrawingMoney; 42 } 43 44 @Override 45 public void run() { 46 test(); 47 } 48 49 // 锁定account 50 private void test() { 51 // 提高性能 52 if (account.money == 0) { 53 return; 54 } 55 // 同步代码块,锁住account 56 synchronized (account) { 57 // 如果账户的钱 - 要取的钱< 0;直接结束 58 if (account.money - DrawingMoney < 0) { 59 return; 60 } 61 try { 62 Thread.sleep(1000); 63 } catch (InterruptedException e) { 64 e.printStackTrace(); 65 } 66 // 账户的里钱=账户总额- 要取的钱 67 account.money -= DrawingMoney; 68 packetTotal += DrawingMoney; 69 System.out.println(this.getName() + "-->口袋里的钱:" + packetTotal); 70 System.out.println(this.getName() + "-->账户余额:" + account.money); 71 } 72 } 73 74 }
操作容器
1 package boom.thread.syn; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * @author Administrator 8 * 9 */ 10 public class SynBlock2 { 11 12 public static void main(String[] args) throws InterruptedException { 13 List<String> list = new ArrayList<String>(); 14 for (int i = 0; i < 1000; i++) { 15 new Thread(() -> { 16 synchronized (list) { 17 list.add(Thread.currentThread().getName()); 18 } 19 }).start(); 20 } 21 Thread.sleep(1000); 22 System.out.println(list.size()); 23 } 24 25 }
死锁:某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
过多的同步可能造成互相不释放资源,从而相互等待,一般发生于同步中持有多个对象的锁。