CSDN话题挑战赛第2期
参赛话题:面试宝典
线程安全在面试中是考官比较青睐的考点,那我就从多线程的组成特点上开始,分析线程安全问题、死锁出现与解决的方法以及线程安全的集合类总结。希望可以帮助大家理清有关知识点,直面考官,收割offer!
概念
线程是进程中并发执行的多个任务,进程是操作系统中并发执行的多个程序任务。
进程具有宏观并行,微观串行的特点:
只存在多线程,不存在多进程
OS进行调度分配,是线程执行的因素之一当多个线程同时访问同一临界资源时,有可能破坏其原子操作,从而导致数据缺失。
每个对象都默认拥有互斥锁,开启互斥锁之后,线程必须同时拥有时间片和锁标记才能执行,其他线程只能等待拥有资源的线程执行结束释放时间片和锁标记之后,才有资格继续争夺时间片和锁标记。
利用synchronized开启互斥锁,使线程同步,可以采取两种方法:
思路:谁访问临界资源,谁对其加锁
synchronized(临界资源对象){
//对临界资源对象的访问操作
}
示例:
public class Test {
public static void main(String[] args) throws Exception {
//myList 是自定义的集合类,封装了添加与遍历集合的方法
MyList m = new MyList();
//线程1:往集合中添加1-5
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
for (int i=1;i<=5;i++){
synchronized (m){
m.insert(i);
}
}
}
});
//线程2:往集合中添加6-10
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i=6;i<=10;i++){
synchronized (m){
m.insert(i);
}
}
}
});
//开启线程
t1.start();
t2.start();
//让t1和t2优先执行
t1.join();
t2.join();
//查看集合元素
m.query();
}
}
此例中临界资源是
m,为了防止t1进程中for循环执行后没来得及为其添加元素就被其他进程抢走时间片,因此在刚执行for循环时就将m锁住。
思路:对多个线程同时访问的方法进行加锁
访问修饰符 synchronized 返回值类型 方法名(){}
示例:
public class MyList {
List<Integer> list = new ArrayList<>();
//往集合属性中添加一个元素
public synchronized void insert(int n){
list.add(n);
}
//查看集合元素
public void query(){
System.out.println("集合长度:"+list.size());
for (int n : list){
System.out.print(n+" ");
}
System.out.println();
}
}
这里是我定义
MyList类的源码,如果这时候在insert方法加锁标记,那么这时线程再想被调度执行就需要同时拥有时间片和锁标记。
通常是由其中一个线程突然休眠导致
当多个线程同时访问多个临界资源对象时:假设线程1拥有锁标记1但是没有时间片和锁标记2,线程2拥有时间片和锁标记2但是没有锁标记1,则双方线程都无法正常执行,程序会被锁死。
结合线程通信来解决死锁问题
临界资源对象.方法名()
wait():使写入该方法的当前线程释放自身所有资源,进入无限期等待状态,直到其他线程执行结束将其强制唤醒之后,才能回到就绪状态继续时间片和锁标记的争夺notify():在当前临界资源的等待队列中随机唤醒一个处于无限期等待状态的线程
notifyAll():强制唤醒当前临界资源等待队列中的所有线程示例:
public class Test2 {
public static void main(String[] args) {
//创建临界资源对象
Object o1=new Object();
Object o2=new Object();
//创建线程1:先访问o1,再访问o2
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (o1){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println("休眠异常!!");
}
synchronized (o2){
System.out.println(1);
System.out.println(2);
System.out.println(3);
System.out.println(4);
//唤醒t2或t3
//o2.notify();
//唤醒t2和t3
o2.notifyAll();
}
}
}
});
//先访问o2,再访问o1
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (o2){
try {
o2.wait();//让当前线程释放自身所有资源,在o2的队列中进入无限期等待
} catch (InterruptedException e) {
System.out.println("操作失败!");
}
synchronized (o1){
System.out.println("A");
System.out.println("B");
System.out.println("C");
System.out.println("D");
}
}
}
});
//先访问o2,再访问o1
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (o2){
try {
o2.wait();//让当前线程释放自身所有资源,在o2的队列中进入无限期等待
} catch (InterruptedException e) {
System.out.println("操作失败!");
}
synchronized (o1){
System.out.println("+");
System.out.println("-");
System.out.println("*");
System.out.println("/");
}
}
}
});
t1.start();
t2.start();
t3.start();
}
}
在线程
t2和t3中增加wait会释放时间片与锁标记陷入无限期等待,而t1进程可以在使用完成o2资源后唤醒其他线程从而操作o1资源,这样就不会出现死锁的情况。
Thread类,wait属于Object类synchronized解决问题ConcurrentHashMap:JDK5.0 java.concurrent
CopyOnWriteArrayList:JDK5.0 java.concurrent
CopyOnWriteArraySet:JDK5.0 java.concurrent
本文多为总结性内容,建议大家收藏哦~