一、使用
Synchronized分为:对象锁、类锁
1.对象锁
(1)同步代码块锁
@Slf4j
public class Demo1 {
public static void main(String[] args) throws Exception{
Person person = new Person();
new Thread(() -> {
synchronized (person){
try {
log.debug(Thread.currentThread().getName() +"线程执行了run方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() +"线程执行完毕");
}catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
synchronized (person){
try {
log.debug(Thread.currentThread().getName() +"线程执行了run方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() +"线程执行完毕");
}catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
总结:使用Synchronized锁住了run方法中的代码块,表示同一个时刻只能有一个线程能够进入代码块。两个线程锁定同一个对象,相当于两个线程只有一把锁,所以要依次执行。
同步代码块锁的是对象,假如让每个线程拥有不同的锁,去访问不同的方法资源呢?
@Slf4j
public class Demo1 {
public static void main(String[] args) throws Exception{
Person person1 = new Person();
Person person2 = new Person();
Thread thread1 = new Thread(() -> {
synchronized (person1) {
try {
log.debug(Thread.currentThread().getName() + "线程执行了run方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (person2) {
try {
log.debug(Thread.currentThread().getName() + "线程执行了run方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()){}
log.debug("主线程执行结束");
}
}
总结:同步代码块锁只是对代码块进行加锁,同一时刻只能有一个线程获取锁,每一把锁只负责当前代码块。两个线程锁定不同的对象,相当于两把锁,各自执行各自的代码互不干扰。
(2)方法锁
@Slf4j
public class Demo1 {
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1();
Thread thread1 = new Thread(() -> {
demo1.getName();
});
Thread thread2 = new Thread(() -> {
demo1.getName();
});
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()){}
log.debug("主线程执行结束");
}
public synchronized void getName(){
try {
log.debug(Thread.currentThread().getName() + "线程执行了getName方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:两个线程对同一个普通同步方法访问,同一个时刻只能有一个线程进入该方法。
@Slf4j
public class Demo1 {
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1();
Thread thread1 = new Thread(() -> {
demo1.getName();
demo1.getAge();
});
Thread thread2 = new Thread(() -> {
demo1.getName();
demo1.getAge();
});
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()){}
log.debug("主线程执行结束");
}
public synchronized void getName(){
try {
log.debug(Thread.currentThread().getName() + "线程执行了getName方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void getAge(){
try {
log.debug(Thread.currentThread().getName() + "线程执行了getAge方法");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行方法getAge完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:Synchronized锁定的是对象,所以同一时刻两个方法中只能有一个线程在执行。
2.类锁
(1)static方法锁
@Slf4j
public class Demo1 implements Runnable{
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1();
Demo1 demo2 = new Demo1();
Thread thread1 = new Thread(demo1);
Thread thread2 = new Thread(demo2);
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()){}
log.debug("主线程执行结束");
}
public static synchronized void getName(){
try {
log.debug(Thread.currentThread().getName() + "线程执行了静态方法getName");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行静态方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override public void run() {
getName();
}
}
总结:定义了两个对象,但是类只有一个,所以同一个时刻只能有一个线程访问该方法。如果把static去掉,就是普通的同步方法,两个线程访问两个对象的方法,也就是每个线程拿到不同的锁,就能随便进入方法了。
(2)Class锁
@Slf4j
public class Demo1 implements Runnable{
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1();
Demo1 demo2 = new Demo1();
Thread thread1 = new Thread(demo1);
Thread thread2 = new Thread(demo2);
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()){}
log.debug("主线程执行结束");
}
public void getName(){
synchronized (Demo1.class) {
try {
log.debug(Thread.currentThread().getName() + "线程执行了方法getName");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
getName();
}
}
这个例子Synchronized锁定的是类Demo1.class,而不是对象。发现线程1、线程2会依次执行。
总结:如果多个线程执行的是同一把锁,则依次执行,否则各种执行各自的,互不干扰。
三、6种常见情况
1.两个线程同时访问一个对象的同步方法
总结:同一时刻只能有一个方法访问成功。
2.两个线程访问两个对象的同步方法
总结:Synchronized包装普通同步方法,是对象锁。由于两个线程访问两个对象的同一个同步方法,也就是这个方法有两把锁,此时锁不起作用,线程1、线程2可以互不干扰的访问该方法。
3.两个线程访问synchronized的静态方法
@Slf4j
public class Demo1 {
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(() -> {
getName();
});
Thread thread2 = new Thread(() -> {
getName();
});
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()) {
}
log.debug("主线程执行结束");
}
public synchronized static void getName() {
try {
log.debug(Thread.currentThread().getName() + "线程执行了静态同步方法getName");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行静态同步方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:两个线程访问同一个静态同步方法,发现此时是同一时刻只能有一个线程能够访问成功。
4.两个线程同时访问同步方法和非同步方法
@Slf4j
public class Demo1 implements Runnable{
public static void main(String[] args) throws Exception {
Demo1 demo1 = new Demo1();
Thread thread1 = new Thread(demo1);
Thread thread2 = new Thread(demo1);
thread1.start();
thread2.start();
//thread1、thread2只要有一个还活着,则一直执行
while (thread1.isAlive() || thread2.isAlive()) {
}
log.debug("主线程执行结束");
}
public synchronized void getName() {
try {
log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getName");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
public void getAge() {
log.debug(Thread.currentThread().getName() + "线程执行了普通方法getAge");
log.debug(Thread.currentThread().getName() + "线程执行普通方法getAge完毕");
}
@Override
public void run() {
getName();
getAge();
}
}
总结:两个线程分别同时访问普通同步方法、普通方法。同一时刻普通同步方法只能有一个线程能够访问成功,普通方法不受影响。
5.一个线程访问一个类的两个普通同步方法
@Slf4j
public class Demo1 implements Runnable{
public static void main(String[] args) throws Exception {
Demo1 demo1 = new Demo1();
Thread thread1 = new Thread(demo1);
thread1.start();
//thread1、只要还活着,则一直执行
while (thread1.isAlive() ) {
}
log.debug("主线程执行结束");
}
public synchronized void getName() {
try {
log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getName");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void getAge() {
try {
log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getAge");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getAge完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
getName();
getAge();
}
}
总结:由于同步代码块锁的是当前对象,this。 而且也只new了一个对象。所以同一时刻只能有一个线程能够访问成功。其实当只有一个线程访问时,上不上锁都是一样的,这里可以认为锁是失效的。
6.同时访问静态同步方法和非静态同步方法
@Slf4j
public class Demo1 implements Runnable{
public static void main(String[] args) throws Exception {
Demo1 demo1 = new Demo1();
Thread thread1 = new Thread(demo1);
Thread thread2 = new Thread(demo1);
thread1.start();
thread2.start();
//只要有一个线程还活着,则一直执行
while (thread1.isAlive() ) {
}
log.debug("主线程执行结束");
}
public synchronized void getName() {
try {
log.debug(Thread.currentThread().getName() + "线程执行了普通同步方法getName");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行普通同步方法getName完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized static void getAge() {
try {
log.debug(Thread.currentThread().getName() + "线程执行了静态同步方法getAge");
Thread.sleep(2000L);
log.debug(Thread.currentThread().getName() + "线程执行静态同步方法getAge完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
getName();
getAge();
}
}
总结:由此可见,普通同步方法与静态同步方法没有任何关联。普通同步方法锁的是对象,静态同步方法锁的是类。
二、缺点:
1.可重入性
不同方法是可重入的
同一个方法是可重入的
不同类方法是可重入的
2.不可中断
一个线程拿到锁之后,其他线程只能等待当前线程执行完毕获取抛异常,释放锁,其他线程才能获取锁,中间不可被打断。如果当前线程一直占用该锁,其他线程就会处于一直等待状态中。
3.不够灵活
加锁、解锁,每个锁只能有一个对象处理,不适用于分布式场景。
4.无法知道是否成功获取锁
5、效率低
Synchronized是不可中断的,所以当一个等待的线程获取不到锁,就会一直等待,而不去做其他的事情。但是可以设置等待时间,超过等待时间又没有拿到锁,就不会继续等待而去做其他事情