关于synchronized
public class Demo{
private int count = 10;
privare Object object = new Object();
public void test(){
synchronized(obejct){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
}
//简便的写法一、
public void test(){
synchronized(this){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
}
//简便的写法二、
public synchronized void test(){
count--;
System.out.println(Thread.currentThread().getName()+" count = "+count);
}
//在这里的排序也是有讲究的,synchronized虽然在一、二这里起到的作用相同。
//但是synchronied如果只需要一部分代码需要上锁,那么就不要锁整个代码块,锁一小部分代码块就行了
//这样的好处在于写法一与写法二的性能上,一的性能会好与二的性能,CPU征用的时间减少了,提高了效率。
}
synchronized 就是所说的锁,但是要清楚的是,它不是锁方法,不是代码块,而是分两种情况,一种是对象类的实例,一种是类的字节码。
比如:当同时有两个线程执行这个方法的时候,当第一个正在使用还未结束时,第二个线程就无法继续使用。一定程度上保证了线程的稳定和安全,但是这并不代表使用synchronized就代表着线程安全。
public class Demo{
public static void test2(){
//能否将Demo.class换成this
synchronized (Demo.class){
count--;
}
}
}
答案是:不能!静态(static)方法不能使用this,因为静态方法可以直接通过调用class.method(类名.方法名)来使用,那么就没有这个实例了,没有就锁不到东西了。所以只能通过Demo.class来锁了
相关面试题:
1.同步方法和非同步方法是否可以同时调用?
答案:可以同时调用,因为它们并非同一个方法自然可以同时调用了。
2.同步方法调用同步方法能否得到锁?
答案:可以。
1.因为它们是用一个锁的,当一个方法里面,还有一个方法也需要这个锁的时候,那么这个方法也是可以得到锁的。
2.这证明synchronized支持 重入锁 的
3.顺便一提,继承关系下的锁也是共享的,也是可以重入锁的。
关于脏读问题:
- 主要表现在读与写方面。例如:一个银行为例子,用户存钱为写,显示余额为读。
- 为了保证用户能够成功存钱,一般都会在写上加上synchronized来保证成功率。
- 但是显示余额上,它却没办法马上读取到刚刚存进去的那部分钱。
- 所以也需要在读的方法上面也加上synchronized来保证数据的读取。
volatile:
- 是synchronized的一个轻量级关键字,它不能保证量子性,却能保证可见性。
- 在JAVA的主内存里(JAVA只有一个主内存),当一个变量在线程中被修改的时候,JAVA的内存模型JMM是不会刷新的,依旧会以变量原本的值进行运行。
- 倘若你在变量的修饰中加了volatile,它的可见性,会通知JMM里所有的进程,这个变量的值变化了。
- 其实不加的话也可以,以现在的科技,可以完全不用加。
- 因为现在的CPU都很高级,性能也很好,现在的CPU有一个缓存协议,它能够保证每个CPU的缓存都能及时刷新过来。
- 缺点:volatile只保证可见性,却没办法保证原子性,它不会管你读取前是什么,只会管显示。倘若10个线程同时i++一百次,按常理i会是1000,但是只有volatile的话,只会少于1000。
- 解决方法:在i++一百次的方法上加synchronized来修饰,倘若不想用synchronized的话,可以用atomic的类来解决。整型用AtomicInteger.incrementAndGet()来解决足以。
- 但是即便如此,多个atomic类连续调用也不能构成原子性。
死锁:
public class Demo{
String s1="Hello";
String s2="Hello";
public void test1(){
synchronized(s1){
.....
}
}
public void test2(){
synchronized(s2){
.....
}
}
public static void main(String[] args){
Demo dm = new Demo();
new Thread(dm::test1,"test1").start();
new Thread(dm::test2,"test2").start();
}
}
死锁是多线程中一个诡异的现象,明明用来当锁的对象并不一样,按照道理来说应该是互不阻塞的。但是运行的时候却会发生阻塞的现象。原因就在于s1和s2是常量。s1和s2是常量,并且都是“Hello”,所以它们在常量池里的地址其实是一样的,所以我们要避免使用常量作为锁。这也是为什么一般都是用对象来当锁的原因。
- 死锁原因
- 当test1运行的时候,由于地址一样。
- 原本只用s1的test1在过程中会用到s2,用s2为锁test2也会在过程中运用到s1,
- 造成了各方都在等待另一方释放钥匙。最后的结果就是发生了阻塞。
wait():等待线程。
notify():唤醒线程,能够唤醒wait()等待线程。
但是要注意的是:
- wait()和notify()必须在同一个方法里面。
- wait()等待会释放锁,但是notify()不会。
- 所以即便notify()唤醒了wait()所在的线程,wait()所在的线程也没办法马上运行。
- 因为它的锁正在nofity()那里,需要等它结束后才能用。