正文
17.原子性
假设对共享变量除了赋值之外并不完成其他操作,那么可以将这些共享变量声明为volatile
18.死锁
注:java中没有任何东西可以避免或打破死锁,必须仔细设计,确保不会出现死锁
每一个线程等待其他的线程唤醒,导致所有的线程都被阻塞
package io.laokou.test.concurrent; import java.util.Arrays; /** * @author Kou Shenhai * @version 1.0 * @date 2022/4/19 0019 上午 8:30 */ public class DeadLockBankTest { private static final int NACCOUNTS = 100; private static final double INITIAL_BALANCE = 100; private static final double MAX_AMOUNT = 1000; private static final int DELAY = 10; public static void main(String[] args) { DeadLockBank bank = new DeadLockBank(NACCOUNTS,INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable runnable = () -> { try { while (true) { int toAccount = (int)(bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random() + 100; bank.transfer(fromAccount,toAccount, amount); Thread.sleep((int)(DELAY * Math.random())); } } catch (Exception e) {} }; new Thread(runnable).start(); } } } class DeadLockBank { private final double[] accounts; public DeadLockBank(int n,double initialBalance) { this.accounts = new double[n]; Arrays.fill(accounts, initialBalance); } public void transfer(int from,int to,double amount) throws InterruptedException { synchronized (this) { if (accounts[from] < amount) { wait(); } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("%10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); notifyAll(); } } public double getTotalBalance() { synchronized (this) { double sum = 0; for (int i = 0; i < accounts.length; i++) { sum += accounts[i]; } return sum; } } public int size() { return accounts.length; } }
19.局部变量
package io.laokou.juc; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; /** * @author Kou Shenhai * @version 1.0 * @date 2022/4/21 0021 上午 9:09 */ public class ThreadLocalTest { private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); public static void main(String[] args) { System.out.println("今天的日期:" + df.get().format(new Date())); } } /** * 今天的日期:2022-04-21 */
package io.laokou.juc; import java.util.concurrent.ThreadLocalRandom; /** * @author Kou Shenhai * @version 1.0 * @date 2022/4/21 0021 上午 10:40 */ public class ThreadLocalRandomTest { public static void main(String[] args) { int randomNumber = ThreadLocalRandom.current().nextInt(1000); System.out.println("随机数:" + randomNumber); } } /** * 随机数:760 */
20.锁测试与超时
tryLock():尝试获得锁而没有发生阻塞,如果成功返回真,这个方法会抢占可用的锁,即使该锁有公平加锁策略,即便其他线程已经等待很久也是如此
21.读写锁
readLock():得到一个可以被多个读操作共用的读锁,但会排斥所有写操作
writeLock():得到一个写锁,排斥所有其他的读操作和写操作
package io.laokou.test.concurrent; import java.util.Arrays; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @author Kou Shenhai * @version 1.0 * @date 2022/4/19 0019 上午 8:30 */ public class ReadWriteLockBankTest { private static final int NACCOUNTS = 100; private static final double INITIAL_BALANCE = 1000; private static final double MAX_AMOUNT = 1000; private static final int DELAY = 10; public static void main(String[] args) { ReadWriteLockBank bank = new ReadWriteLockBank(NACCOUNTS,INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable runnable = () -> { try { while (true) { int toAccount = (int)(bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random(); bank.transfer(fromAccount,toAccount, amount); Thread.sleep((int)(DELAY * Math.random())); } } catch (Exception e) {} }; new Thread(runnable).start(); } } } class ReadWriteLockBank { private final double[] accounts; private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock(); public ReadWriteLockBank(int n,double initialBalance) { this.accounts = new double[n]; Arrays.fill(accounts, initialBalance); } public void transfer(int from,int to,double amount) { //获取锁 writeLock.lock(); try { if (accounts[from] < amount) { return; } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("%10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); } finally { //释放锁 writeLock.unlock(); } } public double getTotalBalance() { readLock.lock(); try { double sum = 0; for (int i = 0; i < accounts.length; i++) { sum += accounts[i]; } return sum; } finally { readLock.unlock(); } } public int size() { return accounts.length; } } /** * Thread[Thread-30,5,main] 429.99 from 30 to 70 Total Balance: 100000.00 * Thread[Thread-70,5,main] 723.57 from 70 to 68 Total Balance: 100000.00 * Thread[Thread-40,5,main] 193.84 from 40 to 1 Total Balance: 100000.00 * Thread[Thread-11,5,main] 287.24 from 11 to 69 Total Balance: 100000.00 * Thread[Thread-67,5,main] 88.12 from 67 to 96 Total Balance: 100000.00 * Thread[Thread-16,5,main] 162.57 from 16 to 99 Total Balance: 100000.00 * Thread[Thread-69,5,main] 715.54 from 69 to 53 Total Balance: 100000.00 * Thread[Thread-61,5,main] 543.64 from 61 to 3 Total Balance: 100000.00 * Thread[Thread-76,5,main] 312.71 from 76 to 43 Total Balance: 100000.00 * Thread[Thread-71,5,main] 89.88 from 71 to 41 Total Balance: 100000.00 * Thread[Thread-40,5,main] 53.28 from 40 to 8 Total Balance: 100000.00 */
22.阻塞队列
LinkedBlockingQueue:容量是没有上边界,可用选择指定最大容量
LinkedBlockingDeque:双向阻塞队列
ArrayBlockingQueue:构造时需要指定容量,并且有一个可选的参数来指定是否需要公平,若设置了公平参数,那么等待了最长时间的线程会优先得到处理,通常,公平性会降低性能,只有在确实非常需要时才使用它
PriorityBlockingQueue:一个带优先级的队列,而不是先进先出队列,元素会按照它们的优先级顺序移出,该队列是没有容量上限,但是,如果队列是空的,取元素的操作会阻塞
package io.laokou.test.concurrent; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.Scanner; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /** * @author Kou Shenhai * @version 1.0 * @date 2022/4/21 0021 下午 3:45 */ public class BlockingQueueTest { private static final int FILE_QUEUE_SIZE = 10; private static final int SEARCH_THREADS = 100; private static final File DUMMY = new File(""); private static BlockingQueue<File> queue = new ArrayBlockingQueue(FILE_QUEUE_SIZE); public static void main(String[] args) { try(Scanner scanner = new Scanner(System.in)) { System.out.print("请输入目录:"); String directory = scanner.nextLine(); System.out.print("请输入关键字:"); String keyword = scanner.nextLine(); Runnable runnable = () -> { try { enumerate(directory); queue.put(DUMMY); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }; new Thread(runnable).start(); for (int i = 1; i <= SEARCH_THREADS; i++) { Runnable search = () -> { boolean done = false; while (!done) { try { //移出并返回头元素,如果队列为空,则阻塞 File file = queue.take(); if (file == DUMMY) { queue.put(file); done = true; } else { search(file,keyword); } } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(search).start(); } } } public static void enumerate(String directory) throws IOException { Files.walkFileTree(Paths.get(directory),new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { try { queue.put(file.toFile()); } catch (InterruptedException e) { e.printStackTrace(); } return super.visitFile(file, attrs); } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return super.visitFileFailed(file, exc); } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return super.postVisitDirectory(dir, exc); } }); } public static void search(File file,String keyword) { try(Scanner scanner = new Scanner(file, StandardCharsets.UTF_8.name())) { int lineNumber = 0; while (scanner.hasNextLine()) { lineNumber++; String line = scanner.nextLine(); if (line.contains(keyword)) { System.out.printf("%s:%d:%s%n",file.getPath(),lineNumber,line); } } } catch (FileNotFoundException e) { e.printStackTrace(); } } } /** * 请输入目录:d:/cloud/KCloud * 请输入关键字:laokou-dubbo-api * d:\cloud\KCloud\.idea\compiler.xml:15: <module name="laokou-dubbo-api" /> * d:\cloud\KCloud\.idea\encodings.xml:4: <file url="file://$PROJECT_DIR$/laokou-cloud/laokou-dubbo/laokou-dubbo-api/src/main/java" charset="UTF-8" /> * d:\cloud\KCloud\.idea\compiler.xml:47: <module name="laokou-dubbo-api" target="1.8" /> * d:\cloud\KCloud\.idea\compiler.xml:80: <module name="laokou-dubbo-api" options="-parameters" /> * d:\cloud\KCloud\.idea\modules.xml:12: <module fileurl="file://$PROJECT_DIR$/laokou-cloud/laokou-dubbo/laokou-dubbo-api/laokou-dubbo-api.iml" filepath="$PROJECT_DIR$/laokou-cloud/laokou-dubbo/laokou-dubbo-api/laokou-dubbo-api.iml" /> * d:\cloud\KCloud\laokou-cloud\laokou-dubbo\laokou-dubbo-api\pom.xml:11: <artifactId>laokou-dubbo-api</artifactId> * d:\cloud\KCloud\laokou-cloud\laokou-dubbo\laokou-dubbo-consumer\pom.xml:20: <artifactId>laokou-dubbo-api</artifactId> * d:\cloud\KCloud\laokou-cloud\laokou-dubbo\laokou-dubbo-consumer\laokou-dubbo-consumer.iml:23: <orderEntry type="module" module-name="laokou-dubbo-api" /> * d:\cloud\KCloud\laokou-cloud\laokou-dubbo\laokou-dubbo-provider\laokou-dubbo-provider.iml:23: <orderEntry type="module" module-name="laokou-dubbo-api" /> * d:\cloud\KCloud\laokou-cloud\laokou-dubbo\laokou-dubbo-provider\pom.xml:20: <artifactId>laokou-dubbo-api</artifactId> * d:\cloud\KCloud\laokou-cloud\laokou-dubbo\pom.xml:17: <module>laokou-dubbo-api</module> */
23.卖票算法企业级模板实现
企业级简单实现(synchronized)
package io.laokou.test.concurrent; /** * 卖票 企业级套路 + 模板 * 在高内聚低耦合的前提: 线程操作(对外暴露的调用方法) 资源类 * 高内聚:资源类对外暴露的功能只在自己身上实现 * 低耦合:调用者和资源类无关 * @author Kou Shenhai */ public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 1; i <= 30; i++) { ticket.saleTicket(); } },"售票1").start(); new Thread(() -> { for (int i = 1; i <= 30; i++) { ticket.saleTicket(); } },"售票2").start(); new Thread(() -> { for (int i = 1; i <= 30; i++) { ticket.saleTicket(); } },"售票3").start(); } //资源类 static class Ticket { private int number = 30; //操作 public synchronized void saleTicket() { if (number > 0) { System.out.println(String.format("%s卖出第%s个,还剩下:%s",Thread.currentThread().getName(),number--,number)); } } } } /** * 售票1卖出第30个,还剩下:29 * 售票1卖出第29个,还剩下:28 * 售票1卖出第28个,还剩下:27 * 售票1卖出第27个,还剩下:26 * 售票1卖出第26个,还剩下:25 * 售票1卖出第25个,还剩下:24 * 售票1卖出第24个,还剩下:23 * 售票1卖出第23个,还剩下:22 * 售票1卖出第22个,还剩下:21 * 售票1卖出第21个,还剩下:20 * 售票1卖出第20个,还剩下:19 * 售票1卖出第19个,还剩下:18 * 售票3卖出第18个,还剩下:17 * 售票3卖出第17个,还剩下:16 * 售票3卖出第16个,还剩下:15 * 售票3卖出第15个,还剩下:14 * 售票3卖出第14个,还剩下:13 * 售票3卖出第13个,还剩下:12 * 售票3卖出第12个,还剩下:11 * 售票3卖出第11个,还剩下:10 * 售票3卖出第10个,还剩下:9 * 售票3卖出第9个,还剩下:8 * 售票3卖出第8个,还剩下:7 * 售票3卖出第7个,还剩下:6 * 售票3卖出第6个,还剩下:5 * 售票3卖出第5个,还剩下:4 * 售票3卖出第4个,还剩下:3 * 售票3卖出第3个,还剩下:2 * 售票3卖出第2个,还剩下:1 * 售票3卖出第1个,还剩下:0 */
juc优化卖票
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
//资源类 static class Ticket { private int number = 30; //可重入锁 private Lock lock = new ReentrantLock(); //操作 public void saleTicket() { lock.lock(); try { if (number > 0) { System.out.println(String.format("%s卖出第%s个,还剩下:%s", Thread.currentThread().getName(), number--, number)); } } finally { lock.unlock(); } } }
24.线程通信
线程横向交互
线程生产者/消费者(synchronized)
package io.laokou.test.concurrent; /** * 两个线程操作一个变量,一个线程让它加1,另外一个线程让它减1 * 实现交替操作 10轮 让这个变量的结果为0 * 1.高内聚低耦合前提下,线程操作资源类 * 2.判断操作通知 */ public class ThreadWaitNotifyTest { public static void main(String[] args) { AirConditioner airConditioner = new AirConditioner(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditioner.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"加1线程").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditioner.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"减1线程").start(); } //资源类 static class AirConditioner{ private int number = 0; public synchronized void increment() throws InterruptedException { //判断 if (number != 0) { this.wait(); } number++; System.out.println(String.format("%s---%s",Thread.currentThread().getName(),number)); //通知 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { //判断 if (number == 0) { this.wait(); } number--; System.out.println(String.format("%s---%s",Thread.currentThread().getName(),number)); //通知 this.notifyAll(); } } } /** * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 * 加1线程---1 * 减1线程---0 */
增加两个线程
package io.laokou.test.concurrent; /** * 两个线程操作一个变量,一个线程让它加1,另外一个线程让它减1 * 实现交替操作 10轮 让这个变量的结果为0 * 1.高内聚低耦合前提下,线程操作资源类 * 2.判断操作通知 */ public class ThreadWaitNotifyTest2 { public static void main(String[] args) { AirConditioner airConditioner = new AirConditioner(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditioner.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"加1线程").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditioner.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"减1线程").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditioner.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"减2线程").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditioner.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"加2线程").start(); } //资源类 static class AirConditioner{ private int number = 0; public synchronized void increment() throws InterruptedException { //判断 if (number != 0) { this.wait(); } number++; System.out.println(String.format("%s---%s",Thread.currentThread().getName(),number)); //通知 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { //判断 if (number == 0) { this.wait(); } number--; System.out.println(String.format("%s---%s",Thread.currentThread().getName(),number)); //通知 this.notifyAll(); } } } /** * 加1线程---1 * 减2线程---0 * 加2线程---1 * 加1线程---2 * 加2线程---3 * 减1线程---2 * 减1线程---1 * 减1线程---0 * 减2线程----1 * 减2线程----2 * 减2线程----3 * 减2线程----4 * 减2线程----5 * 减2线程----6 * 减2线程----7 * 减2线程----8 * 减2线程----9 * 减1线程----10 * 减1线程----11 * 减1线程----12 * 减1线程----13 * 减1线程----14 * 减1线程----15 * 减1线程----16 * 加2线程----15 * 加1线程----14 * 加2线程----13 * 加1线程----12 * 加2线程----11 * 加1线程----10 * 加2线程----9 * 加1线程----8 * 加2线程----7 * 加1线程----6 * 加2线程----5 * 加1线程----4 * 加2线程----3 * 加1线程----2 * 加2线程----1 * 加1线程---0 */
思考:为什么2个线程没有问题,4个线程就会出现虚假唤醒?
两个线程 -> 一个线程wait(),释放内存,那么另一个线程会得到资源
四个或多个线程 -> 一个线程wait(),可能会有相同功能的线程抢到资源继续wait(),当notifyAll()之后,两个相同功能的线程都被唤醒,导致对资源同时操作两次
juc生产者消费者
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
//资源类 static class AirConditioner{ private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() throws InterruptedException { lock.lock(); try { //判断 if (number != 0) { condition.await(); } number++; System.out.println(String.format("%s---%s", Thread.currentThread().getName(), number)); //通知 condition.signalAll(); } finally { lock.unlock(); } } public void decrement() throws InterruptedException { lock.lock(); try { //判断 if (number == 0) { condition.await(); } number--; System.out.println(String.format("%s---%s", Thread.currentThread().getName(), number)); //通知 condition.signalAll(); } finally { lock.unlock(); } } }
juc精确通知顺序访问
juc对比synchronized,能够实现精准加锁和放锁
package io.laokou.test.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 多线程之前按顺序调用 实现 A -> B -> B * AA打印5次 BB打印10次 CC打印15次 * ... 来10轮 * 1.高内聚低耦合前提下,线程 操作 资源类 * 2.判断 干活 通知 * 3.多线程交互中 需要防止多线程虚假唤醒 即判断资源状态使用while 不能用if * 4.标志位 */ public class ThreadOrderAccess { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); for (int i = 0; i < 10; i++) { new Thread(() -> { try { shareResource.print5(); } catch (InterruptedException e) { e.printStackTrace(); } },"线程1").start(); new Thread(() -> { try { shareResource.print10(); } catch (InterruptedException e) { e.printStackTrace(); } },"线程2").start(); new Thread(() -> { try { shareResource.print15(); } catch (InterruptedException e) { e.printStackTrace(); } },"线程3").start(); } } static class ShareResource { //1对应A 2对应B 3对应C private int number = 1; private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); private Condition conditionC = lock.newCondition(); public void print5() throws InterruptedException { lock.lock(); try { while (number != 1) { conditionA.await(); } for (int i = 0; i < 5; i++) { System.out.println(String.format("%s-%s",Thread.currentThread().getName(),i + 1)); } //标志位 number = 2; //通知 conditionB.signal(); } finally { lock.unlock(); } } public void print10() throws InterruptedException { lock.lock(); try { //判断 if (number != 2) { conditionB.await(); } for (int i = 0; i < 10; i++) { System.out.println(String.format("%s-%s",Thread.currentThread().getName(),i + 1)); } //标志位 number = 3; //通知 conditionC.signal(); } finally { lock.unlock(); } } public void print15() throws InterruptedException { lock.lock(); try { //判断 if (number != 3) { conditionB.await(); } for (int i = 0; i < 15; i++) { System.out.println(String.format("%s-%s",Thread.currentThread().getName(),i + 1)); } //标志位 number = 1; //通知 conditionC.signal(); } finally { lock.unlock(); } } } } /** * 线程1-1 * 线程1-2 * 线程1-3 * 线程1-4 * 线程1-5 * 线程2-1 * 线程2-2 * 线程2-3 * 线程2-4 * 线程2-5 * 线程2-6 * 线程2-7 * 线程2-8 * 线程2-9 * 线程2-10 * 线程3-1 * 线程3-2 * ... */
线程口诀
1.高内聚低耦合前提下,线程 操作 资源类
2.判断 操作 通知
3.多线程交互中,需要防止多线程的虚假唤醒,即判断资源状态使用while,不能使用if
4.标志位
package io.laokou.test.concurrent; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 多线程8锁 * 1.标准访问phone的邮件和短信功能,打印顺序不一定(决定权是操作系统) * 2.访问phone的邮件和短信功能 发邮件方法需要4秒 发短信方法不需要 则执行顺序是先邮件 再短信 * 3.新增一个普通方法hello不加lock 先执行hello 再执行email * 4.两部手机 一部发邮件 一部发短信 先短信 后邮件 * 5.两个静态同步方法 同一部手机 先邮件 后短信 * 6.两个静态同步方法 两部手机 一部发邮件 一部发短信 先邮件 再短信 * 7.一个静态同步方法 一个非静态同步方法 一部手机 无论哪个方法是静态的 顺序是先邮件 后短信 * 8.一个静态同步方法 一个非静态同步方法 两部手机 无论哪个方法是静态的 顺序是先邮件 后短信 */ public class Lock8 { public static void main(String[] args) { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(() -> phone1.sendEmail(),"A").start(); new Thread(phone2::sendSms,"B").start(); } static class Phone { private static Lock lock = new ReentrantLock(); public static void sendEmail() { lock.lock(); try { TimeUnit.SECONDS.sleep(4); System.out.println("--- send email"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void sendSms() { lock.lock(); try { System.out.println("--- send sms"); } finally { lock.unlock(); } } public void hello() { System.out.println("--- say hello"); } } } /** * --- send email * --- send sms */
1和2锁解释
一个对象里面如果有多个synchronized(),某一个时刻内,只要一个线程去调用其中的一个synchronized()了,其他的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized(),锁的是当前对象this(资源类),被锁定后,其他的线程都不能进入到当前对象的其他的synchronized()
3锁解释
加个普通方法后发现和同步锁无法(手机和手机壳互不相关)
4.锁解释
换成两个对象后,不是同一把锁了,情况立刻变化(两个人两个手机)
5和6锁解释
synchronized实现同步的基础:Java中每一个对象都可以作为锁
具体表现为以下3种形式
对于普通同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的Class对象(普通同步方法的锁和静态同步方法的锁 两个锁互不影响)
对于同步方法块,锁是synchronized括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁
也就是说如果一个实例对象的非静态方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以无需等待该实例对象已获得锁的非静态同步方法释放锁就可以获取他们自己的锁
7和8锁解释
静态同步方法所得是类这个模板,非静态同步方法所得是这个类的实例,得到的锁不是同一个,没有任何关联
所有的静态同步方法用的也是同一把锁-类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的,但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同实例对象的静态同步方法之间,只要它们同一个类的实例对象