线程同步

59 阅读4分钟

当多个线程同时请求一个数据的时候,会导致数据不准确的情况。相互之间产生影响,容易出现线程安全的问题。比如多个线程操作同一个数据,都打算修改商品库存。

线程的同步真实的意思: 其实就是让你"排队",几个线程之间要排队。一个一个对共享资源进行操作。等一个结束以后,另外一个再进来操作,而不是同时进行操作。确保变量 是唯一的、准确的。

那么怎么实现线程同步呢

可以加锁

1.一个售票案例

package com.qf.c_Lock;
​
class SaleTicket implements Runnable{
    private int ticket = 100; 
    @Override
    public void run() {
        while(true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
                ticket--;
            }else{
                System.out.println("卖完啦");
                break;
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //强调的是:多个线程操作同一个数据 ticket
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket,"线程一").start();
        new Thread(saleTicket,"线程二").start();
    }
}

两个线程都可以对ticket进行操作,售票,但是出现了问题,两个线程都不能即时更新已经被对方修改过的共享资源,线程是不安全的

试着去解决

2.同步方法

方案一:同步方法
public synchronized void eat(){
//synchronized修饰方法,锁方法
}
package com.qf.c_Lock;
​
class SaleTicket implements Runnable{
    private int ticket = 100; 
    @Override
    public synchronized void run() {
        while(true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
                ticket--;
            }else{
                System.out.println("卖完啦");
                break;
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //强调的是:多个线程操作同一个数据 ticket
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket,"线程一").start();
        new Thread(saleTicket,"线程二").start();
    }
}

使用synchronized修饰run()方法,结果发现太极端:

一旦某一个线程抢先执行,它自己就把票卖完了,别的线程等不到资源

3.同步代码块

方案二:同步代码块
synchronized(this){
​
}
将一段代码放到{}中,然后用synchronized修饰,就会对这段代码上锁
package com.qf.c_Lock;
​
class SaleTicket implements Runnable{
    private int ticket = 100; 
    @Override
    public void run() {
        while(true){
            synchronized (this){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
                    ticket--;
                }else{
                    System.out.println("卖完啦");
                    break;
                }
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //强调的是:多个线程操作同一个数据 ticket
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket,"线程一").start();
        new Thread(saleTicket,"线程二").start();
    }
}

这次将锁加到买票的核心代码if-else语句上,既可以实现线程同步(线程排队),又不会锁的太极端。

那能不能不用同步代码块而采用同步方法实现线程同步的卖票呢?

当然可以

4.同步方法再尝试

package com.qf.c_Lock;
​
class SaleTicket implements Runnable{
    private int ticket = 100; 
    @Override
    public void run() {
        while(true){
            test();
            if(ticket<=0){
                System.out.println("票卖完啦");
                break;//这里需要加break结束循环,否则会一直输出票卖完啦
            }
        }
    }
    public synchronized void test(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票");
            ticket--;
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //强调的是:多个线程操作同一个数据 ticket
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket,"线程一").start();
        new Thread(saleTicket,"线程二").start();
    }
}

这里把卖票的核心代码单独拿出来放到一个自己声明的方法中用synchronized修饰,然后调用

5.Java中的锁

1.synchronized

被称为隐式锁,会自动释放,是一个非公平的锁

2.Lock

被称为显示锁,是一个接口,实现ReentrantLock

这两个锁都可以解决线程同步问题,但synchronized更加强大,更加粒度化,更加灵活。所以开发时一般用synchronized 。

以后还会有线程池。线程池也有锁,更高级。

Lock有两个重要的方法:

lock();
unlock();
//要成对使用
package com.qf.c_Lock;
​
import java.util.concurrent.locks.ReentrantLock;
​
class SaleTicket1 implements Runnable{
    int ticket = 100;
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            try{
                lock.lock();
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
                    ticket--;
                }else{
                    System.out.println("票卖完啦");
                }
            }catch (Exception e){
​
            }finally {
                lock.unlock();
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread thread1 = new Thread(saleTicket,"线程一");
        thread1.start();
        Thread thread2 = new Thread(saleTicket,"线程二");
        thread2.start();
    }
}

6.守护线程【了解】

守护线程是用来守护非守护线程的。

package com.qf.c_Lock;
​
class MyThread8 implements Runnable{
    @Override
    public void run() {
        for(int i =0; i<500;i++){
            System.out.println("守护线程"+i);
        }
    }
}
public class Demo3 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();//当前线程是main
        System.out.println(thread.isDaemon());//false 非守护线程
        Thread thread1 = new Thread(new MyThread8());
        thread1.setDaemon(true);//将thread1设置为守护线程
        thread1.start();
        for(int i = 0;i<50; i++){
            System.out.println("主线程:"+i);
        }
    }
}

当程序中只剩下守护线程时,哪怕守护线程没有执行完,代码也会自己结束。

完结,撒花~