多线程锁——详解
先来讲什么是线程:
即:Thread和Runnable两个类,可以实现线程
class Card extends Thread{
//第一步,重写父类Thread中的run方法,这样就可以调度线程,调度线程中启动的方法,即run方法:
@Override
public void run(){
System.out.println("线程启动...");
}
}
为何要重写run方法,因为多线程方法的调用过程就是这么定义的,我们任何线程的
当我们Thread类的引用被创建以后,我们可以调用start方法来启动该线程,很有趣的是,我们应该要思考,我们start启动完线程以后,我们的执行顺序是怎么样的?
首先,我们知道,我们的线程体方法是run,如果我们直接调用run方法,例如
public class Test{
Thread1 t1=new Thread1();
Thread2 t2=new Thread2();
t1.run();
t2.run();
}
这种情况下,我们的执行顺序,是调用方法的执行顺序,即先执行Thread1中的run方法,再是Thread2中的run方法
然而,这样的调用方式,就会把我们多线程的作用给浪费了,那么该如何是好?
所以,我们才有调用Thread.start()方法,这个方法调用的目的,就是为了将我们的线程,提交至线程栈,或者线程队列,(待考证)
当然上面的线程体只是做了打印的操作,对于我们的多个线程的创建并运行并没有什么多大的影响?什么,你告诉我什么是影响?一般都一下几种影响:
1、线程竞争 顾名思义,就是在多个任务(也叫做创建多个线程(多个线程可能都是相同的线程体,也可能是不同的线程体)并启动)同时访问同一个线程体的同时,对执行相同的代码,并且同时执行,例如以下代码
static int ii=0;
public Map<Integer,Object> map=new HashMap<Integer,Object>();
@Override
public void run(){
while(ii<100){
ii++;
System.out.println("ii:" + ii + "currentThreadNo:" + Thread.currentThread().getName());
}
}
public static void main(String[] args){
for(int n=0;n<100;n++){
new Thread(new ThreadSample()).start();
}
}
现在我们要启动100个线程,对相同的线程体进行100次的任务调用,然后,我们截取前10个输出打印,我们将发现如下:
ii:1currentThreadNo:Thread-0
ii:2currentThreadNo:Thread-2
ii:3currentThreadNo:Thread-2
ii:4currentThreadNo:Thread-2
ii:5currentThreadNo:Thread-2
ii:8currentThreadNo:Thread-2
ii:9currentThreadNo:Thread-2
ii:10currentThreadNo:Thread-3
ii:12currentThreadNo:Thread-3
ii:13currentThreadNo:Thread-3
ii:14currentThreadNo:Thread-3
ii:7currentThreadNo:Thread-0
ii:16currentThreadNo:Thread-0
ii:17currentThreadNo:Thread-0
ii:6currentThreadNo:Thread-4
显然,我们看到,我们的线程输出是从1~5之后,直接跳到8,什么意思?就是说在我们线程2调度完毕,输出为5之后,我们线程2接下去直接调度输出就变成了8,因为6和7已经被其他线程调度到一般,值发生了改变,于是等到我们线程0都调度输出为17之后,这个时候,我们先前的线程4,开始输出6了,大家看,一直等到这一刻,我们的线程4才输出6啊,这里有个问题,就是我们的线程是不安全的,同时有多个线程一起来竞争这个资源,也或者说叫做同时有多个任务一起竞争这个资源,那么竞争完后必然会造成线程体中,各线程对于资源的争抢
所以,我们需要对我们的线程体加锁,怎么加锁?给方法上,加上同步synchronized,这样,就能够保证我们的线程是一个安全的线程,什么是安全线程,就是意味着,我们所有一起参与竞争的任务,或者说线程,不会造成一个任务或线程调用到一半的时候,另外的任务或者线程参与进来开始调度,当我们将run方法改写为如下后,
public synchronized void run(){...}
于是,,我们在运行一下看下我们的日志输出是什么样的情况:
ii:1currentThreadNo:Thread-0
ii:2currentThreadNo:Thread-0
ii:3currentThreadNo:Thread-2
ii:4currentThreadNo:Thread-2
ii:5currentThreadNo:Thread-2
ii:6currentThreadNo:Thread-2
ii:7currentThreadNo:Thread-2
ii:8currentThreadNo:Thread-2
.
.
.
ii:531currentThreadNo:Thread-3
ii:532currentThreadNo:Thread-30
ii:533currentThreadNo:Thread-17
ii:534currentThreadNo:Thread-28
ii:535currentThreadNo:Thread-24
ii:536currentThreadNo:Thread-13
ii:537currentThreadNo:Thread-26
ii:538currentThreadNo:Thread-9
ii:539currentThreadNo:Thread-22
ii:540currentThreadNo:Thread-18
ii:541currentThreadNo:Thread-14
ii:542currentThreadNo:Thread-10
ii:543currentThreadNo:Thread-5
ii:544currentThreadNo:Thread-6
大吃一惊,我们最终输出的结果居然到了544,而我们同时参与并发的线程任务才只有500个,这又是为啥呢?
原来,我们没有对run内部调用的所有代码(我们把所有这些代码看成一个方法,)进行加锁操作,所以,我们需要对方法进行如下改进:
@Override
public void run(){
synchronized(this){
while(ii<500){
ii++;
queue.add(ii);
System.out.println("ii:" + ii + "currentThreadNo:" + Thread.currentThread().getName());
}
}
}
但是,当我们运行多个任务调度的时候,发现,在一个线程参与一次迭代的时候,接下去还是会有其他线程参与竞争,这样的话,问题就来了
为什么我们加了锁以后,我们的线程还是不安全,还是会有其他线程参与竞争,,,,这个问题很严重啊
于是我们将方法修改如下 :
@Override
public void run(){
synchronized(ThreadSample.class){
while(ii<500){
ii++;
queue.add(ii);
System.out.println("ii:" + ii + "currentThreadNo:" + Thread.currentThread().getName());
}
}
}
这个时候,我们高兴的发现,我们的输出,终于是线程安全的了,就是当我们一个线程执行这个线程体的过程中,其他线程是无法参与进来的,输出如下:
ii:1currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
ii:2currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
ii:3currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
ii:4currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
.
.
ii:497currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
ii:498currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
ii:499currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
ii:500currentThreadNo:Thread-0 Tue Apr 30 15:18:50 CST 2019
这样,我们的线程终于安全了。
锁:synchronized