1、synchronized保证三大特性
原子性
(1)使用synchronized保证原子性
在第一个线程获取到锁之后,在他执行完之前不允许其他的线程获取锁并操作共享数据,从而保证了程序的原子性。synchronized保证原子性的原理,synchronized保证只有一个线程拿到锁,能够进入同步代码块
可见性
(1)volatile关键字
(2)使用synchronized
(3)打印(因为打印语句里面也有用到synchronized)
有序性
(1)为什么要重排序
为了提高程序的执行效率,编译器和CPU会对程序中代码进行重排序。
(2)as-if-serial语义
as-if-serial语义的意思是:不管编译器和CPU如何重排序,必须保证在单线程情况下程序的结果是正确的。
编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。例如:修改顺序后运算结果改变
(3)synchronized 保证有序性的原理
synchronized后,虽然进行了重排序,保证只有一个线程会进入同步代码块,也能保证有序性。
synchronized保证有序性的原理,我们加synchronized后,依然会发生重排序,只不过,我们有同步代码块,可以保证只有一个线程执行同步代码中的代码。保证有序性
2、synchronized的特性
可重入
(1)可重入演示
public class Test { public static void main(String[] args) { Runnable sellTicket = new Runnable() { @Override public void run() { synchronized (Test.class) { System.out.println("我是run"); test01(); } } public void test01() { synchronized (Test.class) { System.out.println("Test"); } } }; new Thread(sellTicket).start(); new Thread(sellTicket).start(); } }
我是run
Test
我是run
Test
概念:
一个线程可以多次执行synchronized,重复获取同一把锁。
原理:
synchronized的锁对象中有一个计数器(recursions变量)会记录线程获得几次锁
synchronized是可重入锁,内部锁对象中会有一个计数器记录线程获取几次锁了,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁
好处:
- 可以避免死锁
- 可以让我们更好的来封装代码
不可中断
(1)synchronized 不可中断的演示
public class Test { private static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { Runnable run = () -> { synchronized (obj) { //在Runnable定义同步代码块 String name = Thread.currentThread().getName(); System.out.println(name + "进入同步代码块"); try { // 保证不退出同步代码块 Thread.sleep(888888); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t1 = new Thread(run); // 开启一个线程来执行同步代码块 t1.start(); Thread.sleep(1000);//主线程 Thread t2 = new Thread(run); //后开启一个线程来执行同步代码块(阻塞状态) t2.start();//没有锁阻塞 System.out.println("停止线程前"); // 停止第二个线程 t2.interrupt();//中断线程 System.out.println("停止线程后"); System.out.println(t1.getState());//执行的是sleep,处于TIMED_WAITING System.out.println(t2.getState()); } }
Thread-0进入同步代码块
停止线程前
停止线程后
TIMED_WAITING
BLOCKED
一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程不释放锁,第二个线程会一直阻塞或等待,不可被中断。
(2)ReentrantLock不可中断的演示
public class Test { private static Lock lock = new ReentrantLock(); public static void test01() throws InterruptedException { Runnable run = () -> { String name = Thread.currentThread().getName(); try { lock.lock(); System.out.println(name + "获得锁,进入锁执行"); Thread.sleep(88888); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println(name + "释放锁"); } }; Thread t1 = new Thread(run);//第一个线程 t1.start(); Thread.sleep(1000); Thread t2 = new Thread(run);//第二个线程 t2.start(); System.out.println("停止t2线程前"); t2.interrupt();//终止线程2 System.out.println("停止t2线程后"); Thread.sleep(1000); System.out.println(t1.getState());//TIMED_WAITING //一个线程在一个特定的等待时间内等待另一个线程完成一个动作会在这个状态 System.out.println(t2.getState());//WAITING // 一个线程在等待另一个线程执行一个动作时在这个状态 } public static void main(String[] args) throws InterruptedException { test01(); } }
但是Lock也存在可中断的情况:
public class Test { private static Lock lock = new ReentrantLock(); public static void test02() throws InterruptedException { Runnable run = () -> { String name = Thread.currentThread().getName(); boolean b = false; try { b = lock.tryLock(3, TimeUnit.SECONDS); if (b) { System.out.println(name + "获得锁,进入锁执行"); Thread.sleep(88888); } else { System.out.println(name + "在指定时间没有得到锁做其他操作"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (b) { lock.unlock(); System.out.println(name + "释放锁"); } } }; Thread t1 = new Thread(run); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(run);//第二个线程尝试获得锁,停留三秒后未获得成功,执行false后的语句 t2.start(); } public static void main(String[] args) throws InterruptedException { test02(); } }
Thread-0获得锁,进入锁执行
Thread-1在指定时间没有得到锁做其他操作
不可中断是指,当一个线程获得锁后,另一个线程一直处于阻塞或等待状态,前一个线程不释放锁,后一个线程会一直阻塞或等待,不可被中断。
- synchronized属于不可被中断
- Lock的lock方法是不可中断的
- Lock的tryLock方法是可中断的