synchronized 和 volatile 使用
线程抛出异常,锁会被释放
如下demo
public class Demo1 {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + ": start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1 / 0;
}
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
Runnable r = new Runnable() {
@Override
public void run() {
demo1.m();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
如果不想释放那把线程锁,则在1 / 0
地方加上try/catch
。则本段程序不会执行线程t2
代码,因为t1
一直没有释放。
线程之间的可见性
主线程将值改变,子线程由于cpu的缓冲区占满,无法被读取主内存的变量。
public class Demo2 {
boolean running = true;
synchronized void m() {
System.out.println(": start");
while (running) {
}
System.out.println(": end");
}
public static void main(String[] args) {
Demo2 demo1 = new Demo2();
new Thread(demo1::m, "t1").start();
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo1.running = false;
}
}
通过定义volatile
关键字(线程之间通信的方式)来,让缓冲区读取主内存数据
可见性
一旦某个线程修改了volatile关键字修饰的变量,则改变量会立即保存修改后的值到物理内存。其他线程读取该值时,也可以立即获取修改后的值。
在java运行时,为了提高程序运行效率,对于一些变量的操作通常在寄存器或者CPU缓存上进行,之后才会保存到物理内存中,而使用volatile关键字修饰的变量则是直接读取物理内存。
volatile最佳实践
volatile由于不能保证原子性
,用户不需要依赖于上一个变量的场景
Atomic使用
AtomicInteger
等类,是用来解决简单的类似 count++ 原子计算的问题。他的效率比synchronized
高的多。
public class Demo2 {
AtomicInteger count = new AtomicInteger(0);
void m() {
for (int i = 0; i < 100000; i++) {
count.incrementAndGet();
}
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
List<Thread> threads = new ArrayList<>();
for (int j = 0; j < 10; j++) {
threads.add(new Thread(demo2::m, "theads" + j));
}
threads.forEach(o -> o.start());
threads.forEach(o -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(demo2.count.get());
}
}
但是如果AtomicXXX多个方法不构成原子性比如
if(count.get() < 1000) {
count.incrementAndGet();
}
这时候在两段代码执行中,可能被其他线程执行
synchronized
- 不要以字符串常量作为锁定对象
String s1="hello";
String s2="world";
synchronized(s1) {
}
锁定的是字符串的对象,这时候锁定S1以及S2都是锁定的同一个对象,造成死锁的情况。
wait/notify
- wait 会让出锁对象,notify不会释放锁的对象。
- wait 是锁定当前的对象,notify会随机唤醒一个线程
CountDownLatch
每个线程执行完一个任务“倒数”一次。总结来说,CountDownLatch的作用就是等待其他的线程都执行完任务,必要时可以对各个任务的执行结果进行汇总
网友评论