最近在写代码的时候被同学问到多线程的问题,虽然最后用Synchronized包裹同步代码块解决了,但是感觉这块学的很肤浅,所以重新拿起来继续学习学习,在看了许多大牛的博客之后,突然恍然大悟,原来Synchronized还可以这么玩,哈哈,感谢大牛激情的分享,让我学到很多。@这绝对是大佬,算了不夸了,大家是来学习知识的,让我们细细品。。
先来看一段代码:
package thread; /** * ⊙﹏⊙&&&&&&⊙▽⊙ * * @Auther: pangchenbo * @Date: 2019/11/1 10:49 * @Description: */ public class TicketWindow1 implements Runnable { private int tickets = 50; Object lock = new Object(); @Override public void run() { while (true) { synchronized (lock) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "买票:" + tickets--); } else { System.exit(0); } } } } }在上面的类中,利用synchronized对while(true)中的语句进行了修饰,lock表示一种特殊的类,其实也就是Object类,在这里用this代替也是可以的,上面写的类似于一个窗口买票的程序,只要开启多条进程就可以同时进行卖票了。
TicketWindow1 ticketWindow1 = new TicketWindow1(); new Thread(ticketWindow1,"窗口一").start(); new Thread(ticketWindow1,"窗口二").start(); new Thread(ticketWindow1,"窗口三").start(); new Thread(ticketWindow1,"窗口四").start(); new Thread(ticketWindow1,"窗口五").start();运行结果
从运行结果可以看出来,五个线程在轮番进行卖票,而且线程之间共享50张票,线程之间不受影响,,因为同步锁synchronized每次只允许一个线程进行操作,只有当这条线程释放该资源之后,下一个线程才可以继续进行然后又进行上锁,不允许其他线程再进入,这就保证了票数不会出现混乱的情况。
接下来看这样的操作
Thread thread = new Thread(new TicketWindow1(),"窗口1"); Thread thread1 = new Thread(new TicketWindow1(),"窗口2"); Thread thread2 = new Thread(new TicketWindow1(),"窗口3"); Thread thread3 = new Thread(new TicketWindow1(),"窗口4"); Thread thread4 = new Thread(new TicketWindow1(),"窗口5"); thread.start(); thread1.start(); thread2.start(); thread3.start(); thread4.start();然后看他的输出结果:
发现什么没有?好像开启的每个线程都拥有了50张票这是为什么呢?而且每个线程之间没有干扰,原因在于synchronized作用的是一个对象,后面的创建方式中每次都创建了一个对象,所以就各个线程之间不受影响,所以五个线程之间同时进行。。。。
**当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
package thread; class Counter implements Runnable{ private int count; public Counter() { count = 0; } public void countAdd() { synchronized(this) { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized public void printCount() { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + " count:" + count); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public void run() { String threadName = Thread.currentThread().getName(); if (threadName.equals("A")) { countAdd(); } else if (threadName.equals("B")) { printCount(); } } }测试代码:
Counter counter = new Counter(); Thread thread1 = new Thread(counter, "A"); Thread thread2 = new Thread(counter, "B"); thread1.start(); thread2.start();测试结果
从测试结果可以看出来,在A线程运行的时候,B线程也在运行,只不过B线程不执行A线程占据锁的那一段代码,两个线程执行之间不受影响。然而你会看到两次B出现在A:1前面,这就更加说明之间不受影响了,就是我跑我的,你跑你的喽,后面还有一次B先4跑在了A的前面了,哈哈。
测试代码
Account account = new Account("zhang san", 10000.0f); AccountOperator accountOperator = new AccountOperator(account); final int THREAD_NUM = 5; Thread threads[] = new Thread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i ++) { threads[i] = new Thread(accountOperator, "Thread" + i); threads[i].start();结果显示
可以看到,对象的每一条线程执行完时候才会进行锁的释放然后下一条进程再继续执行,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。
public void method3(SomeObject obj) { //obj 锁定的对象 synchronized(obj) { // todo } }当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable { private byte[] lock = new byte[0]; // 特殊的instance变量 public void method() { synchronized(lock) { // todo 同步代码块 } } public void run() { } }就是上面看到的Object类实现lock对象,他就是一个不明确的锁对象。。。
这个前面已经用到,不在多说,这里介绍他的两种修饰方式
写法一:
public synchronized void method() { // todo }写法二
public void method() { synchronized(this) { // todo } }其实就是用了代码块和方法修饰,至于结果,其实是一样的撒。。。。。。
在用synchronized修饰方法时要注意以下几点: 1. synchronized关键字不能继承。 虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下: 在子类方法中加上synchronized关键字
class Parent { public synchronized void method() { } } class Child extends Parent { public synchronized void method() { } }在子类方法中调用父类的同步方法
class Parent { public synchronized void method() { } } class Child extends Parent { public void method() { super.method(); } }所以在书写的时候要么在子类中加上synchronized关键字,要么利用super实现继承关系,直接调用或者重写不会同步他的同步锁过程的,就这样了。
静态方法属于类方法,所以对静态方法进行修饰就是对所有的对象进行同步,不信?看下面的代码喽。。。。。
package thread; /** * ⊙﹏⊙&&&&&&⊙▽⊙ * * @Auther: pangchenbo * @Date: 2019/11/3 00:17 * @Description: */ public class Counter1 implements Runnable { private static int count; public Counter1() { count = 0; } public synchronized static void method() { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void run() { method(); } }测试代码
Counter1 counter11 = new Counter1(); Counter1 counter12 = new Counter1(); Thread thread = new Thread(counter11,"线程1"); Thread thread3 = new Thread(counter12,"线程2"); thread.start(); thread3.start();结果展示
可以看到线程共享了count变量,用了同一把锁,并且线程之间不发生冲突,而且是两个对象的线程。。。。神奇撒!
用法:
class ClassName { public void method() { synchronized(ClassName.class) { // todo } } }代码演示
package thread; /** * ⊙﹏⊙&&&&&&⊙▽⊙ * * @Auther: pangchenbo * @Date: 2019/11/3 00:35 * @Description: */ public class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public static void method() { synchronized(SyncThread.class) { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public synchronized void run() { method(); } }其实这个和静态的的修饰是一样的,都是所有对象公用一个锁。。。。。。
