package com.yigjn.Thread;
public class MyThread extends Thread {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
count++;
System.out.println(Thread.currentThread().getName() + "=====");
}
}
public static void main(String[] args) throws InterruptedException {
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();
while(Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(myThread.count);
}
}
通过这段代码会发现count会保证有序性,保证了线程安全,但是去掉run方法循环体中输出语句会发现,这时候的count++非原子性操作在多线程的并发下就会变成线程不安全,之前网上有很多人说因为println是线程安全
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
但是又觉得不太合理这个解释,因为sysnchronized锁的只是输出的对象,所以经过查阅资料,发现hotsport
jvm有一个锁优化原则那就是 : 粗化锁
如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即 使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。 如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(膨胀)到整个操作序列的外部(由多次加锁编程只加锁一次)。
所以实际run方法里面执行的代码是
synchronized(){
for(;;){
}
}
所以这才保证了上面的代码是线程安全的。
工作内存与主内存的同步
大家也都知道jvm大致分为工作内存与主内存
说到工作内存与主内存的同步操作就得说到一个关键次 就是:volatile
volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。而最彻底的同步要保证有序性和可见性,例如synchronized。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的
package com.yigjn.Thread;
public class MyThread extends Thread {
private boolean flag = true;
@Override
public void run() {
while(flag) {
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "======");
}
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread, "t1");
t1.start();
Thread.sleep(3000);
myThread.flag = false;
Thread.sleep(3000);
}
}
之前看有人说上述代码,可以保证flag可以在被修改之后获取到主内存的值,因此说是pringln是具有清空工作内存,同步主内存的功能,但其实你会发现把注释代码松开输出语句注释掉,其实也是可以停止的,jvm在有volatile关键字时不存在工作内存中存储主内存的副本,来保证多线程的可见性,但是没有的时候,jvm也会尽可能的在空闲的时候会去同步主内存中的值,才造成的假象