1.线程安全概念,当多个线程访问某一个类(对象或者方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或者方法)就是线程安全的。
2.Synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为“互拆区”或者“临界区”
来看下一个简单的例子:
package com.ck.thread;
public class MyThread extends Thread{
private int count = 5;
@Override
public void run() {
count--;
System.out.println(this.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread, "t1");
Thread t2 = new Thread(myThread, "t2");
Thread t3 = new Thread(myThread, "t3");
Thread t4 = new Thread(myThread, "t4");
Thread t5 = new Thread(myThread, "t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
运行结果:
t1 count = 2
t5 count = 0
t2 count = 2
t4 count = 1
t3 count = 2
看结果是不是很诧异,与我们想象的中的不一样这是为什么呢,是因为MyThread类不少线程安全的,那么能让运行结果正确呢?这时候就是synchronized 大显身手的时候了,我们看下下面代码:
package com.ck.thread;
public class MyThread extends Thread{
private int count = 5;
@Override
public synchronized void run() {
count--;
System.out.println(this.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread, "t1");
Thread t2 = new Thread(myThread, "t2");
Thread t3 = new Thread(myThread, "t3");
Thread t4 = new Thread(myThread, "t4");
Thread t5 = new Thread(myThread, "t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
运行结果:
t1 count = 4
t3 count = 3
t5 count = 2
t4 count = 1
t2 count = 0
这结果与我们想象中是一样的,但是执行的顺序并不是我们写代码的顺序,而是CPU分配的顺序
我们对上面的列子总结下:
当多个线程访问MyThread的run方法时,以排队的方式进行处理,排队的方式是按照CPU分配的先后顺序而定的,CPU对各个线程调度是随机的。
调度模式有两种:分时调度和抢占式调度
分时调度:所有线程轮流得到CPU使用权,并且平均分配每个线程占用的时间;
抢占式调度:根据线程的优先级来获取CPU的使用权
JVM采用的是抢占式模式
线程执行synchronized修饰的方法或者代码块,
- 首先尝试获得锁、
- 如果拿到锁执行synchronized的代码体,如果拿不到锁,线程会不断的尝试获得synchronized的锁,与其他多个线程一起竞争,直到拿到锁。
我们再回过来看下上面没有加synchronized时为什么会出现与我们想象中不一样的结果,
我们可以看到上面不一致的结果是由于各个线程间数据不同步引起的,加锁synchronized以后每个线程会把数据同步到主内存里面,同时其他等待的线程也会从主内存中获取变量,也就是我们所说的可见性与一致性。