Day19 Thread2(线程安全、Lock接口、守护线程、线程池)

1、线程安全

为什么会出现线程安全问题?

  • 需求:A线程将“Hello”存入数组;B线程将“World”存入数组。
  • 线程不安全:
    • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
    • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
1.1 解决线程安全问题-同步锁

同步锁的关键词是synchronized。
有两种方式
1.同步代码块:在同一个时间里只允许一条线程进入同步代码块,当代码块运行完毕后,其他线程才会被CPU调度,必须保证监视器为同一个对象。

语法:

synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}

注意:

package Day19_thread2.safe;/*
        车站售票
        解决线程安全的问题:
            1.同步锁:synchronized,是一个关键词来的
                1.1同步代码块:
                    在同一个时间里只允许一条线程进入同步代码块,当代码块运行完毕后,其他线程
                    才会被CPU调度,必须保证监视器为同一个对象
                1.2同步方法:
                    被synchronized修饰的方法为同步方法,同一时间只允许一条线程调用,当同步方法
                    执行完后,其他线程才会被CPU调度,监视器为方法本身。要求保证调用同一个方法,
                    可以使用静态方法或者保证对象为同一个。
 */

public class Demo01 {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            Thread thread = new Thread(new Task("窗口"+i));
            thread.start();
            //new Thread(new Task("窗口00"+i)).start();
        }
    }
}
class Task implements Runnable{
    private static int ticket = 1000;
    private static final Object obj = new Object();//静态属性的Object()
    private String name;
    public Task(String name){
        this.name = name;
    }

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

    private void safe() throws InterruptedException {
        while (true){//死循环
            Thread.sleep(1);
            synchronized (obj){
                if (ticket > 0){
                    System.out.println(name+"正在售票"+ticket--);
                }else {
                    System.out.println(name+"售票结束!!");
                    break;
                }
            }
        }
    }
}

2.同步方法:被synchronized修饰的方法为同步方法,同一时间只允许一条线程调用,当同步方法执行完后,其他线程才会被CPU调度,监视器为方法本身。要求保证调用同一个方法,可以使用静态方法或者保证对象为同一个。

语法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
// 代码(原子操作)
}

注意:

package Day19_thread2.safe;/*
    使用同步方法,每一个线程实现的都不是同一个方法,方法为静态方法
    同步方法:被synchronized修饰的方法为同步方法,同一时间只允许一条线程调用,当同步方法执行完后,其他线程
    才会被CPU调度,监视器为方法本身。要求保证调用同一个方法,可以使用静态方法或者保证对象为同一个。
 */

public class Demo02 {
    public static void main(String[] args) {
        for (int i = 1; i < 11; i++) {
            new Thread(new Task2(),"窗口"+i).start();
        }
    }
}
class Task2 implements Runnable {
    private static int ticket = 1000;
    private static final Object obj = new Object();//静态属性的Object()
    private boolean flag = true;

    @Override
    public void run() {
        try {
            while (flag) {
                Thread.sleep(1);
                flag = safe();//定义了全局变量flag,需要赋值给safe(),不然程序会一直运行下去。
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private synchronized static boolean safe() throws InterruptedException {
        if (ticket > 0) {
            //Thread.currentThread().getName()获取程序名
            System.out.println(Thread.currentThread().getName() + "正在售票" + ticket--);
        } else {
            System.out.println(Thread.currentThread().getName() + "售票结束!!");
            return false;
        }
        return true;
    }
}

案例:同步方法的应用

package Day19_thread2.safe;
//同步方法,对象为同一个
public class Demo03 {
    public static void main(String[] args) {
        //任务类对象只有一个
        Task3 task3 = new Task3();
        for (int i = 1; i < 11; i++) {
            //Thread(Thread构造方法,线程名)
            new Thread(task3,"窗口"+i).start();
        }
    }
}
class Task3 implements Runnable{
    private static int ticket = 1000;
    private boolean flag = true;
    @Override
    public void run() {
        try {
            while (flag){
                Thread.sleep(1);
                flag = safe();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //同步方法 --非静态
    private synchronized boolean safe(){
        if (ticket > 0){
            System.out.println(Thread.currentThread().getName()+"正在出售"+ticket--);
        }else {
            System.out.println(Thread.currentThread().getName()+"售票结束!!");
            return false;
        }
        return true;
    }
}
1.2线程通信方法
方法说明
public final void wait()释放锁,进入等待队列
public final void wait(long timeout)在超过指定的时间前,释放锁,进入等待队列
public final void notify()随机唤醒、通知一个线程
public final void notifyAll()唤醒、通知所有线程

注意:所有的等待、通知方法必须在对加锁的同步代码块中。

wait()特点:

  • 1、会使线程进入阻塞状态;
  • 2、必须在synchronized中使用,没有则会出现IllegalMonitorStateException异常;
  • 当线程调用wait()进入阻塞状态的时候,资源会被释放;
  • 当线程调用wait()进入阻塞状态后,没有通过notify()或者notifyAll()唤醒线程,线程就会进入死锁状态。
package Day19_thread2.safe;
public class Demo05 {
    public static final Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
/*      //第一种方法
        new Thread(new Task5()).start();
        Thread.sleep(1000);
        new Thread(new Task5()).start();
        Thread.sleep(1000);
        new Thread(new Task6()).start();*/
        //会陷入阻塞状态
        new Task5().start();
        Thread.sleep(1000);
        //会陷入阻塞状态
        new Task5().start();
        Thread.sleep(1000);
        //唤醒陷入阻塞状态的线程
        new Task6().start();
    }
}
class Task5 extends Thread{
    //private static Object obj =new Object();
    @Override
    public void run() {
        /*try {
            //在synchronized外面使用wait()
            Demo05.obj.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        synchronized (Demo05.obj){
            System.out.println(Thread.currentThread().getName()+"线程开始");
            try {
                //会释放资源
                Demo05.obj.wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"线程结束!");
        }
    }
}
class Task6 extends Thread{
    //private static Object obj =new Object();
    @Override
    public void run() {
        synchronized (Demo05.obj){
            System.out.println(Thread.currentThread().getName()+"线程开始");
            //通过notify()唤醒进入阻塞状态的线程
            //通过notifyAll()唤醒所有进入阻塞状态的线程
            Demo05.obj.notifyAll();
            System.out.println(Thread.currentThread().getName()+"线程结束!");
        }
    }
}

2、Lock接口

2.1 Lock
  • JDK5加入,与synchronized比较,显示定义,结构更灵活。
  • 提供更多实用性方法,功能更强大、性能更优越。

常用方法:

方法名描述
void lock()获取锁,如锁被占用,则等待。
boolean tryLock()尝试获取锁(成功返回true。失败返回false,不阻塞)。
void unlock()释放锁。
package Day19_thread2.lock;
//Lock:互斥对象锁或者排它锁,与同步锁最大的区别是需要显示获取和释放锁,不容易出现死锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo01 {
    public static void main(String[] args) {
        for (int i = 1; i < 11; i++) {
            new Thread(new Task("窗口"+i)).start();
        }
    }
}
class Task implements Runnable{
    private static int ticket = 1000;
    private static String name;
    private static Lock lock = new ReentrantLock();
    public Task(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            safe();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void safe() throws InterruptedException {
        while (true){
            Thread.sleep(1);
            //获取锁
            lock.lock();
            try {
                //wait()只能在synchronized中使用
                //lock.wait();
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"正在出售"+ticket--);
                }else {
                    System.out.println(Thread.currentThread().getName()+"售票结束!!");
                    break;
                }
            }finally{
                //释放锁(关闭)
                lock.unlock();
            }
        }
    }
}

3、守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆。

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),它就是一个很称职的守护者。

User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

package Day19_thread2.daemon;/*
 * @author  pyh
 * @date  2020/12/10 0010 下午 5:40
 * @version 1.0
 * @since JDK1.8_241
        守护线程:当一条线程被设置为守护线程时,其他线程执行完,守护线程则销毁
                 守护线程守护所有的线程
                 垃圾回收机制(GC)也是一条守护线程
 */

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        MyThread1 myThread1 = new MyThread1();
        myThread1.setDaemon(true);
        myThread1.start();
        new MyThread2().start();
        for (int i = 0; i < 150; i++) {
            Thread.sleep(1);
            System.out.println("主线程:"+i);
        }
    }
}
class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("守护线程:"+i);
        }
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程:"+i);
        }
    }
}

案例:

package Day19_thread2.daemon;/*
 * @author  pyh
 * @date  2020/12/10 0010 下午 5:55
 * @version 1.0
 * @since JDK1.8_241
 *      在B线程中设置A为守护线程
 *      到底是B线程结束,A线程跟着结束
 *      还是所有线程结束,A线程结束
 */

public class Demo02 {
    public static void main(String[] args) {
        //A线程的对象
        MyThread3 myThread3 = new MyThread3();
        //B线程
        new MyThread4(myThread3).start();
        //C线程
        new MyThread5().start();
    }
}
//A线程
class MyThread3 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 400; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("守护A子线程:"+i);
        }
    }
}
//B线程
class MyThread4 extends Thread{
    private Thread thread;
    //通过构造方法接收A线程的对象
    public MyThread4(Thread thread){
        this.thread = thread;
    }
    @Override
    public void run() {
        //设置A线程为守护线程
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i < 200; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B子线程:"+i);
        }
    }
}
class MyThread5 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("C子线程:"+i);
        }
    }
}

4、线程池

线程池:节省了线程的创建和销毁

Executor:线程池的顶级接口。

ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。

Executors工厂类:通过此类可以获得一个线程池。

方法名描述
newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,无上限。
newSingleThreadExecutor()创建单个线程的线程池,只有一个线程。
newScheduledThreadPool()创建固定大小的线程池,可以延迟或定时执行任务。
package Day19_thread2.pool;/*
 * @author  pyh
 * @since JDK1.8_241
    线程池:节省了线程的创建和销毁
 */
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo01 {
    public static void main(String[] args) {
        /*//返回包含单条线程的线程池
        ExecutorService pool = Executors.newSingleThreadExecutor();
        Task1 task = new Task1();
        for (int i = 1; i < 11; i++) {
            //提交需要执行的线程任务到线程池中
            pool.submit(task);
        }
        //关闭线程池
        pool.shutdown();*/

        /*//返回包含3条线程的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Task1 task = new Task1();
        for (int i = 1; i < 11; i++) {
            //提交需要执行的线程任务到线程池中
            pool.submit(task);
        }
        //关闭线程池
        pool.shutdown();*/

        //创建一个带缓冲区的线程池,会根据需求创建线程
        ExecutorService pool = Executors.newCachedThreadPool();
        Task1 task = new Task1();
        for (int i = 1; i < 11; i++) {
            //提交需要执行的线程任务到线程池中
            pool.submit(task);
        }
        //关闭线程池
        pool.shutdown();
    }
}
class Task1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值