线程同步

卖票案例

需求:有100张票,有3个窗口,设计程序模拟买票

安全问题

原因:

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

解决:

  • 把多条语句操作共享数据的代码锁起来,让任意时刻只能有一个线程执行

同步代码块

synchronized(任意对象){
	多条语句操作共享数据的代码
}
package com.thread.sellTicket;

//1.定义一个类SellTicket实现Runnable接口
public class SellTicket implements Runnable {
    private int ticket = 100;
    private Object obj =new Object();

    //2.重写run()方法
    @Override
    public void run() {
        while (true){
            synchronized (obj){
                if (ticket > 0) {
                    //通过slep()方法来模拟出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                    ticket--;
                }
            }

        }
    }
}
package com.thread.sellTicket;
/*
    需求:有100张票,有3个窗口,设计程序模拟买票

 */
public class SellTicketDemo {
    public static void main(String[] args) {
        //3.1创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //3.2创建3个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应窗口名称
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        //3.3启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

同步方法

同步方法

修饰符 synchronized 返回值类型 方法名(方法参数){
	多条语句操作共享数据的代码
}

锁的对象是 this

同步静态方法

修饰符 static synchronized 返回值类型 方法名(方法参数){
	多条语句操作共享数据的代码
}

锁的对象是 类名.class

案例

package com.thread.sellTicket;

//1.定义一个类SellTicket实现Runnable接口
public class SellTicket implements Runnable {
//    private int ticket = 100;
    private static int ticket = 100;
    private Object obj =new Object();
    private int x = 0;

    //2.重写run()方法
    @Override
    public void run() {
        while (true){
            if (x%2==0){
//                synchronized (obj){
//                synchronized (this){
                synchronized (SellTicket.class){
                    if (ticket > 0) {
                        //通过slep()方法来模拟出票时间
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                        ticket--;
                    }
                }
            }else {
//                synchronized (obj){
//                    if (ticket > 0) {
//                        //通过slep()方法来模拟出票时间
//                        try {
//                            Thread.sleep(100);
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
//                        ticket--;
//                    }
//                }
                sellTicket();
            }
            x++;

        }
    }

//    private void sellTicket() {
//        synchronized (obj){
//            if (ticket > 0) {
//                //通过slep()方法来模拟出票时间
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
//                ticket--;
//            }
//        }
//    }
    
//    private synchronized void sellTicket() {
//            if (ticket > 0) {
//                //通过slep()方法来模拟出票时间
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
//                ticket--;
//            }
//        }
    
    private static synchronized void sellTicket() {
        if (ticket > 0) {
            //通过slep()方法来模拟出票时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
            ticket--;
        }
    }

}
package com.thread.sellTicket;
/*
    需求:有100张票,有3个窗口,设计程序模拟买票

 */
public class SellTicketDemo {
    public static void main(String[] args) {
        //3.1创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //3.2创建3个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应窗口名称
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        //3.3启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

线程安全的类

   
StringBuffer 1. 线程安全,可变的字符序列。 2. 从版本JDK 5开始,这个类已经被一个等同的类补充了,它被设计为使用一个线程, StringBuilder 。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步。
Vector 从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable 1.该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键值或值。 2.从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 。 如果需要线程安全高度并发的实现,那么建议使用ConcurrentHashMap代替Hashtable 。
package com.thread.sellTicket;

import java.util.*;

public class ThreadDemo {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        StringBuilder sb2 = new StringBuilder();

        Vector<String> v = new Vector<>();
        ArrayList<String> array = new ArrayList<>();

        Hashtable<String, String> ht = new Hashtable<>();
        HashMap<String, String> hm = new HashMap<>();

        //static <T> List<T> synchronizedList(List<T> list) 返回由指定列表支持的同步(线程安全)列表
        List<String> list = Collections.synchronizedList(new ArrayList<String>());
    }
}

Lock锁

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。

  • void lock():获得锁
  • void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

​ 构造方法:public ReentrantLock():创建一个ReentrantLock的实例。

package com.thread.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
    private int ticket = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try{
                lock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                    ticket--;
                }
            }finally {
                lock.unlock();
            }

        }
    }
}
package com.thread.lock;

/*
    需求:有100张票,有3个窗口,设计程序模拟买票

 */
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}