1、通过同步机制,来解决线程的安全问题
1.1、方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码
}
说明:
- 操作共享数据的代码,即为需要被同步的代码 ——>不能包含代码多了,也不能包含代码少了
- 共享数据:多线程共同操作的变量。比如 ticket就是共享数据
- 同步监视器:俗称 锁,任何一个类的对象,都可以来充当锁
- 要求:多个线程必须要共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
package com.haiyang.java;/*** @author 杨磊* @create 2021-09-24 22:35*/class Window1 implements Runnable{private int ticket = 100;private Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (this){if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);ticket--;} else {System.out.println("票已经卖完了");break;}}}}}public class WindowTest1 {public static void main(String[] args) {Window1 w1 = new Window1();Thread t1 = new Thread(w1);Thread t2 = new Thread(w1);Thread t3 = new Thread(w1);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}}
/*** 使用同步代码块解决继承Thread类的方式的线程安全问题** 例子:创建三个c窗口卖票,总票数为100张*/class Windows extends Thread{private static int ticket = 100;private static Object obj = new Object();@Overridepublic void run() {while(true){//正确的// synchronized (obj) {synchronized (Windows.class){ //Class clazz = Windows.class//错误的,因为此时this表示的是t1,t2,t3三个对象// synchronized (this) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":卖票,票号为: " + ticket);ticket--;} else {break;}}}}}public class WindowsTest2 {public static void main(String[] args) {Windows t1 = new Windows();Windows t2 = new Windows();Windows t3 = new Windows();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}}
1.2、方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
关于同步方法的总结
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法,同步监视器是 :this
- 静态的同步方法,同步监视器是 : 当前类本身
同步的方式,解决了线程的安全问题。—-好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。—-局限性
package com.haiyang.java;/*** 使用同步方法解决实现Runnable接口的线程安全问题* @author 杨磊* @create 2021-09-25 12:47*/public class WindowTest3 {public static void main(String[] args) {Window2 w2 = new Window2();Thread t1 = new Thread(w2);Thread t2 = new Thread(w2);Thread t3 = new Thread(w2);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}}class Window2 implements Runnable{private int ticket = 100;boolean flag = true;@Overridepublic void run() {while (flag){show();}}private synchronized void show() { //同步监视器是 thisif (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "买票,票号为" + ticket);ticket--;}else {flag = false;}}}
package com.haiyang.java;/*** 使用同步方法完成对继承Thread类的线程安全问题* @author 杨磊* @create 2021-09-25 12:58***关于同步方法的总结* 1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明* 2、非静态的同步方法,同步监视器是 :this* 静态的同步方法,同步监视器是 : 当前类本身**/public class WindowTest4 {public static void main(String[] args) {Window4 w1 = new Window4();Window4 w2 = new Window4();Window4 w3 = new Window4();w1.setName("窗口一");w2.setName("窗口二");w3.setName("窗口三");w1.start();w2.start();w3.start();}}class Window4 extends Thread{private static int ticket = 100;static boolean flag = true;@Overridepublic void run() {while (flag){show();}}private static synchronized void show() { //同步监视器 Window4.classif (ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "买票,票号为" + ticket);ticket--;}else {flag = false;}}}
1.3、方式三:lock锁
- 实例化
ReentrantLock对象 - 调用锁定方法lock()
- 调用解锁方法:unlock()
注意:如果同步代码有异常,要将unlock()写入finally语句块
import java.util.concurrent.locks.ReentrantLock;/*** 解决线程安全的方式三: lock锁 ---JDK5新增* @author 杨磊* @create 2021-09-25 15:20*/public class LockTest {public static void main(String[] args) {Window w1 = new Window();Thread t1 = new Thread(w1);Thread t2 = new Thread(w1);Thread t3 = new Thread(w1);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}}class Window implements Runnable{private int ticket = 100;//1、实例化 ReentrantLockprivate ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){try {//2、调用锁定方法lock()lock.lock();if (ticket > 0){Thread.sleep(100);System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);ticket--;}else {System.out.println("票已经卖完了");break;}} catch (InterruptedException e) {e.printStackTrace();} finally {//3、调用解锁方法 : unlock()lock.unlock();}}}}
1.4、面试题:synchronized 与 Lock的异同?
相同:
- 二者都可以解决线程安全问题
不同:
- synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
- Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
1.5、优先使用顺序:
Lock —-> 同步代码块(已经进入了方法体,分配了相应资源)——> 同步方法(在方法体之外) ```java package com.haiyang.exer;
import java.util.concurrent.locks.ReentrantLock;
/**
- @author 杨磊
@create 2021-09-25 15:40 */ public class AccountTest { public static void main(String[] args) {
Account acct = new Account(0);Customer c1 = new Customer(acct);Customer c2 = new Customer(acct);c1.setName("甲用户");c2.setName("乙用户");c1.start();c2.start();
} } class Account { private static ReentrantLock lock = new ReentrantLock(); private double balance;
public Account(double balance) {
this.balance = balance;
}
public synchronized void deposit(double amt){
if (amt > 0){balance += amt;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "存钱成功,余额为" + balance);}
} } class Customer extends Thread{ private Account acct;
public Customer(Account acct) {
this.acct = acct;
}
@Override public void run() {
for (int i = 0; i < 3; i++) {acct.deposit(1000);}
} }
<a name="RCmLi"></a># 2、线程安全的单例模式之懒汉式```java/*** 使用同步机制将单例模式中的懒汉式改写为线程安全的*/public class BankTest {}class Bank{private Bank(){}private static Bank instance = null;public static Bank getInstance(){//方式一:效率稍差//快捷键:Alt+Shift+Z// synchronized (Bank.class) {// if(instance == null){// instance = new Bank();// }// return instance;// }//方式二:效率较高if(instance == null) {synchronized (Bank.class) {if (instance == null) {instance = new Bank();}}}return instance;}}
3、死锁的问题
- 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
我们使用同步时,要避免出现死锁。 ```java /**
- 演示线程的死锁 *
- 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
- 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 2.说明:
- 》出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
》我们使用同步时,要避免出现死锁。 */ public class ThreadTest { public static void main(String[] args) {
StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer();
new Thread(){ @Override public void run() {
synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}
} }.start();
new Thread(new Runnable() { @Override public void run() {
synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}
} }).start(); } }
```javaclass A {public synchronized void foo(B b) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了A实例的foo方法"); // ①try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用B实例的last方法"); // ③b.last();}public synchronized void last() {System.out.println("进入了A类的last方法内部");}}class B {public synchronized void bar(A a) {System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 进入了B实例的bar方法"); // ②try {Thread.sleep(200);} catch (InterruptedException ex) {ex.printStackTrace();}System.out.println("当前线程名: " + Thread.currentThread().getName()+ " 企图调用A实例的last方法"); // ④a.last();}public synchronized void last() {System.out.println("进入了B类的last方法内部");}}public class DeadLock implements Runnable {A a = new A();B b = new B();public void init() {Thread.currentThread().setName("主线程");// 调用a对象的foo方法a.foo(b);System.out.println("进入了主线程之后");}public void run() {Thread.currentThread().setName("副线程");// 调用b对象的bar方法b.bar(a);System.out.println("进入了副线程之后");}public static void main(String[] args) {DeadLock dl = new DeadLock();new Thread(dl).start();dl.init();}}
