Java进阶 - 线程方法、线程安全问题、等待与唤醒

1.线程方法

  (1)getName()获取线程名称

主方法:
public class DemoThread {
    public static void main(String[] args) {
        MyThread01 thread1 = new MyThread01();
        thread1.start();
        //获取主线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}

线程子类:
public class MyThread01 extends Thread {
    @Override
    public void run() {
        //获取线程名称方法1
        /*String name = getName();
        System.out.println(name);*/
        //获取线程名称方法2
        Thread t = Thread.currentThread();
        System.out.println(t.getName());
    }
}

   (2)setName(name)设置线程名称

    可用线程调用setName方法设置名称;或者创建一个线程子类,并设置一个带参数的构造方法,并且调用父类的方法super(name);即可

  (3)sleep()使当前执行的线程暂停指定的毫秒数后再继续执行,需要捕捉异常

public class DemoThread {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 1; i <= 60; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 2.创建线程的第二种方式:实现runnable接口 并定义一个run的无参数方法  再创建一个Thread对象 将该实现类对象传递进去

实现类:
public class MyRunner implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
main方法:
public class DemoThread {
    public static void main(String[] args) {
        MyRunner run1 = new MyRunner();
        Thread t = new Thread(run1);
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

 

两种开启线程方法的区别:

  实现runnable接口的好处:

    (1)避免了单继承的局限性

    (2)增强了程序的扩展性,降低了程序的耦合性

        把设置线程任务和开启新线程(经由Thread的对象传参执行)进行了分离(解耦)

3.使用匿名内部类创建线程

public class DemoThread {
    public static void main(String[] args) {
       //第一种方式
        new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 20; i++) {
                   System.out.println(Thread.currentThread().getName() + i);
               }
           }
       }.start();
        //第二种方式 接口实现
        Runnable r1 = new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        new Thread(r1).start();
        //简化方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}
匿名内部类创建多线程

 

4.线程安全问题,三个线程共抢一个共享资源,这种情况时不允许出现的

实现类:
public class MyRunner implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        //模拟卖票
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticket>0){
                System.out.println(Thread.currentThread().getName() + "正在卖第" +ticket +"张票" );
                ticket --;
            }
        }
    }
}
主线程:
public class DemoThread {
    public static void main(String[] args) {
       MyRunner run = new MyRunner();
       //开启三个线程卖票
       Thread t1 = new Thread(run);
       Thread t2 = new Thread(run);
       Thread t3 = new Thread(run);
       t1.start();
       t2.start();
       t3.start();
    }
}

 

 解决线程安全问题的三个方法

  (1)同步代码块

  (2)同步方法

  (3)锁机制

(1)同步代码块

  格式:

    synchronized(锁对象){

      可能出现线程安全问题的代码块(访问了共享数据的代码)  

      }

注意:1.同步代码块中的锁对象,可以是任何对象

      2.但是必须保证多个线程使用同一个锁对象

   3.锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

public class MyRunner implements Runnable {
    private int ticket = 100;
    //创建一个锁对象,必须在run方法外面,要保证该对象的同一性
    Object obj = new Object();
    @Override
    public void run() {
        //模拟卖票
        while (true){
            //创建同步代码块
            synchronized (obj){
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖第" +ticket +"张票" );
                    ticket --;
                }
            }
        }
    }
}
同步代码块

 原理:

 (2)同步方法:它也会把方法内部的代码块锁住,其锁对象就是实现类对象new出来的那个,也就是this

 

实现类对象:
public class ThreadSecurity implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            payTicket();
        }
    }

    public synchronized void payTicket() {
        //模拟卖票
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}
主方法:
public class DemoThread {
    public static void main(String[] args) {
       ThreadSecurity ts = new ThreadSecurity();
       //开启三个线程卖票
       Thread t1 = new Thread(ts);
       Thread t2 = new Thread(ts);
       Thread t3 = new Thread(ts);
       t1.start();
       t2.start();
       t3.start();
    }
}
同步方法

 

 

也可用静态同步方法,但要注意访问的变量也是静态的  静态方法的锁对象是本类的class属性 - - > class文件对象(反射)

 

(3)Lock锁

  首先在成员位置创建一个ReentrantLock对象,然后调用Lock接口的lock和unlock方法

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

    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock(); //无论程序是否异常都会释放锁
                }

            }
        }
    }
}
Lock锁

 

5.线程状态图

 

 生产者消费者等待唤醒案例:

package basicpart.day01.MultiThread;

public class PSmodel {
    public static void main(String[] args) {
        //1.创建一个锁对象
        Object obj = new Object();
        //2.创建消费者
        new Thread(){
            @Override
            public void run() {
                while (true){
                    //创建同步代码块,保证一个线程等待,另一个线程执行
                    synchronized (obj){
                        System.out.println("我要一个肉包");
                        System.out.println("----------------------");
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("那我开吃啦");
                        System.out.println("吃完啦");
                    }
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //创建生产者
                    synchronized (obj) {
                        System.out.println("您的包子做好了");
                        System.out.println("--------------");
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}
View Code

 

什么是等待唤醒机制
  这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
  就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。

等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

调用wait和notify方法需要注意的细节

1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。

2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。

3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

 

包子铺案例分析:

 

 

代码实现:

//资源类 包子
public class BaoZi {
    //包子皮
    String pi;
    //包子馅
    String xian;
    //包子状态,true or false,初始值为false
    boolean flag = false;
}
包子类

 

/*
注意:
    1.包子铺线程和包子线程关系 - - > 通信(互斥)
    2.必须同时同步技术保证两个线程只能有一个在执行
    3.锁对象必须唯一,可以使用包子对象作为锁对象
    4.包子铺类和吃货的类需要把包子对象作为参数传递进来
        既需要在成员位置创建一个包子变量
        使用有参构造方法,为这个变量赋值

*/
public class BaoZiPu extends Thread {
    private BaoZi bz;

    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    //设置线程任务;生产包子
    @Override
    public void run() {
        //定义一个变量,来交替生产包子
        int count = 0;
        //让包子铺一直生产包子
        while (true) {
            synchronized (bz) {
                //对包子的状态进行判断
                if (bz.flag == true) {
                    //有包子,让线程等待,让吃货去吃
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //被唤醒之后执行,吃货吃完之后,包子铺生产包子
                //增加一些趣味性:交替生产两种包子
                if (count % 2 == 0) {
                    //生产 薄皮 韭菜馅包子
                    bz.pi = "薄皮";
                    bz.xian = "韭菜馅";
                } else {
                    //生产 冰皮 猪肉馅
                    bz.pi = "冰皮";
                    bz.xian = "猪肉馅";
                }
                count++; //每次生产完一个包子加一,包子就可以交替生产不同的馅了
                System.out.println("包子铺正在生产" + bz.pi + bz.xian + "的包子");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好包子后,改变包子的状态
                bz.flag = true;
                //唤醒吃货线程
                bz.notify();
                System.out.println("新鲜的" + bz.pi + bz.xian + "包子出炉啦!!!");
            }
        }
    }
}
包子铺类

 

public class ChiHuo extends Thread {
    private BaoZi bz;

    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true){
            synchronized (bz){
                if(bz.flag == false){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //线程被唤醒之后,就是吃包子
                System.out.println("吃货正在吃" + bz.pi + bz.xian + "的包子");
                //吃货吃完包子,修改包子的状态
                bz.flag = false;
                //吃货唤醒包子铺生产包子
                bz.notify();
                System.out.println("吃货已经把" + bz.pi + bz.xian + "的包子吃完了");
                System.out.println(" ============================= ");
            }
        }
    }
}
吃货类

 

public class Demo {
    public static void main(String[] args) {
        BaoZi bz = new BaoZi();
        //创建包子铺线程
        new BaoZiPu(bz).start();
        //创建吃货线程
        new ChiHuo(bz).start();
    }
}
测试类

 

posted @ 2020-05-09 13:54  五号世界  阅读(229)  评论(0编辑  收藏  举报