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();
@Override
public 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();
@Override
public 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;
@Override
public void run() {
while (flag){
show();
}
}
private synchronized void show() { //同步监视器是 this
if (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;
@Override
public void run() {
while (flag){
show();
}
}
private static synchronized void show() { //同步监视器 Window4.class
if (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、实例化 ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public 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(); } }
```java
class 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();
}
}