Java-线程同步
在Java中,我们通过同步机制,来解决线程的安全问题。
实现线程安全的三种方法:
1.同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
-
操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
-
共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
-
同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
代码举例:
继承Thread类的多线程同步代码块的实现
package com.lmw.thred1;
/**
* @author
* @version 1.0.0
* @createTime 2022/5/14 11:03
* @description 使用同步代码块来处理继承Thread类时的线程安全问题
*/
public class WindowTest1 {
public static void main(String[] args) {
// 创建子线程1的对象
MyWindow m1 = new MyWindow();
MyWindow m2 = new MyWindow();
MyWindow m3 = new MyWindow();
// 设置子线程1 的名称
m1.setName("线程1");
// 启动子线程1
m1.start();
m2.setName("线程2");
m2.start();
m3.setName("线程3");
m3.start();
}
}
class MyWindow extends Thread {
// 通过 声明静态变量的方式实现不同对象控制对一个变量
private static int ticket = 3;
@Override
public void run() {
while (true){
// 使用synchronized 锁住同一个对象,这个对象对应调用它的对象要唯一,**多个线程必须要共用同一把锁
synchronized (MyWindow.class){
// 操作共享数据的代码块,被锁包住
if(ticket > 0) {
System.out.println(getName() + "卖票,票号为:" + ticket);
ticket --;
}else {
break;
}
}
}
}
}
实现Runnable接口的多线程同步代码块的实现
package com.lmw.thred1;
/**
* @author
* @version 1.0.0
* @createTime 2022/5/14 11:27
* @description 使用同步代码块解决实现Runnable接口的线程安全问题
*/
public class Window2 {
public static void main(String[] args) {
MyWindow2 m2 = new MyWindow2();
// 创建多个线程时不需要再创建多个对象
Thread t1 = new Thread(m2);
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(m2);
t2.setName("线程2");
t2.start();
Thread t3 = new Thread(m2);
t3.setName("线程3");
t3.start();
}
}
class MyWindow2 implements Runnable {
private int ticket = 3;
@Override
public void run() {
while (true) {
// 此时this 是唯一的 MyWindow2 对象
synchronized (this) {
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket --;
}else {
break;
}
}
}
}
}
2.同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
在方法体声明时加上 synchronized ,特别地继承Thread类是需要将操作共享数据的方法声明为静态的
说明:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:
this
- 静态的同步方法,同步监视器是:当前类本身
类.class
代码举例:
继承Thread类的多线程同步方法的实现
package com.lmw.thred1;
/**
* @author limingwei6
* @version 1.0.0
* @createTime 2022/5/14 14:04
* @description 使用同步方法来处理继承Thread类时的线程安全问题
*/
public class WindowTest3 {
public static void main(String[] args) {
MyWindow3 m1 = new MyWindow3();
m1.setName("线程1");
m1.start();
MyWindow3 m2 = new MyWindow3();
m2.setName("线程1");
m2.start();
MyWindow3 m3 = new MyWindow3();
m3.setName("线程1");
m3.start();
}
}
class MyWindow3 extends Thread {
private static int ticket = 3;
boolean flag = true;
@Override
public void run() {
while (flag) {
flag = show();
}
}
private static synchronized boolean show() {// 同步监视器 MyWindow3.class
// private synchronized boolean show() {// 同步监视器有三个: t1,t2,t3,这样不能解决线程同步问题
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket --;
return true;
}else {
return false;
}
}
}
实现Runnable接口多线程方法的实现
package com.lmw.thred1;
/**
* @author
* @version 1.0.0
* @createTime 2022/5/14 14:05
* @description 使用同步方法解决实现Runnable接口的线程安全问题
*/
public class WindowTest4 {
public static void main(String[] args) {
MyWindow4 m1 = new MyWindow4();
Thread t1 = new Thread(m1);
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(m1);
t2.setName("线程2");
t2.start();
Thread t3 = new Thread(m1);
t3.setName("线程3");
t3.start();
}
}
class MyWindow4 implements Runnable {
private int ticket = 3;
@Override
public void run() {
while (true) {
if (ticket > 0) {
show();
}else {
break;
}
}
}
// 操作共享数据的方法在一个方法中, 在声明方法时加 synchronized 给这个方法加锁
private synchronized void show() {// 同步监视器是this
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket --;
}
}
}
3.Lock锁
package com.lmw.thred1;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author
* @version 1.0.0
* @createTime 2022/5/14 14:41
* @description 使用Lock接口的方式解决多线程的线程同步安全
*/
public class LockTest {
public static void main(String[] args) {
MyWindow5 m2 = new MyWindow5();
// 创建多个线程时不需要再创建多个对象
Thread t1 = new Thread(m2);
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(m2);
t2.setName("线程2");
t2.start();
Thread t3 = new Thread(m2);
t3.setName("线程3");
t3.start();
}
}
class MyWindow5 implements Runnable {
private int ticket = 3;
//1. 声明一个 实现Lock接口的类 ReentrantLock 的对象 ,可以接收参数,true表示平均分配
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//2.调用lock.lock 加锁方法
lock.lock();
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket --;
}else {
break;
}
}finally {
//3.解锁
lock.unlock();
}
}
}
}
4.线程同步的利弊:
- 同步的方式,解决了线程的安全问题。
- 操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
5.三种方式对比
synchronized 与 Lock的异同?
使用的优先顺序:
- Lock —> 同步代码块(已经进入了方法体,分配了相应资源 ) —> 同步方法(在方法体之外)