网站建设a云世家宋南南,创业做什么好,百度域名解析,怎么建设淘客自己的网站、Day30
1.作业
1.1 线程类解决 1.计算任务#xff0c;一个包含了2万个整数的数组#xff0c;分拆了多个线程来进行并行计算#xff0c;最后汇总出计算的结果。 注意#xff1a;使用线程类的方式解决该需求 public class Test01 {public static void main(String[] args) t…Day30
1.作业
1.1 线程类解决 1.计算任务一个包含了2万个整数的数组分拆了多个线程来进行并行计算最后汇总出计算的结果。 注意使用线程类的方式解决该需求 public class Test01 {public static void main(String[] args) throws InterruptedException {//创建数组int[] arr new int[20000];//初始化数组数据 -- {1,2,3,....,20000}for (int i 0; i arr.length; i) {arr[i] i1;}//创建线程MyThread t1 new MyThread(0, 5000, arr);MyThread t2 new MyThread(5000, 10000, arr);MyThread t3 new MyThread(10000, 15000, arr);MyThread t4 new MyThread(15000, 20000, arr);//启动线程t1.start();t2.start();t3.start();t4.start();//问题出现原因四个子线程还没有运行完毕就被主线程抢到CPU资源了//解决思路让四个子线程执行完毕后主线程才能执行 --- 主线程阻塞//解决方案一
// Thread.sleep(6);//解决方案二
// while(t1.isFlag() || t2.isFlag() || t3.isFlag() || t4.isFlag()){}//解决方案三t1.join();t2.join();t3.join();t4.join();//合并子线程的运行结果int result t1.getSum() t2.getSum() t3.getSum() t4.getSum();System.out.println(result);}
}public class MyThread extends Thread{private int startIndex;private int endIndex;private int[] arr;public MyThread(int startIndex, int endIndex, int[] arr) {this.startIndex startIndex;this.endIndex endIndex;this.arr arr;}private int sum;private boolean flag true;Overridepublic void run() {for (int i startIndex; i endIndex; i) {sum arr[i];}flag false;}public int getSum() {return sum;}public boolean isFlag() {return flag;}
}1.2 任务类解决 注意使用任务类的方式解决该需求 public class Test01 {public static void main(String[] args) throws InterruptedException {//创建数组int[] arr new int[20000];//初始化数组数据 -- {1,2,3,....,20000}for (int i 0; i arr.length; i) {arr[i] i1;}//创建任务Task task1 new Task(0, 5000, arr);Task task2 new Task(5000, 10000, arr);Task task3 new Task(10000, 15000, arr);Task task4 new Task(15000, 20000, arr);//创建线程Thread t1 new Thread(task1);Thread t2 new Thread(task2);Thread t3 new Thread(task3);Thread t4 new Thread(task4);//启动线程t1.start();t2.start();t3.start();t4.start();//问题出现原因四个子线程还没有运行完毕就被主线程抢到CPU资源了//解决思路让四个子线程执行完毕后主线程才能执行 --- 主线程阻塞//解决方案一
// Thread.sleep(6);//解决方案二
// while(task1.isFlag() || task2.isFlag() || task3.isFlag() || task4.isFlag()){}//解决方案三t1.join();t2.join();t3.join();t4.join();//合并任务的运行结果int result task1.getSum() task2.getSum() task3.getSum() task4.getSum();System.out.println(result);}
}public class Task implements Runnable{private int startIndex;private int endIndex;private int[] arr;public Task(int startIndex, int endIndex, int[] arr) {this.startIndex startIndex;this.endIndex endIndex;this.arr arr;}private int sum;private boolean flag true;Overridepublic void run() {for (int i startIndex; i endIndex; i) {sum arr[i];}flag false;}public int getSum() {return sum;}public boolean isFlag() {return flag;}}1.线程安全
1.1 线程类解决
1.1.1 同步代码块 线程安全 – 加锁 注意要想多个线程互斥住就必须使用同一把锁(对象) 重要 synchronized 同步代码块 synchronized(锁对象){//自动上锁 …想要互斥的代码… }//自动解锁 Lock 需求铁道部发布了一个售票任务要求销售1000张票要求有3个窗口来进行销售请编写多线程程序来模拟这个效果 窗口001正在销售第1张票 窗口001正在销售第2张票 窗口002正在销售第3张票 。。。 窗口002正在销售第1000张票 窗口002票已售完 窗口001票已售完 窗口003票已售完 注意使用线程类的方式解决该需求 问题一三个窗口各卖1000张票一共卖了3000张 出现原因allTicket和curTicket是run方法的局部变量三个线程抢到CPU资源后都会调用run方法run方法被调用了3次所以卖了3000张票 解决方案将allTicket和curTicket设置为静态变量让三个线程共享 问题二有些票没有卖有些票卖了重票 出现原因当前线程抢到CPU资源后做了票的自增但是还没来得及输出时间片到了就退出CPU资源然后其他线程抢到CPU资源了 解决方案当前线程抢到CPU资源后票的自增和输出执行完毕才能切换到其他线程运行 – 加锁 问题三多卖了票 出现原因curTicket到了临界点999三个线程都可以进判断然后上锁 解决方案在锁中再次判断 注意锁对象使用static非常不好 public class Test01 {public static void main(String[] args) {MyThread t1 new MyThread(001);MyThread t2 new MyThread(002);MyThread t3 new MyThread(003);t1.start();t2.start();t3.start();}
}public class MyThread extends Thread{private static int allTicket 1000;private static int curTicket 0;private static Object obj new Object();public MyThread(String name) {super(name);}Overridepublic void run() {while(curTicket allTicket){//都是锁对象//synchronized (String.class) {//synchronized (abc) {synchronized (obj) {if(curTicket allTicket){curTicket;System.out.println(窗口 Thread.currentThread().getName() 正在销售第 curTicket 张票);}if(curTicket allTicket){System.out.println(窗口 Thread.currentThread().getName() 票已经售完);}}}}
}同步代码块解决数据安全问题【应用】 安全问题出现的条件 - 是多线程环境 - 有共享数据 - 有多条语句操作共享数据 如何解决多线程安全问题呢? - 基本思想让程序没有安全问题的环境 怎么实现呢? - 把多条语句操作共享数据的代码给锁起来让任意时刻只能有一个线程执行即可 - Java提供了同步代码块的方式来解决 同步代码块格式 synchronized(任意对象) { 多条语句操作共享数据的代码 } synchronized(任意对象)就相当于给代码加锁了任意对象就可以看成是一把锁 同步的好处和弊端 - 好处解决了多线程的数据安全问题 - 弊端当线程很多时因为每个线程都会去判断同步上的锁这是很耗费资源的无形中会降低程序的运行效率 1.1.2 同步方法 synchronized 同步代码块 synchronized(锁对象){//自动上锁 …想要互斥的代码… }//自动解锁 同步方法 同步方法 – 成员同步方法 注意锁对象 - this public synchronized void method(){//自动上锁 …想要互斥的代码… }//自动解锁 同步方法 – 静态同步方法 注意锁对象 - 类.class public static synchronized void method(){//自动上锁 …想要互斥的代码… }//自动解锁 public class Test01 {public static void main(String[] args) {MyThread t1 new MyThread(001);MyThread t2 new MyThread(002);MyThread t3 new MyThread(003);t1.start();t2.start();t3.start();}
}
public class MyThread extends Thread{private static int allTicket 1000;private static int curTicket 0;public MyThread(String name) {super(name);}Overridepublic void run() {while(curTicket allTicket){method();}}//一定是要加static因为不加锁对象是this锁不住//锁对象MyThread.classpublic static synchronized void method(){if(curTicket allTicket){curTicket;System.out.println(窗口 Thread.currentThread().getName() 正在销售第 curTicket 张票);}if(curTicket allTicket){System.out.println(窗口 Thread.currentThread().getName() 票已经售完);}}}
同步方法解决数据安全问题【应用】 同步方法的格式 同步方法就是把synchronized关键字加到方法上 修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体 } 同步方法的锁对象是什么呢? this 静态同步方法 同步静态方法就是把synchronized关键字加到静态方法上 修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体 } 1.1.3 Lock锁 Lock //锁对象 Lock lock new ReentrantLock(); //要加 try…catch… lock.lock();//手动上锁 写在 synchronized的位置代替 …想要互斥的代码… lock.unlock();//手动解锁 public class Test01 {public static void main(String[] args) {MyThread t1 new MyThread(001);MyThread t2 new MyThread(002);MyThread t3 new MyThread(003);t1.start();t2.start();t3.start();}
}public class MyThread extends Thread{private static int allTicket 1000;private static int curTicket 0;//必须是静态的因为有三个对象让线程所有对象都共享private static Lock lock new ReentrantLock();public MyThread(String name) {super(name);}Overridepublic void run() {while(curTicket allTicket){lock.lock();//手动上锁try {if(curTicket allTicket){curTicket;System.out.println(窗口 Thread.currentThread().getName() 正在销售第 curTicket 张票);}if(curTicket allTicket){System.out.println(窗口 Thread.currentThread().getName() 票已经售完);}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();//手动解锁}}}}Lock是接口不能直接实例化这里采用它的实现类ReentrantLock来实例化 ReentrantLock构造方法 ReentrantLock()创建一个ReentrantLock的实例 加锁解锁方法 lock()获得锁 unlock()释放锁 1.2 任务类解决
1.2.1 同步代码块 需求铁道部发布了一个售票任务要求销售1000张票要求有3个窗口来进行销售请编写多线程程序来模拟这个效果 窗口001正在销售第1张票 窗口001正在销售第2张票 窗口002正在销售第3张票 。。。 窗口002正在销售第1000张票 窗口002票已售完 窗口001票已售完 窗口003票已售完 注意使用任务类的方式解决该需求 (与上面的不一样) 问题一三个窗口各卖1000张票一共卖了3000张 出现原因三个线程抢到CPU资源后都会调用run方法curTicket和allTicket都是run方法里的局部变量所以会调用3次 解决思路curTicket和allTicket设置为成员属性三个线程共用同一个任务 问题二有些票没有卖有些票卖了重票 出现原因当前线程抢到CPU资源后做了票的自增但是还没来得及输出时间片到了就退出CPU资源然后其他线程抢到CPU资源了 解决方案当前线程抢到CPU资源后票的自增和输出执行完毕才能切换到其他线程运行 – 加锁 问题三多卖了票 出现原因curTicket到了临界点999三个线程都可以进判断然后上锁 解决方案在锁中再次判断 public class Test01 {public static void main(String[] args) {Task task new Task();Thread t1 new Thread(task, 001);Thread t2 new Thread(task, 002);Thread t3 new Thread(task, 003);t1.start();t2.start();t3.start();}
}public class Task implements Runnable{private int allTicket 1000;private int curTicket 0;Overridepublic void run() {while(curTicket allTicket){synchronized (this) {//使用this,不用objif(curTicket allTicket){curTicket;System.out.println(窗口 Thread.currentThread().getName() 正在销售第 curTicket 张票);}}}System.out.println(窗口 Thread.currentThread().getName() 票已售完);}}1.2.2 同步方法
public class Test01 {public static void main(String[] args) {Task task new Task();Thread t1 new Thread(task, 001);Thread t2 new Thread(task, 002);Thread t3 new Thread(task, 003);t1.start();t2.start();t3.start();}
}public class Task implements Runnable{private int allTicket 1000;private int curTicket 0;Overridepublic void run() {while(curTicket allTicket){method();}System.out.println(窗口 Thread.currentThread().getName() 票已售完);}//用成员同步方法public synchronized void method(){if(curTicket allTicket){curTicket;System.out.println(窗口 Thread.currentThread().getName() 正在销售第 curTicket 张票);}}}1.2.3 Lock锁 Lock锁要加try…catch public class Test01 {public static void main(String[] args) {Task task new Task();Thread t1 new Thread(task, 001);Thread t2 new Thread(task, 002);Thread t3 new Thread(task, 003);t1.start();t2.start();t3.start();}
}public class Task implements Runnable{private int allTicket 1000;private int curTicket 0;private Lock lock new ReentrantLock();Overridepublic void run() {while(curTicket allTicket){lock.lock();try {if(curTicket allTicket){curTicket;System.out.println(窗口 Thread.currentThread().getName() 正在销售第 curTicket 张票);}} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}System.out.println(窗口 Thread.currentThread().getName() 票已售完);}}注意一般解决需求使用任务类更灵活 简答题 关于 Java多线程在面试的时候问的比较多的就是①悲观锁和乐观锁 具体可以看我的这篇文章面试必备之乐 观锁与悲观锁、②synchronized和lock区别以及volatile和synchronized的区别**③可重入锁与非可重入锁的区 别、④多线程是解决什么问题的、⑤线程池解决什么问题、⑥线程池的原理、**⑦线程池使用时的注意事项、⑧AQS原理、⑨ReentranLock源码设计原理整体过程等等问题。 面试官在多线程这一部分很可能会问你有没有在项目中实际使用多线程的经历。所以如果你在你的项目中有实际使用Java多线程的经历 的话会为你加分不少哦 1.什么是线程安全 线程安全就是说多线程访问同一代码不会产生不确定的结果如果你的代码所在的进程中有多个线程在同时运行而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的而且其他的变量 的值也和预期的是一样的就是线程安全的。 在多线程环境中当各线程不共享数据的时候即都是私有private成员那么一定是线程安全的但这种情况并不多见在多数情况下需要共享数据这时就需要进行适当的同步控制了。 线程安全一般都涉及到synchronized 就是一段代码同时只能有一个线程来操作不然中间过程可能会产生不可预制的结果。 2…说一说自己对于synchronized关键字的了解 synchronized关键字解决的是多个线程之间访问资源的同步性synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 另外在 Java 早期版本中synchronized属于重量级锁效率低下因为监视器锁monitor是依赖于底层的操作系统的 Mutex Lock 来实现的Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程都需要操作系统帮忙完成而操作系统实现线程之间的切换时需要从用户态 转换到内核态这个状态之间的转换需要相对比较长的时间时间成本相对较高这也是为什么早期的synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优 化所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 3…说说自己是怎么使用synchronized关键字在项目中用到了吗 synchronized关键字最主要的三种使用方式 修饰实例方法: 作用于当前对象实例加锁进入同步代码前要获得当前对象实例的锁 修饰静态方法: 也就是给当前类加锁会作用于类的所有对象实例因为静态成员不属于任何一个实例对象是类成员 static 表明这是该类的一个静态资源不管new了多少个对象只有一份。所以如 果一个线程A调用一个实例对象的非静态 synchronized 方法而线程B需要调用这个实例对象所属类的 静态 synchronized 方法是许的不会发生互斥现象因为访问静态 synchronized 方法占用的锁 是当前类的锁而访问非静态 synchronized 方法占用的锁是当前实例对象锁。 修饰代码块: 指定加锁对象对给定对象加锁进入同步代码库前要获得给定对象的锁。 总结 synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因 为JVM中字符串常量池具有缓存功能 下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 面试中面试官经常会说“单例模式了解吗来给我手写一下给我解释一下双重检验锁方式实现单例模式的原理呗” 双重校验锁实现对象单例线程安全 public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过没有实例化过才进入加锁代码
if (uniqueInstance null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance null) {
uniqueInstance new Singleton();
}
}
}
return uniqueInstance;
}
}另外需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 uniqueInstance 采用 volatile 关键字修饰也是很有必要的 uniqueInstance new Singleton(); 这段代码其实是分为三步执行 1.为 uniqueInstance 分配内存空间 2.初始化 uniqueInstance 3.将 uniqueInstance 指向分配的内存地址 但是由于 JVM 具有指令重排的特性执行顺序有可能变成 1-3-2。指令重排在单线程环境下不会出先问题但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如线程 T1 执行了 1 和 3 此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空因此返回 uniqueInstance但此时 uniqueInstance 还未被初始化。 使用 volatile 可以禁止 JVM 的指令重排保证在多线程环境下也能正常运行。 4.讲一下 synchronized*关键字的底层原理 synchronized 关键字底层原理属于 JVM 层面。 ① synchronized 同步语句块的情况 public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println(synchronized 代码块);
}
}
}在cmd控制台通过 javap 命令查看 SynchronizedDemo 类的相关字节码信息 synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令其中 monitorenter **指令指向同步代码块的开始位置**monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中synchronized 锁便是通过这种方式获取锁的也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后将锁计数器设为0表明锁被释放。如果获取对象锁失败那当前线程就要阻塞等 待直到锁被另外一个线程释放为止。 Java中synchronized和ReentrantLock有什么不同 相似点 这两种同步方式有很多相似之处它们都是加锁方式同步而且都是阻塞式的同步也就是说当如果一 个线程获得了对象锁进入了同步块其他访问该同步块的线程都必须阻塞在同步块外面等待而进行线程阻塞和唤醒的代价是比较高的. 区别 这两种方式最大区别就是对于Synchronized来说它是java语言的关键字是原生语法层面的互斥需 要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁需要lock()和unlock()方法配合try/finally语句块来完成。 Synchronized进过编译会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时首先要尝试获取对象锁。如果这个对象没被锁定或者当前线程已经拥有了那个对象锁把锁的计算器加1相应的在执行monitorexit指令时会将锁计算器就减1当计算器为0时锁就被释放了。如果获取对象锁失败那当前线程就要阻塞直到对象锁被另一个线程释放为止。 由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁相比Synchronized ReentrantLock类提供了一些高级功能主要有以下3项 1.等待可中断持有锁的线程长期不释放的时候正在等待的线程可以选择放弃等待这相当于Synchronized来说可以避免出现死锁的情况。 2.公平锁多个线程等待同一个锁时必须按照申请锁的时间顺序获得锁Synchronized锁非公平锁ReentrantLock默认的构造函数是创建的非公平锁可以通过参数true设为公平锁但公平锁表现的性能不是很好。 3.锁绑定多个条件一个ReentrantLock对象可以同时绑定多个对象。 谈谈synchronized和ReenTrantLock的区别 ① 两者都是可重入锁 两者都是可重入锁。“可重入锁”概念是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁此时这个对象锁还没有释放当其再次想要获取这个对象的锁的时候还是可以获取的如果不可锁重入的话就会造成死锁。同一个线程每次获取锁锁的计数器都自增1所以要等到锁的计数器下降为0时才能释放锁。 ② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API synchronized 是依赖于 JVM 实现的前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化但是这些优化都是在虚拟机层面实现的并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的也就是 API 层面需要 lock() 和 unlock 方法配合 try/finally 语句块来完成所以我们可以通过查看它的源代码来看它是如何实现的。 ③ ReenTrantLock 比 synchronized 增加了一些高级功能 相比synchronizedReenTrantLock增加了一些高级功能。主要来说主要有三点①等待可中断②可实现公平锁 ③可实现选择性通知锁可以绑定多个条件 ReenTrantLock提供了一种能够中断等待锁的线程的机制通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待改为处理其他事情。 ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReenTrantLock默认情况是非公平的可以通过 ReenTrantLock类的ReentrantLock(boolean fair) 构造方法来制定是否是公平的。 synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制ReentrantLock类当然也可以实现但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的它具有很好的灵活性比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例即对象监视 器线程对象可以注册在指定的Condition中从而可以有选择性的进行线程通知在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时被通知的线程是由JVM选择的用ReentrantLock类结合Condition实例可以实现“选择性通知” 这个功能非常重要而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例所有的线程都注册在它一个身上。如果 执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题而Condition实例的 signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。如果你想使用上述功能那么选择ReenTrantLock是一个不错的选择。 ④ 性能已不是选择标准 总结 1.评讲作业 2.买票的案例 – 线程安全 重要 1.完全掌握卖票案例 2.理解对象内存结构 - https://blog.csdn.net/weixin_44606481/article/details/134802419 3.线程安全 — 买票案例 synchronized代码块 synchronized方法成员同步方法、静态同步方法 Lock锁 注意 1.加锁的方式 2.锁对象多个线程去操作同一把锁才能互斥住