线程同步
多个线程操作同一个资源
并发
同一个对象被多个线程同时操控
线程同步其实就是一种等待机制(类似于排队),多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
线程同步条件:队列 + 锁
锁举例:厕所,前一个人上厕所要锁门,等前一个人解决完了才能让下一个人进来厕所
锁机制(synchronized):当一个线程操作一个对象的时候,会获得对象的排它锁,独占资源,其他线程必须等待,当前线程使用完释放锁即可

三大不安全案例
略过,反正就是讲线程不同步就会不安全
同步方法以及同步块(隐式锁)
同步方法:使用synchronized关键字
public synchronized void method(int args){}
同步块:使用synchronized块
每个对象都有一把锁,每个synchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就会独占该锁,直到该方法返回才释放锁,后面的线程才能获得这把锁,才能执行
缺陷:将一个大的方法申明为synchronized效率将会降低

只读的代码无需使用同步方法,因为读数据并不会造成线程不安全,要修改数据的代码则需要使用同步方法,只选择有必要的地方使用同步方法,可以提高效率,避免浪费
修改案例(原本不安全买票)
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "hong").start();
new Thread(buyTicket, "ming").start();
new Thread(buyTicket, "huang").start();
}
}
class BuyTicket implements Runnable{
//票数
private int ticketNums = 10;
//外部停止标识
boolean flag = true;
//外部停止方法
public void stop(){
this.flag = false;
}
@Override
public void run() {
while (flag){
buy();
}
}
//synchronized同步锁,导致三个线程没办法同时操作一个对象,保证了安全性
private synchronized void buy(){
if (ticketNums <= 0){
stop();
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}

解决了前面那一章不安全的情况
死锁
发生原因:两个线程都独自占有一些资源,然后线程A想要线程B的资源,线程B想要线程A的资源,结果就僵持住了,程序停止运行
某一个同步块同时拥有两个以上对象的锁时,就有可能发生死锁问题
解析:下面这个例子中,hong获得了pencil的锁等1秒过后并没有释放pencil的锁,而是想要继续获得eraser的锁,这肯定是不行的,因为ming已经获得了eraser的锁,并且这个ming还想要pencil的锁,两个线程互相抢对方的资源,就死锁了
public class DeadLock {
public static void main(String[] args) {
DoHomeWork t1 = new DoHomeWork(0,"hong");
DoHomeWork t2 = new DoHomeWork(1,"ming");
t1.start();
t2.start();
}
}
class Pencil{
}
class Eraser{
}
class DoHomeWork extends Thread{
//保证资源只有一份
static Pencil pencil = new Pencil();
static Eraser eraser = new Eraser();
int choice;
String name;
//继承Thread类要手动写构造器
DoHomeWork(int choice, String name){
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
doYourHomeWork();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void doYourHomeWork() throws InterruptedException {
if(choice == 0){
synchronized (pencil){
System.out.println(this.name + "获得笔的锁");
Thread.sleep(1000);
synchronized (eraser){
System.out.println(this.name + "获得橡皮擦的锁");
}
}
}else {
synchronized (eraser){
System.out.println(this.name + "获得橡皮擦的锁");
Thread.sleep(2000);
synchronized (pencil){
System.out.println(this.name + "获得笔的锁");
}
}
}
}
}
解决方法:把同步块分开写即可
synchronized (pencil){
System.out.println(this.name + "获得笔的锁");
Thread.sleep(1000);
}
synchronized (eraser){
System.out.println(this.name + "获得橡皮擦的锁");
}
这样sleep时间一到,就自动释放锁pencil锁,也就不会发生互相抢资源的现象了,之前是嵌套写,时间到了还是不会释放锁
LOCK锁(显示定义同步锁)
前面写的同步方法和同步块利用的是synchronized 关键字,这是隐式定义锁,出了作用域会自动释放锁,
而LOCK锁需要手动开启锁和关闭锁
首先要定义一个LOCK对象
ReentrantLock---可重入锁
private final ReentrantLock lock = new ReentrantLock();
加锁方法---写在try语块里
lock.lock();
解锁方法---写在finally语块里
lock.unlock();
需要在重写run方法里面使用
例子的话就用上面同步方法修改案例(原本不安全买票)的例子,修改了run方法,以及加了几个语句
@Override
public void run() {
while (flag){
try {
lock.lock();//建议使用try-catch语块
buy();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
补充:要执行的代码如果没有异常就写finally里,有异常写try里