Java中为了解决多线程中的线程安全问题就诞生出了一个概念叫做锁,而这个锁的作用就是在若干个线程要几乎同时对某个操作进行执行时为了保证该操作的原子性从而限制线程的访问,在这个过程中只能由拿到锁的线程对该操作进行执行,而其他线程只能在该线程将锁“归还”后,再进行抢占执行来获得锁,这样一来就很好的保证了该操作的原子性,从而增强了线程安全。
其实锁的对象是被synchronized所修饰的“对象”(这个对象并不是实例化后的对象,可能是一个方法,也可能是一个代码块),这个过程可以形象的描述为一个线程在和别的线程再抢占的过程中抢到了先手,然后拿到了synchronized所修饰的“对象”的锁子,然后将自己和这个“对象”锁在一个房间里然后执行操作,然后再操作执行完后,进行解锁,把这个锁子解开又挂再门上,然后供别的线程抢,但是如果再定义一个方法,其中写上同样的代码但不上锁会怎么样,锁其实针对的是双方,也就是说我通过锁去找这个语句,那么我和被synchronized修饰的“对象”就要互相遵守锁的法则,可另一个没有锁的方法提供了另一条渠道去访问被synchronized修饰的“对象”,就好比在这个房间上又开一道门,而访问这个方法的线程就可以通过这个门去访问被synchronized修饰的“对象”,而这个门没有锁,这样一来这个被synchronized修饰的“对象”的原子性又不保证了,所以都上锁才可以保证线程安全。这其就是锁的过程,虽然有点不正经但,但好理解,而这只是synchronized修饰方法的时候,代码如下:
class counter{
int count=0;
public synchronized void increase1(){
count++;
}
}
public class Main {
static counter c=new counter();
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
for (int i = 0; i < 50000; i++){
c.increase1();
}
});
Thread thread2=new Thread(()->{
for (int i = 0; i < 50000; i++) {
c.increase1();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(c.count);
}
}
这就是使用synchronized避免了线程安全.
那如果修饰代码块呢,当synchronized修饰代码块是后面需要填写一个参数,而这个参数说实话不好理解,这个参数只能填写对象或者类对象,那么这个参数到底是什么意思,填上的作用是什么呢,好,请看这几个代码的区别:
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
class counter1{ }
class counter{
int count=0;
counter1 a=new counter1();
counter1 b=new counter1();
public void increase1(){
synchronized(a){
count++;
}
}
public void increase2(){
synchronized(b){
count++;
}
}
}
public class Main {
static counter c=new counter();
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
for (int i = 0; i < 50000; i++){
c.increase1();
}
});
Thread thread2=new Thread(()->{
for (int i = 0; i < 50000; i++) {
c.increase2();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(c.count);
}
}
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
class counter1{ }
class counter{
int count=0;
counter1 a=new counter1();
counter1 b=new counter1();
public void increase1(){
synchronized(a){
count++;
}
}
public void increase2(){
synchronized(a){
count++;
}
}
}
public class Main {
static counter c=new counter();
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
for (int i = 0; i < 50000; i++){
c.increase1();
}
});
Thread thread2=new Thread(()->{
for (int i = 0; i < 50000; i++) {
c.increase2();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(c.count);
}
}
这两个代码的区别就在:
public void increase1(){
synchronized(a){
count++;
}
}
public void increase2(){
synchronized(a){
count++;
}
}
public void increase1(){
synchronized(a){
count++;
}
}
public void increase2(){
synchronized(b){
count++;
}
}
代码就在第一个中的synchronized后面的参数是一样的都是a,而第二个是不一样的一个a,一个b,所以大家大致可以看出这个参数的作用是什么了吧,没错其实就是一个标记而已,并没有我们想象的那么复杂,就是对于同一个操作count++,在不同的地方上了锁而已,如果这两个方法中synchronized后面的参数相同就说明如果有两个线程分别访问这连个“对象”,那么,这两个线程就会产生锁竞争,如果不同就不会产生锁竞争。仅此而已。
接着上面的例子将这个概念添加进去,后面的参数就相当于一把锁,而增加一把锁就相当于又开了一扇门,如果两个线程要通过同一个锁去访问被synchronized修饰的“对象”,是会产生锁竞争的,但是如果通过两个锁去访问被synchronized修饰的“对象”,就好比我从这个门进去,然后把门锁了,你呢又从另一个门进去了,然后把门锁了,我们两个又同时进入房间里了,被synchronized修饰的“对象”的原子性又不保证了,所以,这个参数其实就是一个锁,锁一样就竞争,不一样就不竞争。
而synchronized修饰的位置可以是一个方法也,可以是一个代码块,也可以是一个静态方法,而修饰静态方法就相当于synchronized后面的参数是一个类对象,而一个类对象在哪出现都是一样的,所以两个实例化对象中的这个静态方法就可以看作是两个用了一把锁的方法。