Synchronized使用方式
demo1
package com.w.juc;
public class SynchronizedDemo {
static int value;
public static void increment() {
for (int i = 0; i <10000; i++) {
value++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> increment(), "线程【A】");
Thread thread1 = new Thread(() -> increment(), "线程【B】");
thread.start();
thread1.start();
thread.join();//等待线程A执行完毕后返回
thread1.join();
System.out.println(value);//这里正常想要打印出来2万
}
}
上面代码并没有用到synchronized,也就是说并未同步。先说明上面代码是一个有着并发问题的代码:value是一个共享变量,而value++其实是三步操作,先读取,后+1,最后写入。对于A,B线程来说,可能出现如下场景,线程A读取了value=4,在未+1时,线程B也读取到了value=4。这时线程A执行完毕value=5,但是对于线程B执行后的结果也是value=5,这就少加了一次。
可见,多个线程共同访问修改一个共享变量会出现并发问题,所以需要解决,这里就使用到了synchronized,当然并发包下的也可以用。
synchronized实现同步的三种方式。
- 可以锁普通方法
- 锁静态方法
- 代码块
demo2
package com.w.juc;
public class SynchronizedDemo {
static int value;
public static synchronized void increment() {
for (int i = 0; i <10000; i++) {
value++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> increment(), "线程【A】");
Thread thread1 = new Thread(() -> increment(), "线程【B】");
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(value);//一定输出2万
}
}
先使用的是在静态方法上使用。锁的是当前Class对象,类对象。或者
demo3
package com.w.juc;
public class SynchronizedDemo {
static int value;
public synchronized void increment() {
for (int i = 0; i <10000; i++) {
value++;
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo sd = new SynchronizedDemo();
Thread thread = new Thread(sd::increment, "线程【A】");//Java8特性,方法引用
Thread thread1 = new Thread(() -> sd.increment(), "线程【B】");//Lambda表达式
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(value);
}
}
demo3锁的是对象实例,也就是sd。现在demo3锁的是Class对象,对于demo2和demo3来说好像没什么区别都是正常输出2万
但是现在看看demo4
package com.w.juc;
public class SynchronizedDemo {
static int value;
public synchronized void increment() {
for (int i = 0; i <10000; i++) {
value++;
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo sd = new SynchronizedDemo();
SynchronizedDemo sd2 = new SynchronizedDemo();//和demo3区别
Thread thread = new Thread(sd::increment, "线程【A】");
Thread thread1 = new Thread(() -> sd2.increment(), "线程【B】");//区别
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(value);
}
}
对于普通方法上加锁,锁的是当前实例对象,也就是demo4中sd和sd2这是俩对象,所以线程A持有锁sd,线程B持有锁sd1,这样大家互不干扰,也就都可以同时访问修改value和deom1一样的并发问题出现了。怎么解决呢?看看demo2,锁的是类对象,而类对象只存在一个。看demo5吧
demo5
package com.w.juc;
public class SynchronizedDemo {
static int value;
public static synchronized void increment() {//加static,锁的就是类对象
for (int i = 0; i <10000; i++) {
value++;
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo sd = new SynchronizedDemo();
SynchronizedDemo sd2 = new SynchronizedDemo();
Thread thread = new Thread(()->sd.increment(), "线程【A】");//这里我的会报错。。。因为加了阿里巴巴规范插件,不推荐我使用对象实例调用类的静态成员
Thread thread1 = new Thread(() -> sd2.increment(), "线程【B】");
thread.start();
thread1.start();
thread.join();
thread1.join();
System.out.println(value);//正常输出2万
}
}
锁了类对象,俩实例对象sd和sd2就会独占的获取锁,同一时刻只有一个线程才能进入increment方法
以问题深入记忆三种使用方式
可以看视频
问题1:标志访问先打印哪个?
package com.w.juc;
class Person{
public synchronized void eatFood(){
System.out.println("吃肉");
}
public synchronized void drinkWater(){
System.out.println("喝水");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
new Thread(()->person.eatFood(),"t1").start();
new Thread(()->person.drinkWater(),"t2").start();
}
}
//吃肉
//喝水
解释:对普通方法使用synchronized锁的是当前实例对象person,当线程t1启动则获取到锁,线程t2必须等待t1执行结束后执行
问题2:吃饭用2s,先打印哪个?
package com.w.juc;
class Person{
public synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public synchronized void drinkWater(){
System.out.println("喝水");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
new Thread(()->person.eatFood(),"t1").start();
new Thread(()->person.drinkWater(),"t2").start();
}
}
//吃肉
//喝水
解释:和问题1一样
问题3:新增普通方法sleep,先打印哪个?
package com.w.juc;
class Person{
public synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public synchronized void drinkWater(){
System.out.println("喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
new Thread(()->person.eatFood(),"t1").start();
new Thread(()->person.toSleep(),"t2").start();
}
}
睡觉
吃肉
解释:睡觉是个普通未同步方法,随时可以访问,只要线程t2不去抢实例对象person锁,则不会被阻塞。
问题4:俩人,先吃肉还是喝水?
package com.w.juc;
class Person{
public synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public synchronized void drinkWater(){
System.out.println("喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person1 = new Person();
Person person2 = new Person();
new Thread(()->person1.eatFood(),"t1").start();
new Thread(()->person2.drinkWater(),"t2").start();
}
}
喝水
吃肉
解释:俩人只要不去干同一件事就不会有冲突,锁的是当前实例对象,线程t1和线程t2持有的锁不一样,就不会冲突,也就不存在因为锁阻塞线程
问题5:静态同步方法,一个人,先吃肉还是喝水?
package com.w.juc;
class Person{
public static synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public static synchronized void drinkWater(){
System.out.println("喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person1 = new Person();
Person person2 = new Person();
new Thread(()->person1.eatFood(),"t1").start();
new Thread(()->person1.drinkWater(),"t2").start();
}
}
吃肉
喝水
解释:在静态方法上面用synchroniezd,锁的是当前类对象,只有一个类对象。所以各个线程还是需要等待锁的获取
package com.w.juc;
class Person{
public static synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public static synchronized void drinkWater(){
System.out.println(Thread.currentThread()+"喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person1 = new Person();
Person person2 = new Person();
new Thread(()->person1.eatFood(),"t1").start();
new Thread(()->person1.drinkWater(),"t2").start();
new Thread(()->person1.drinkWater(),"t1").start();
}
}
吃肉
Thread[t1,5,main]喝水
Thread[t2,5,main]喝水
这里按照程序顺序应该是线程t2先喝水,为什么执行却是线程t1先喝?因为重进入,虽然线程t1和t2喝水都被阻塞了,但是t1先持有锁,可重进入锁就快
问题6:静态同步方法,俩人,先打印吃肉还是喝水?
package com.w.juc;
class Person{
public static synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public static synchronized void drinkWater(){
System.out.println(Thread.currentThread()+"喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person1 = new Person();
Person person2 = new Person();
new Thread(()->person1.eatFood(),"t1").start();
new Thread(()->person2.drinkWater(),"t2").start();
}
}
吃肉
Thread[t2,5,main]喝水
解释:和问题5一样,静态方法,获取到的锁是类对象,只有一个,也就是说同时只有一个线程可以获取到锁,去执行方法。
问题7:一个静态同步方法,一个普通方法,一个人,会先打印什么
package com.w.juc;
class Person{
public static synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public static synchronized void drinkWater(){
System.out.println(Thread.currentThread()+"喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person1 = new Person();
Person person2 = new Person();
new Thread(()->person1.eatFood(),"t1").start();
new Thread(()->person1.toSleep(),"t2").start();
}
}
睡觉
吃肉
解释: 普通方法可以任意访问,不受限制
问题8:一个静态同步方法,一个普通方法,俩个人,会先打印什么
package com.w.juc;
class Person{
public static synchronized void eatFood(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃肉");
}
public static synchronized void drinkWater(){
System.out.println(Thread.currentThread()+"喝水");
}
public void toSleep(){
System.out.println("睡觉");
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Person person1 = new Person();
Person person2 = new Person();
new Thread(()->person1.eatFood(),"t1").start();
new Thread(()->person2.toSleep(),"t2").start();
}
}
睡觉
吃肉
解释:和问题7一样,没有同步的方法并不会是独占的,任意线程都可以访问。
以上只是写了怎么使用和一些理解。主要还得深究原理,和JVM相关,对象头。