文章目录
线程同步
概述
并发与并行
1、并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事情。
2、并发:一个CPU(采用时间片)同时执行多个任务,同一个对象被多个线程操作。比如:买票,取款等等。
多线程同步
1、多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有个先来后到。
2、同步 = 排队 + 锁
3、几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作。
4、为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。
实现同步
锁
1、为了确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权力访问该共享资源。
2、一个线程持有锁会导致其它所有需要此锁的线程挂起;在多线程的竞争下,加锁、释放锁会导致比较多的上下文切换和调度延迟时,将引起性能问题。
同步代码块
有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
synchronized(同步监视器){
//需要被同步的代码;
}
代码示例——实现卖票
public class ThreadTicket {
/*这是一个程序入口*/
public static void main(String[] args) {
Ticket t1 = new Ticket();
t1.setName("窗口1");
t1.start();
Ticket t2 = new Ticket();
t2.setName("窗口2");
t2.start();
}
}
class Ticket extends Thread {
//共享资源
static int num = 10;
//同步锁对象
static Object obj = new Object();
@Override
public void run() {
while (true) {
//同步代码块
//同步对象,可以是任意对象,只能是唯一的对象
//当一个线程持有同步锁后,进入同步代码块,当此线程将同步代码块的中的所有内容执行完后才会释放
synchronized (obj){
if (num > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "剩余票数:" + num);
num--;
} else {
System.out.println("票已售完");
break;
}
}
}
}
}
同步方法
synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
public synchronized void show(String name){
//需要同步的代码;
}
代码示例——实现卖票
public class RunnableTicket {
/*这是一个程序入口*/
public static void main(String[] args) {
TicketWindow ticket = new TicketWindow();
Thread t1 = new Thread(ticket, "窗口1");
t1.start();
Thread t2 = new Thread(ticket, "窗口2");
t2.start();
}
}
class TicketWindow implements Runnable{
int num = 10;
//同步方法---只能在实现Runnable接口的类中实现
public synchronized void printNum(){
if (num > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "剩余票数:" + num);
num--;
}
}
@Override
public void run() {
while(true){
if (num == 0) {
System.out.println("票已售完");
break;
}
printNum();
}
}
}
注意:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
同步监视器
同步监视器可以是任何对象,必须保证唯一,保证多个线程获得同一个对象(锁)
同步监视器的执行过程
1、第一个线程访问,锁定同步监视器,执行其中的代码。
2、第二个线程访问,发现同步监视器被锁定,无法访问。
3、第一个线程访问完毕,解锁同步监视器。
4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
线程死锁
出现死锁的前提
1、多线程。
2、线程同步。
死锁描述
1、不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己所需要的同步资源,这就形成了线程的死锁。
2、出现死锁后,不会出现异常,也不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
避免死锁
1、让程序每次至多获得一个锁。当然,在多线程环境下,这种情况通常是不现实的。
2、设计是要考虑清楚锁顺序,尽量减少嵌套的加锁交互数量。
代码示例——出现死锁
public class DealLockDemo {
/*这是一个程序入口*/
public static void main(String[] args) {
DealLock t1 = new DealLock(true);
DealLock t2 = new DealLock(false);
t1.start();
t2.start();
}
}
class DealLock extends Thread {
boolean flag;
public DealLock(boolean flag) {
this.flag = flag;
}
//锁objA
static Object objA = new Object();
//锁objb
static Object objb = new Object();
@Override
public void run() {
/*
* 当线程t1运行拿到锁objA进入if后,
* 线程t2运行拿到锁objb进入else,
* 此时if和else中被嵌套的锁就不能够拿到相应的钥匙,这就陷入了死锁
*/
if (flag){
synchronized (objA) {
System.out.println("if objA");
synchronized (objb){
System.out.println("if objb");
}
}
}else{
synchronized (objb) {
System.out.println("else objb");
synchronized (objA) {
System.out.println("else objA");
}
}
}
}
}
Lock(锁)
概述
1、从JDK5.0开始,Java就提供了更强大的线程同步机制-通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
3、锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
4、ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReenactmentLock,可以显示的加锁、释放锁。
5、Lock锁,使用时手动获取锁和释放锁,比synchronized更加灵活,可以中断的获取锁,也可以超时获取锁。
Lock的使用
1、lock():获得锁。
2、unlock():释放锁。
Lock锁最基本的用法就是lock()方法上锁,unlock()方法解锁。
3、lockInterruptibly():获得锁,可以中断。线程AB同时通过此方法获得某个锁时,如果线程A获得锁了,按理说线程B只能等待,但是这里的线程B可以调用interrupt()方法中断线程B的等待。
4、tryLock():获取空闲的锁,没有获取到锁不会等待。
代码示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 蔡伟东
* @create 2020-12-15 22:54
* @function
*/
public class LockDemo {
/*这是一个程序入口*/
public static void main(String[] args) {
TicketWindow ticket = new TicketWindow();
Thread t1 = new Thread(ticket, "窗口1");
t1.start();
Thread t2 = new Thread(ticket, "窗口2");
t2.start();
}
}
class TicketWindow implements Runnable{
int num = 10;
//创建一个锁对象
Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
try{
/*上锁*/
l.lock();
if (num > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "剩余票数:" + num);
num--;
}else {
System.out.println("票已售完");
break;
}
}finally {
/*解锁*/
l.unlock();
}
}
}
}
Lock和synchronized的区别
1、Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放。
2、Lock只有代码块锁,synchronized有代码块锁和方法锁,使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且Lock具有更好的扩展性(提供更多子类)。