Java岗:实打实掌握[Java多线程]和[并发编程]

article/2025/10/18 19:22:52

概述

面试中,多线程和并发编程已经是必不可少的了,我经常看到此类问题,当时也简单了解过,什么继承Thread类,实现Runnable接口,这些都被说烂了,知道这些当然是远远不够的,于是这几天搜索相关资料恶补了一下,为了方便后期复习,在此做个总结。

继承Thread类

这个可以说是很原始的方式,就是继承Thread类,重写run方法 。

package com.hzy;public class Main {public static void main(String[] args) {new MyThread().start();}
}
class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}

由于Java是单继承的,所以这种方法用的比较少,一般都是用Runnable接口

实现Runnable接口

这里给出几个常用的构造方法

先给出一个传统的方法实现

package com.hzy;public class Main {public static void main(String[] args) {new Thread(new MyThread()).start();new Thread(new MyThread(),"贺志营").start();}
}
class MyThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}

我们也可以通过匿名内部类实现

package com.hzy;public class Main {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}}).start();}
}

还可以通过Lamata表达式实现

package com.hzy;public class Main {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}).start();}
}

实现Callable接口

Callable接口可以接收返回值,可以抛出异常,重写的是call方法

package com.hzy;import java.util.concurrent.*;public class Main {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建执行服务ExecutorService service = Executors.newFixedThreadPool(3);// 提交执行Future<Boolean> future1 = service.submit(new MyThread());Future<Boolean> future2 = service.submit(new MyThread());Future<Boolean> future3 = service.submit(new MyThread());// 获取返回值Boolean b1 = future1.get();Boolean b2 = future2.get();Boolean b3 = future3.get();System.out.println(b1);System.out.println(b2);System.out.println(b3);// 关闭服务service.shutdown();}
}class MyThread implements Callable<Boolean> {@Overridepublic Boolean call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}return true;}
}

另外还可以通过FutureTask适配器创建

package com.hzy;import java.util.concurrent.*;public class Main {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建一个适配器FutureTask futureTask = new FutureTask(new MyThread());new Thread(futureTask,"A").start();Boolean o = (Boolean) futureTask.get();System.out.println(o);}
}class MyThread implements Callable<Boolean> {@Overridepublic Boolean call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}return true;}
}

线程池

线程的创建跟Callable差不多,也是用ExecutorService

package com.hzy;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Main {public static void main(String[] args){// 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。ExecutorService service1 = Executors.newCachedThreadPool();// 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。ExecutorService service2 = Executors.newFixedThreadPool(10);// 创建一个定长线程池,支持定时及周期性任务执行。ExecutorService service3 = Executors.newScheduledThreadPool(10);// 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。ExecutorService service4 = Executors.newSingleThreadExecutor();// 执行service1.execute(new MyThread());service2.execute(new MyThread());service3.execute(new MyThread());service4.execute(new MyThread());// 关闭连接service1.shutdown();service2.shutdown();service3.shutdown();service4.shutdown();}
}
class MyThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}

在阿里巴巴开发手册中有规定,创建线程池要用ThreadPoolExecutor

对于ThreadPoolExecutor的学习,就从七大参数四种拒绝策略

int corePoolSize// 核心线程池大小
int maximumPoolSize// 最大核心线程池大小
long keepAliveTime// 超时存活时间
TimeUnit unit//  超时单位
BlockingQueue<Runnable> workQueue// 阻塞队列
ThreadFactory threadFactory// 线程工厂,用于创建线程
RejectedExecutionHandler handler// 拒绝策略

AbortPolicy());// 银行满了还有人进来,不处理,抛出异常(默认)
CallerRunsPolicy();// 银行满了,不处理,哪里来的去哪里,一般抛给main线程
DiscardPolicy();// 银行满了,把该线程丢掉,不抛异常
DiscardOldestPolicy();// 银行满了,会和先来的线程竞争,不抛异常
package com.hzy;import java.util.concurrent.*;public class Main {public static void main(String[] args){/*** 用一个银行的例子进行讲解这七大参数*/ExecutorService threadPool = new ThreadPoolExecutor(2,// 两个常开营业窗口5,// 五个窗口,其中三个应急用3,// 超时存货时间TimeUnit.SECONDS,// 超时时间单位new LinkedBlockingDeque<>(3),// 银行候客区大小Executors.defaultThreadFactory(),// 默认线程池工厂new ThreadPoolExecutor.AbortPolicy());// 银行满了还有人进来,不处理,抛出异常(默认)// new ThreadPoolExecutor.CallerRunsPolicy();// 银行满了,不处理,哪里来的去哪里,一般抛给main线程// new ThreadPoolExecutor.DiscardPolicy();// 银行满了,把该线程丢掉,不抛异常// new ThreadPoolExecutor.DiscardOldestPolicy();// 银行满了,会和先来的线程竞争,不抛异常for (int i = 0; i < 8; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName());});}// 关闭连接threadPool.shutdown();}
}

在定义线程池最大大小的时候,一般有两种策略CPU密集型IO密集型,所谓CPU密集型,也就是,几核的CPU就定义为几,我的是八核,所以定义为8,Runtime.getRuntime().availableProcessors();// 获取CPU的核数,IO密集型,就是判断程序中有多少个非常耗IO线程的程序,最大线程池的大小要大于这个值即可。

线程的五大状态

创建状态所谓的创建状态,也就是我们的new Thread();就绪状态所谓的就绪状态,就是我们myThread.start();运行状态运行状态就是我们的代码执行。阻塞状态当线程等待(wait)、同步(synchronized)、sleep和join的时候死亡状态run()结束、main()结束线程常用方法

  • setPriority(int new Priority)更改线程的优先级
  • sleep(long millis)让当前线程进入休眠
  • join()相当于插队,插入的线程执行结束后,被插队线程继续执行。
  • yield()线程礼让,暂停当前的线程,并把该线程状态转化为就绪状态
  • interrupt()中断线程(不建议用)
  • isAlive()判断线程是否处于存活状态

多线程买票案例

当多个线程操作同一个资源的时候,就会出现线程安全问题。假设我们有100张票,在三个窗口同时卖,也就是我们有三个线程去买票。

package com.hzy;public class Main {public static void main(String[] args){MyThread myThread = new MyThread();new Thread(myThread,"窗口1").start();new Thread(myThread,"窗口2").start();new Thread(myThread,"窗口3").start();new Thread(myThread,"窗口4").start();new Thread(myThread,"窗口5").start();}
}class MyThread implements Runnable {private int ticket = 100;private boolean flag = true;@Overridepublic void run() {while (flag) {buy();}}public void buy() {if (ticket <= 0) {flag = false;return;}try {Thread.sleep(100);// 模拟买票延迟100ms} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":"+ ticket--);}
}

可以看出,出现了线程安全问题,原因是在对ticket变为0之前,有多个线程同时进来。解决办法,可以通过synchronized线程同步,可以使用同步方法和同步代码块进行解决。同步方法,也就是在方法上加一个synchronized关键字。

package com.hzy;public class Main {public static void main(String[] args){MyThread myThread = new MyThread();new Thread(myThread,"窗口1").start();new Thread(myThread,"窗口2").start();new Thread(myThread,"窗口3").start();}
}class MyThread implements Runnable {private int ticket = 100;private boolean flag = true;@Overridepublic void run() {while (flag) {buy();}}public synchronized void buy() {if (ticket <= 0) {flag = false;return;}try {Thread.sleep(100);// 模拟买票延迟100ms} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":"+ ticket--);}
}

同步代码块锁的是一个对象,即是需要变化的量

package com.hzy;public class Main {public static void main(String[] args){MyThread myThread = new MyThread();new Thread(myThread,"窗口1").start();new Thread(myThread,"窗口2").start();new Thread(myThread,"窗口3").start();}
}class MyTicket {int ticket;public MyTicket(int ticket) {this.ticket = ticket;}
}class MyThread implements Runnable {MyTicket myTicket = new MyTicket(100);private boolean flag = true;@Overridepublic void run() {while (flag) {buy();}}public void buy() {synchronized (myTicket) {if (myTicket.ticket <= 0) {flag = false;return;}try {Thread.sleep(100);// 模拟买票延迟100ms} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":"+ myTicket.ticket--);}}
}

死锁

所谓死锁,也就是A拥有A资源的同时想要B的资源,B拥有B资源的同时想要A的资源。

package com.hzy;public class Main {public static void main(String[] args){A a = new A();B b = new B();new Thread(new MyThread(a,b,0)).start();new Thread(new MyThread(a,b,1)).start();}
}
class A {}
class B {}class MyThread implements Runnable {private A a;private B b;private int choice;public MyThread(A a,B b,int choice) {this.a = a;this.b = b;this.choice = choice;}@Overridepublic void run() {if (choice == 0) {synchronized (a) {System.out.println(Thread.currentThread().getName() + "获得" + a);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (b) {System.out.println(Thread.currentThread().getName() + "获得" + b);}}}else {synchronized (b) {System.out.println(Thread.currentThread().getName() + "获得" + b);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (a) {System.out.println(Thread.currentThread().getName() + "获得" + a);}}}}
}

可以看出,A需要B资源,B需要A资源,出现了死锁的状态。

Lock锁

Lock是一个接口,而不是一个关键字,他是一个显示锁,只能锁同步代码块,不能锁方法,可以显示加锁,释放锁,可以指定唤醒某一个线程,Lock锁有一个实现类是ReentrantLock,可重入锁(递归锁),在这里说一下,所有的锁都是可重入锁,也就是如果我们获得了外面的锁之后,会自动获取里面的锁。

正常的,如果我们不加锁,会出现线程安全问题

package com.hzy;public class Main {public static void main(String[] args){MyThread myThread = new MyThread();new Thread(myThread).start();new Thread(myThread).start();new Thread(myThread).start();}
}
class MyThread implements Runnable {private int ticket = 100;@Overridepublic void run() {while (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticket --);}}
}

可以通过RenntrantLock进行加锁,显示锁,记得解锁。

package com.hzy;import java.util.concurrent.locks.ReentrantLock;public class Main {public static void main(String[] args){MyThread myThread = new MyThread();new Thread(myThread).start();new Thread(myThread).start();new Thread(myThread).start();}
}
class MyThread implements Runnable {private int ticket = 100;// 定义lock锁ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {lock.lock();if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticket --);}else {break;}lock.unlock();}}
}

比ReentrantLock更细的锁是,ReentrantReadWriteLock,这里有一个读锁,一个写锁,

package com.hzy;import java.util.HashMap;
import java.util.Map;public class Main {public static void main(String[] args) {MyReadWriteLock myReadWriteLock = new MyReadWriteLock();for (int i = 0; i < 5; i++) {int temp = i;new Thread(()->{myReadWriteLock.put(temp + "",temp + "");},temp + "").start();}for (int i = 0; i < 5; i++) {int temp = i;new Thread(()->{myReadWriteLock.get(temp + "");},temp + "").start();}}
}class MyReadWriteLock {private volatile Map<String,String> map = new HashMap<>();public void put(String key,String value) {System.out.println(Thread.currentThread().getName() + "写入" + key);map.put(key,value);System.out.println(Thread.currentThread().getName() + "写入完成");}public void get(String key) {System.out.println(Thread.currentThread().getName() + "读取" + key);map.get(key);System.out.println(Thread.currentThread().getName() + "读取完成");}
}

如果不加锁的话,在写入完成之前会被其他线程插入

而我们想要的是,在写入的时候,只能是一个线程,而读取的时候,可以是多个线程。

package com.hzy;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class Main {public static void main(String[] args) {MyReadWriteLock myReadWriteLock = new MyReadWriteLock();for (int i = 0; i < 5; i++) {int temp = i;new Thread(()->{myReadWriteLock.put(temp + "",temp + "");},temp + "").start();}for (int i = 0; i < 5; i++) {int temp = i;new Thread(()->{myReadWriteLock.get(temp + "");},temp + "").start();}}
}class MyReadWriteLock {private volatile Map<String,String> map = new HashMap<>();ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();public void put(String key,String value) {readWriteLock.writeLock().lock();System.out.println(Thread.currentThread().getName() + "写入" + key);map.put(key,value);System.out.println(Thread.currentThread().getName() + "写入完成");readWriteLock.writeLock().unlock();}public void get(String key) {readWriteLock.readLock().lock();System.out.println(Thread.currentThread().getName() + "读取" + key);map.get(key);System.out.println(Thread.currentThread().getName() + "读取完成");readWriteLock.readLock().unlock();}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344

可以看出,在写入的时候,是一个写入,一个写入完成,在读取的时候,可能会有多个读取。

synchronized和Lock的区别

  • synchronized是关键字,Lock是类
  • synchronized无法获取锁的状态,Lock可以
  • synchronized会自动释放锁,Lock需要手动
  • synchronized没有Lock锁灵活(Lock锁可以自己定制)
    只说理论是不行的,下面说几个ReentrantLock的场景
  • ReentrantLock默认是非公平锁,但它可以设置公平锁,也就是谁先来的谁先获得锁new ReentrantLock(true)。
  • ReentrantLock可以响应中断,也就是当两个线程发生死锁的时候,你把A线程中断了,B线程可以正常运行。
  • ReentrantLock可以通过tryLock()实现限时等待,这样可以解决死锁问题。
    synchronized
  • synchronized在JDK1.6进行了锁的优化,也就是当一个线程多次访问一个同步代码块的时候,此时会记录该线程的threadId也就是,当你再来访问的时候,我就只需判断threadId就行了,效率高,这属于偏向锁。
  • 当有多个线程来的时候,那么这个锁就会升级为轻量级锁,也就是通过CAS,来进行尝试获取锁,是一种自旋锁的状态。如果在短时间内可以获得锁,不会堵塞,而且节约了CUP上下文切换的时间。
  • 如果长时间没有获取到锁,会消耗CUP的资源,因为在那一直死循环,经过一个时间段后会升级为重量级锁,会发生阻塞。其中锁升级是不可逆的。

生产者消费者问题

这是一个经典的线程通信问题,也就是不同线程之间有联系,生产者生产的东西放到缓冲区,如果缓冲区满了,生产者进入堵塞,缓冲区空了,消费者堵塞。常用的方法有wait(),notify(),notifyAll()

package com.hzy;import java.util.concurrent.locks.ReentrantLock;public class Main {public static void main(String[] args){Buffer buffer = new Buffer();new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println("生产者生产了" + i);buffer.put();}},"生产者").start();new Thread(()->{for (int i = 0; i < 100; i++) {System.out.println("消费者消费了" + i);buffer.get();}},"消费者").start();}
}// 缓冲区
class Buffer {private int len = 0;public synchronized void put() {if (len < 10) {len ++;} else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}notifyAll();}public synchronized void get() {if (len > 0) {len --;} else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}notifyAll();}
}

学习了Lock锁,这里的生产者消费者问题,可以通过Lock锁实现,可以指定唤醒某个线程,常用的方法是await,signal。这里给出缓冲区

// 缓冲区
class Buffer {private int len = 0;Lock lock = new ReentrantLock();Condition condition = lock.newCondition();// 该对象可以设置一些条件public void put() {lock.lock();if (len < 10) {len ++;} else {try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}condition.signalAll();lock.unlock();}public void get() {lock.lock();if (len > 0) {len --;} else {try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}condition.signalAll();lock.unlock();}
}

其中这里引入了Condition,他可以指定唤醒某个线程,这里我们演示三个线程ABC。

package com.hzy;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Main {public static void main(String[] args){Buffer buffer = new Buffer();new Thread(()->{for (int i = 0; i < 10; i++) {buffer.a();}},"A").start();new Thread(()->{for (int i = 0; i < 10; i++) {buffer.b();}},"B").start();new Thread(()->{for (int i = 0; i < 10; i++) {buffer.c();}},"C").start();}
}// 缓冲区
class Buffer {private int flag = 1;// 1执行A,2执行B,3执行CLock lock = new ReentrantLock();Condition condition1 = lock.newCondition();// 该对象可以设置一些条件Condition condition2 = lock.newCondition();// 该对象可以设置一些条件Condition condition3 = lock.newCondition();// 该对象可以设置一些条件public void a() {lock.lock();try {while (flag != 1) {condition1.await();}System.out.println("A");flag = 2;condition2.signal();// 指定唤醒B} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void b() {lock.lock();try {while (flag != 2) {condition2.await();}System.out.println("B");flag = 3;condition3.signal();// 指定唤醒C} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void c() {lock.lock();try {while (flag != 3) {condition3.await();}System.out.println("C");flag = 1;condition1.signal();// 指定唤醒A} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}

结果都是ABC、ABC…

八锁问题

package com.hzy;
import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone = new Phone();new Thread(()->{phone.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone.call();}, "B").start();}
}class Phone {public synchronized void sendMsg() {System.out.println("sendMsg");}public synchronized void call() {System.out.println("call");}
}

先输出sendMsg,不要理解为是先调用了sendMsg,而是因为A线程先获得了锁。

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone = new Phone();new Thread(()->{phone.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone.call();}, "B").start();}
}class Phone {public synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}public synchronized void call() {System.out.println("call");}
}

这个例子,再次解释了是因为sendMsg先获得的锁,这里的synchronized锁的对象是调用者,这两个方法用的是同一把锁,谁先拿到,谁先执行

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone = new Phone();new Thread(()->{phone.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone.call();}, "B").start();}
}class Phone {public synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}//    public synchronized void call() {
//        System.out.println("call");
//    }public void call() {System.out.println("call");}
}

这里把call方法的synchronized去掉了,会先输出哪个呢,答案是先输出call,因为他不去获取锁资源,所以直接输出了

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone1 = new Phone();Phone phone2 = new Phone();new Thread(()->{phone1.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone2.call();}, "B").start();}
}class Phone {public synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}public synchronized void call() {System.out.println("call");}
}

这里用两个phone对象,分别调用sendMsg和call方法,会先执行哪个,答案是先执行call,因为这里的锁锁的是对象,而他们不是同一个对象,所以资源不受影响。

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone = new Phone();new Thread(()->{phone.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone.call();}, "B").start();}
}class Phone {public static synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}public static synchronized void call() {System.out.println("call");}
}

这里是通过一个对象,去调用静态的同步方法,看看是先sendMsg还是call,答案是sentMsg,因为这里锁的是Class对象(只有一个)

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone1 = new Phone();Phone phone2 = new Phone();new Thread(()->{phone1.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone2.call();}, "B").start();}
}class Phone {public static synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}public static synchronized void call() {System.out.println("call");}
}

这里是通过两个对象去调用sentMsg和call,结果是什么呢,答案还是先执行sentMsg,因为锁的是Class对象,所以不管是phone1还是phone2,都是属于Phone的Class对象。

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone = new Phone();new Thread(()->{phone.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone.call();}, "B").start();}
}class Phone {public static synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}public synchronized void call() {System.out.println("call");}
}

这里锁的一个是静态同步方法,一个是普通同步方法,用一个对象对调用,结果是什么呢,答案是先call,因为静态同步方法锁的是Class对象,而普通同步方法锁的是调用者,锁的不是同一个东西。

package com.hzy;import java.util.concurrent.TimeUnit;public class Main {public static void main(String[] args){Phone phone1 = new Phone();Phone phone2 = new Phone();new Thread(()->{phone1.sendMsg();}, "A").start();try {TimeUnit.SECONDS.sleep(1);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone2.call();}, "B").start();}
}class Phone {public static synchronized void sendMsg() {try {TimeUnit.SECONDS.sleep(3);// 休息一秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("sendMsg");}public synchronized void call() {System.out.println("call");}
}

这里用两个对象去调用静态同步方法sendMsg和普通同步方法call,会先输出哪个,答案是call,因为sendMsg锁的是Class,而call锁的是调用者,不是同一个一个东西

volatile

说起volatile,不难想出三大特性保证可见性不保证原子性禁止指令重排。在讲解volatile之前,需要讲解一个东西,Java内存模型(Java Memory Model,JMM),当线程读取内存中的一个变量时,会先把这个变量拷贝到CPU的高速缓存区,然后对其进行操作,操作完成后,会把该变量写入到内存中。在单线程中是不会出现任何问题的,但是在多线程中就会有问题,当线程1读取了该变量a=1到缓存区进行了加1操作,还没写到内存中,线程2读取了内存中的变量a=1也进行加1操作,然后线程1写入内存a=2,线程2也写入a=2到内存,那么最后,该变量的值是2,而不是3(出现了线程安全问题)。我们想要当线程1进行了加1操作之后,让线程2知道,这就是volatile的作用了,可以保证可见性,也就是,当线程1对a变量进行了加1操作,会直接写入到内存中(立即马上),并且通知线程2,变量被修改了,要求线程2缓冲区的值去内存中重新读取。但是,加1操作不是原子性的(三步,首先读取a变量的值,然后对其进行加1操作,然后赋值给a),也就是说,当线程1读取a变量到缓冲区后,还没有修改a的值,此时线程2进来了,读取了a的值,并且对其进行了加1操作,由于可见性,会把线程1缓冲区的值进行修改,但是,线程1中的CPU已经读取了缓冲区的值,而且是更新前的值,所以出现了线程安全问题,也是volatile不保证原子性的问题。于是就需要加1操作是原子性操作,于是就有了一个automic包,通过该包下的方法,可以实现加1的原子性操作(还有其他原子性操作)。

在原子操作里的自增,其实用的是自旋锁,也就是一直进行比较并交换。

说起原子性操作,其实得聊聊CAS(Compare And Swap,比较并交换),如果我们想要修改某个值num,那么我们可以通过一个方法compareAndSet(5,6)意思是,我们期望num的值是5,如果是,就修改为6。但是这就会有一个问题,我们期望的num是5,如果有其他线程把5修改为了8,然后又修改为了5,最终是5,但是已经被修改过一次了,这就是ABA问题。我们可以通过AtomicStampedReference,也就是原子引用,在创建的时候,有一个印记,相当于版本号,每被修改一次,版本号都被更新,所以,当出现ABA问题的时候,我们就可以清楚的知道,被修改了多少次。

还有一个特性就是禁止指令重排,既然要禁止他,那么就得知道什么是指令重排,所谓指令重排,也就是,为了提高程序执行效率,寄存器对代码的优化,如果我们有一段代码如下

a = 1;// 语句1
b = 2;// 语句2
a = a + 1;// 语句3
b = a + 1;// 语句4

所谓指令重排,也就是在不影响单线程程序程序结果的情况下进行最优执行排序,可以看出,语句1和语句2的执行顺序并不会影响程序的结果,最终a的值为2,b的值为3。下面看这个代码Instance instance = new Instance();从微观的角度看,这条语句可以分解为三步,一是分配内存空间,二是初始化对象三是指向该内存空间,其中二和三会发生指令重排,如果在多线程的情况下,线程A分配了内存空间,并且执行了该内存空间(没有初始化对象),线程B进行了访问该内存空间,这就会出错了。而volatile就是禁止指令重排的,也并非绝对的禁止,假设我们有1、2、3、4、5条语句

a = 1;
b = 2;
c = 3;
d = 4;
e = 5;

如果我们对c操作进行了volatile修饰,那么a和b依然可以指令重排,d和e也可以指令重排,ab和de不能指令重排了,c把他们分开了(专业术语是给c加了个内存屏障)。


http://chatgpt.dhexx.cn/article/2cyVYQpV.shtml

相关文章

我用Java能干什么?

Java是一门面向对象编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实现了面向对象理论&#xff0c;…

什么是JAVA?JAVA能做什么?

什么是Java&#xff1f; Java既是一种编程语言&#xff0c;又是一个平台。 Java程序语言 Java是具有以下特征的高级程序语言&#xff1a; 简单   面向对象   可分布   可解释   强壮   安全性   结构化   轻便   功能强大   多线程   动态 Java既可以被编译…

Java是什么?主要是干什么的?

随着Java技术不断发展&#xff0c;许多人都想问&#xff1a;Java是什么?主要是干什么的呀&#xff1f;现在小朗来为大家解惑。java是一种高级计算机语言&#xff0c;一种可以编写跨平台应用软件、完全面向对象的程序设计语言。那Java主要是干嘛的呀&#xff1f; 一、java可以做…

学java可以从事什么工作-java岗位有哪些?

学java可以从事的工作主要有&#xff1a;企业级应用开发、网站开发、软件开发、嵌入式领域、大数据、科学应用等。java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。 1、企业级应用开发 企业级应用开发大可以做全国联网的系统&#xff0c;小到中小企…

Java到底能做什么事情呢?

相信很多小伙伴看到标题第一个出现在脑海里的想法是赚钱,难道你们只知道java薪资高?那么你太low,Java的应用领域很广,可以说是现在最普及的,遍布各行各业,可见其优势所在。 1、大数据领域 Hadoop以及其他大数据处理技术普遍用的都是Java,当然其他语言也有用到,基于Jav…

Java编程到底能干什么,可以从事哪些岗位?

听说程序员工资很高&#xff0c;想学Java语言&#xff0c;但不知道它到底能做什么&#xff1f;” 如果你是一个Java初学者&#xff0c;你可能对Java应用在什么地方感到困惑。除了“马里奥”“贪吃蛇”等经典游戏&#xff0c;其他领域好像也找不到Java的踪迹&#xff0c;那么Jav…

Java是用来干什么的?

Java是用来干什么的&#xff1f;这个问题首先我们得知道这个“Java”是个什么东西&#xff1f; Java是什么 有问题“百度一下&#xff0c;你就知道”&#xff0c;输入“Java”关键词&#xff0c;我们会看见“百度百科”对Java较官方的解释&#xff0c;看就是下图&#xff1a; …

Java是什么?Java能干嘛?

我刚开始学习Java的时候&#xff0c;很长一段时间都有这么个疑惑&#xff1f; Java到底是啥&#xff1f;它能干什么&#xff1f; 自己也看过不少的课程和书&#xff0c;大部分都是从Java的发展史开始讲&#xff0c;总之就是那些什么Java历史悠久&#xff0c;Java很优秀&#x…

Java能做什么?学完Java可以从事什么工作呢?

如果你是一个Java初学者&#xff0c;你可能对Java应用在什么地方感到困惑。除了“马里奥”“贪吃蛇”等经典游戏&#xff0c;其他领域好像也找不到Java的踪迹&#xff0c;那么Java究竟能做什么&#xff1f;学完Java可以从事什么工作呢&#xff1f;本文&#xff0c;小千就来详细…

什么是JAVA?JAVA能用来干什么?

我刚开始学习Java的时候&#xff0c;很长一段时间都有这么个疑惑&#xff1f; Java到底是啥&#xff1f;它能干什么&#xff1f; 自己也看过不少的课程和书&#xff0c;大部分都是从Java的发展史开始讲&#xff0c;总之就是那些什么Java历史悠久&#xff0c;Java很优秀&#x…

Java具体可以做什么?

Java具体可以做什么&#xff1f;一起来看看吧。 1、Java可以用来做网站 Java可以用来做网站&#xff0c;很多大型网站都是用JSP写的&#xff0c;JSP全名JavaServerPages。这是一种动态网页技术&#xff0c;比如我们熟悉的B站&#xff0c;很多政府网站都是用这个写的所以想学习…

Java到底能干什么?有哪些实际用途?

Java 是一种跨平台的、面向对象的高级编程语言&#xff0c;主要用来进行网站后台开发和 Android APP 开发。Java 是全球最受欢迎的编程语言之一&#xff0c;在世界编程语言排行榜 TIOBE 中&#xff0c;Java 一直霸占着前三名&#xff0c;有好多年甚至都是第一名。 JetBrains 每…

Java是什么?Java到底能干嘛?

我刚开始学习Java的时候&#xff0c;很长一段时间都有这么个疑惑&#xff1f; Java到底是啥&#xff1f;它能干什么&#xff1f; 自己也看过不少的课程和书&#xff0c;大部分都是从Java的发展史开始讲&#xff0c;总之就是那些什么Java历史悠久&#xff0c;Java很优秀&#xf…

学习java技术能干什么工作

Java岗位的工作有&#xff1a; Java工程师的职位包括&#xff1a;手机软件开发&#xff0c;游戏开发&#xff0c;网站开发、技术支持、项目经理、产品销售、架构师、系统分析等。 真正的JaVa工程师应具备adit&#xff0c;即analysis(分析问题的能力)、规划设计解决问题方案的…

学习Java开发可以就业哪些岗位和领域?

学好Java,能达到职业能力,有7大就业方向可以选择: 1、企业级应用开发 企业级应用开发大可以做全国联网的系统&#xff0c;小到中小企业的应用解决方案。多数没有前端开发的通 常是从一个服务器接收数据&#xff0c;处理后发给另一个处理系统。 如今&#xff0c;Java编程已经在…

Python 矩阵基本运算【numpy】

文章目录 一、实验说明二、Python 矩阵基本运算1. python矩阵操作2. python矩阵乘法3. python矩阵转置4. python求方阵的迹5. python方阵的行列式计算方法6. python求逆矩阵/伴随矩阵7. python解多元一次方程 一、实验说明 实验环境 Anaconda python3.6 jupyter 二、Python 矩…

机器学习之python矩阵运算

python矩阵基本运算 一、python矩阵操作二、python矩阵乘法三、python矩阵转置四、python求方阵的迹五、python方阵的行列式计算方法六、python求逆矩阵/伴随矩阵七、python解多元一次方程 一、python矩阵操作 1、引入numpy简写为np 2、使用mat函数创建一个2*3的矩阵 3、使用s…

python矩阵乘法运算

一、矩阵乘法 矩阵乘法为 AB 或 np.dot(A,B) &#xff0c;若为对应元素相乘则用 A*B 或 np.multiply(A,B) 。 1. AB 和 np.dot(A,B) A np.array([[1,2],[3,4] ])B np.array([[1,2],[3,4] ])C1 A B C2 np.dot(A,B) print(C1) print(---------) print(C2)输出为 [[ 7 10…

Python矩阵基本运算

文章目录 一、python矩阵操作二、python 矩阵乘法三、python 矩阵转置四、python 求方阵的迹五、python 方阵的行列式计算方法六、python 求逆矩阵 / 伴随矩阵七、python 解多元一次方程 一、python矩阵操作 引入numpy&#xff0c; 使用mat函数创建一个2X3矩阵 #引入numpy im…

python未知数的矩阵运算,机器学习的数学 之python矩阵运算

本文提纲 1. 什么是矩阵 2. 矩阵在现实应用场景 3. 矩阵表示 4. 矩阵运算 5. 理解矩阵乘法 一、 什么是矩阵 一个 m n 的矩阵是一个由 m 行 n 列元素排列成的矩形阵列。以下是一个由 6 个数字元素构成的 2 行 3 列的矩阵: 矩阵属于线性代数数学分支。线性代数是关于向量空间和…