前言

本系列记录Java从入门开始的知识点,本文介绍线程同步:什么是线程同步、线程不安全的例子、同步块、JUC、死锁和Lock锁。

文章目录

  • 前言
  • 一、什么是线程同步
  • 二、线程不安全的例子
  • 三、同步块
  • 四、扩展:JUC
  • 五、死锁
  • 六、Lock锁


一、什么是线程同步

  1. 在处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时就需要线程同步。线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象等待池形成对列,等待前面的线程使用完毕,下一个线程再使用。
  2. 为了实现线程的安全性,需要使用对列+锁。
  3. 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
  4. 线程同步存在的问题:
    (1)一个线程持有锁会导致其他所有需要此锁的线程挂起;
    (2)在多线程竞争下,加锁,释放锁,会导致比较多的上下文切换和调度延时,引起性能问题;
    (3)如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题;

二、线程不安全的例子

  1. 不安全抢票:
//不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"小红").start();
        new Thread(buyTicket,"小明").start();
    }
}

class BuyTicket implements Runnable{

    private int ticketNums = 10;
    boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public synchronized void buy(){
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
    }
}

java 多个线程共享数据 java多线程数据同步_System

  1. 不安全取钱:
package Thread.syn;

import oop.demo08.Action;

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing boyFriend = new Drawing(account, 100, "boyFriend");

        you.start();
        boyFriend.start();
    }
}

//账户
class Account{
    int money;
    String name;

    public Account(int money,String name) {
        this.money = money;
        this.name = name;
    }
}

class Drawing extends Thread{
    Account account;
    int drawingMoney;
    int nowMoney;
    String name;

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;

    }

    @Override
    public void run() {
        if(account.money - drawingMoney < 0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money = account.money - drawingMoney;
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        System.out.println(this.getName()+"手里的钱为:"+nowMoney);
    }
}

java 多个线程共享数据 java多线程数据同步_线程同步_02

  1. 不安全列表:
import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

java 多个线程共享数据 java多线程数据同步_System_03

三、同步块

java 多个线程共享数据 java多线程数据同步_线程同步_04

  1. 解决不安全买票:
public synchronized void buy(){...}

java 多个线程共享数据 java多线程数据同步_线程同步_05


2. 解决不安全取钱

public void run() {
    synchronized (account) {
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
            return;
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money = account.money - drawingMoney;
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name + "余额为:" + account.money);
        System.out.println(this.getName() + "手里的钱为:" + nowMoney);
    }
}

java 多个线程共享数据 java多线程数据同步_System_06

  1. 解决不安全列表
new Thread(()->{
    synchronized (list){
        list.add(Thread.currentThread().getName());
    }
}).start();

四、扩展:JUC

package Thread.syn;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

五、死锁

死锁就是两个对象手中都持有对方需要的资源,谁都不先放手,就会产生死锁。

package Thread.syn;

public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0,"灰姑凉");
        Makeup g2 = new Makeup(1,"白雪公主");

        g1.start();
        g2.start();
    }
}

class Lipstick{

}

class Mirror{

}

class Makeup extends Thread{
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;
    String girlName;

    Makeup(int choice,String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void makeup() throws InterruptedException {
        if(choice == 0){
            synchronized (lipstick){
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlName+"获得镜子的锁");
                }
            }
        }else{
            synchronized (mirror){
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(1000);
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得口红的锁");
                }
            }
        }
    }
}

java 多个线程共享数据 java多线程数据同步_java_07


java 多个线程共享数据 java多线程数据同步_System_08

六、Lock锁

  1. Lock锁的使用方式
  2. 解决不安全买票:
package Thread.syn;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2,"小红").start();
        new Thread(testLock2,"小明").start();
        new Thread(testLock2,"小蓝").start();
    }

}

class TestLock2 implements Runnable{
    int ticketNums = 100;
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                if(ticketNums<=0){break;}
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"拿到第"+ticketNums--+"张票");
            } finally {
                lock.unlock();
            }
        }
    }
}
  1. synchronized和Lock的对比

java 多个线程共享数据 java多线程数据同步_开发语言_09