一、线程安全
二、多线程并发的安全问题
当多个线程并发操作同一临界资源 由于线程切换实际不确定 导致操作顺序出现混乱
产生的程序bug 严重时出现系统瘫痪
临界资源 :操作该资源的完整流程同一时间只能被单一线程操作的资源
多线程并发会出现的各种问题、
如图会出现的各种异常问题
1. 数组长度不够
2. 数组下标越界
3. 0元素过多两个线程可能会同时读取和修改c.array的长度,从而导致数组长度不一致,进而导致越界异常。在第一个线程使用Arrays.copyOf方法增加数组长度时,第二个线程可能会在此同时读取数组长度,然后在第一个线程修改完成之前,第二个线程又对数组进行了操作,从而导致长度不一致,然后就会出现越界异常。
public class Test {public static void main(String[] args) throws InterruptedException {Coo c = new Coo();Thread t1 = new Thread() {@Overridepublic void run() {synchronized (c){// System.out.println("this1:"+this);for (int i = 0; i < 100; i++) {c.array = Arrays.copyOf(c.array, c.array.length + 1);c.array[c.array.length-1] = i;}}}};Thread t2 = new Thread() {@Overridepublic void run() {synchronized (c){// System.out.println("this:"+this);for (int i = 100; i < 200; i++) {c.array = Arrays.copyOf(c.array, c.array.length + 1);c.array[c.array.length-1] = i;}}}};t1.start();t2.start();/*** 多执行几次,检查程序可能存在的问题,并尝试分析为什么会出现这些情况,以及解决方案** 1. 数组长度不够* 2. 数组下标越界* 3. 0元素过多*///Thread.sleep(1000); //阻塞1秒钟,等待上面两个线程干完活再输出t1.join();t2.join();System.out.println(c.array.length);System.out.println(Arrays.toString(c.array));} }
当一个方法使用synchronized修饰 ,这个方法称之为同步方法,多个线程不能同时在方法内部执行
同步与异步的区别 总结来说,同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作
同步代码块使用的时候需要注意的是同步监视器对象的选择(重点)
1:对象可以是引用数据类型的对象
2:必须保证多个线程看到的对象是同一个
3:在方法上使用synchronized ,那么同步监视器对象就是this,不能自行指定,是默认的
4: 静态方法上若使用synchronized,则该方法一定具有同步效果
5注意:静态方法上执行的同步监视器对象不能使用this,而是当前类的类对象静态方法中使用同步代码块的锁对象应当是当前类的对象,格式:类名.class
* 类对象:
* Class类的一个实例,JVM加载一个类的时候就会实例化一个Class的实例并用于保存加载这个类的
* 相关信息,因此每个被加载的类都是有且仅有一个Class的实例与之对应,静态方法的锁对象就是它
* 类对象会在后续的反射知识中详细说明有限的缩小同步范围可以在保证并发安全的前提下保证效率
互斥锁
当使用多个Synchronized锁定多个代码片段 并且指定的所的对象相同时 这些代码片段就是互斥的
多个线程不能同时调用他们
package com.oracle.day37;public class SynchronizedDemo4 {public static void main(String[] args) {Doo d = new Doo();// Thread t1 = new Thread(() -> d.methodA());Thread t1 = new Thread(){@Overridepublic void run() {d.methodA();d.methodB();}}; // Thread t2 = new Thread(() -> d.methodB());Thread t2 = new Thread(){@Overridepublic void run() {d.methodA();d.methodB();}};t1.start();t2.start();} } class Doo{public synchronized void methodA() {Thread t = Thread.currentThread();System.out.println(t.getName()+"正在执行方法A");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(t.getName()+"方法A执行完毕");}public static void methodB() {Thread t = Thread.currentThread();System.out.println(t.getName()+"正在执行方法B");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(t.getName()+"方法B执行完毕");} }
死锁
在没有释放一把锁的同时调用了另一把锁
锁中放锁
线程的通讯
每一个线程都是独立运行的状态 但需要多个线程完成同一件事
需要多个线程有规律地运行
线程之间就需要通信,告知对方自己的状态
概念 等待队列 唤醒
相关方法:
void join()线程进入等待状态 等待另一条线程执行完毕然后继续执行
void wait()释放锁标记,进入等待队列
void wait(long time)释放锁 进入等待队列等待多长时间
void notify()唤醒线程
void notifyAll()唤醒全部线程
package com.oracle.day37.thread;public class ThreadDemo2 {public static Object o = new Object();public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {synchronized(o){for (int i = 1; i <= 26; i++) {System.out.println(i);try {o.wait();} catch (InterruptedException e) {e.printStackTrace();}o.notify();}}}};Thread t2 = new Thread(){@Overridepublic void run() {synchronized (o){for (int i = 'A'; i <= 'Z'; i++) {System.out.println((char)i);o.notify();try {o.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}};t1.start();t2.start();} }
锁的几种形式
①:方法中添加synchronized修饰符
②:使用同步代码块
synchronized(){};
* synchronized()可以添加 * this * Object o * Shop.class * "abc"③:Lock接口
要使用Lock接口,必须在线程中创建并获得Lock对象,然后在需要进行线程同步的代码块中使用lock()方法获取锁,使用unlock()方法释放锁。在多线程环境下,每个线程都必须单独创建自己的Lock对象,并进行锁定和解锁。这种方式可以保证每个线程都能够获取到自己的锁,从而在并发访问时保证线程安全性。
class BuyTicketMethod extends Thread{private static Integer M = 20;private final Lock lock = new ReentrantLock();@Overridepublic void run() {for (int i = 0; i < 10; i++) {lock.lock();try {if (M > 0){System.out.println("您在"+Thread.currentThread().getName()+"购买成功剩余票是"+(--M)+"张");}} finally {lock.unlock();}}} }
线程状态(线程的生命周期)
1创建状态:new Thread()对象
2就绪状态:线程对象.start(),线程等待cpu随机分配时间片长度
3运行状态:cpu随机分配给线程执行的cpu时间片
4阻塞状态:暂时让出cpu资源
5死亡状态