Day17 JavaSE 线程(下)

JavaSE 线程(下)

三、Thread类的常用方法

1 线程一般方法

方法名称 功能
void start() 启动线程
run() 线程在被调度时执行的操作
String getName() 取出线程名称
void setName(String name) 设置线程名称
static currentThread() 返回当前线程
int getPriority() 获取线程优先级
void setPriority() 设置线程优先级(默认为5)

补充:线程优先级就是哪个线程有较大的概率被执行。优先级用数字1-10表示,数字越大,优先级越高。若未设置优先级,默认为5。

以上方法案例展示:

package com.thread;

public class Demo01 {
    public static void main(String[] args) {
        RunnalbalImpl run0 = new RunnalbalImpl();
        RunnalbalImpl run1 = new RunnalbalImpl();

        Thread t0 = new Thread(run0);
        Thread t1 = new Thread(run1);

        t1.setName("线程t1");

         /*
            线程优先级就是哪个线程有较大的概率被执行。
            优先级用数字1-10表示,数字越大,优先级越高。
            若为设置,默认为5。
         */
        t0.setPriority(1); //设置优先级为1,若为设置默认为5

        t0.start();
        t1.start();

        System.out.println("t0的线程名称: "+ t0.getName()); //若在创建线程时没有指定名称,则系统默认给出"Thread-0/1/..."
        System.out.println("t1的线程名称: "+ t1.getName());

        System.out.println("t0的优先级: "+t0.getPriority()); //获取线程优先级
    }
}
class RunnalbalImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Runnable多线程运行的代码");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"这是Runnable多线程的逻辑代码" + i);
        }
    }
}
/*运行结果:
Thread-0Runnable多线程运行的代码
线程t1Runnable多线程运行的代码
线程t1这是Runnable多线程的逻辑代码0
线程t1这是Runnable多线程的逻辑代码1
t0的线程名称: Thread-0
线程t1这是Runnable多线程的逻辑代码2
Thread-0这是Runnable多线程的逻辑代码0
Thread-0这是Runnable多线程的逻辑代码1
线程t1这是Runnable多线程的逻辑代码3
Thread-0这是Runnable多线程的逻辑代码2
线程t1这是Runnable多线程的逻辑代码4
Thread-0这是Runnable多线程的逻辑代码3
Thread-0这是Runnable多线程的逻辑代码4
t1的线程名称: 线程t1
t0的优先级: 1
 */

2 线程控制方法

方法名称 功能
static void yield() 线程让步:
1. 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
2. 若队列中没有同优先级的线程,忽略此方法
join() 当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到·join()方法加入的join线程执行完为止。
static void sleep(long millis) 1. 另当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队。
2. 需要抛出InterruptedException异常
stop() 强制线程结束
boolean isAlive() 返回boolean值,判断线程是否还存活

以上方法案例展示:

package com.thread;

public class Demo02 {
    public static void main(String[] args) {
        RunnalbalImpl2 run0 = new RunnalbalImpl2();
        RunnalbalImpl2 run1 = new RunnalbalImpl2();

        Thread t0 = new Thread(run0);
        Thread t1 = new Thread(run1);

        t0.start();
        t1.start();

        System.out.println("-----------------------------------------1");
        System.out.println("-----------------------------------------2");

        t1.stop(); //强制停止线程
        try {
            t0.join(); //相当于在这里将t0的run方法插入到这个位置执行!
            /* 专业说法
                阻塞当前main方法,先不执行System.out.println("-----------------------------------------3");代码
                优先执行join进来的线程的代码。
                但是较为不明显。
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----------------------------------------3");
        System.out.println(t0.isAlive()); //判断线程是否存活
        System.out.println(t1.isAlive());

    }
}
class RunnalbalImpl2 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Runnable多线程运行的代码");
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);//每次循环睡眠1000ms
                //相当于当前循环每隔1000ms执行一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (i % 2 == 0){
                Thread.yield(); //线程让步 <-- 当i是偶数时
            }
            System.out.println(Thread.currentThread().getName()+"这是Runnable多线程的逻辑代码" + i);
        }
    }
}
/* 运行结果:
Thread-0Runnable多线程运行的代码
Thread-1Runnable多线程运行的代码
-----------------------------------------1
-----------------------------------------2
Thread-0这是Runnable多线程的逻辑代码0
Thread-0这是Runnable多线程的逻辑代码1
Thread-0这是Runnable多线程的逻辑代码2
Thread-0这是Runnable多线程的逻辑代码3
Thread-0这是Runnable多线程的逻辑代码4
-----------------------------------------3
false
false
 */

四、线程的生命周期

线程在一个完整的生命周期中通常要经历如下五种状态:

  • 新建:当一个Thread类或其子类的对象被声明应创建时,新生的线程对象处于新建状态。
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件。
  • 运行:当就绪的线程被嗲度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
  • 阻塞:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
  • 死亡:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

生命周期图示:

JavaThread

五、线程的同步

1 案例

例子:同一个账户,支付宝转账,微信转账。现有两个手机,一个手机开支付宝,另一个开微信,假设现有余额3000,支付宝和微信同时提款2000。 如果没有线程同步控制,账户就会变为-1000,这种情况不可以出现。

该例子通过代码实现如下:

package com.thread;

public class Demo03 {
    public static void main(String[] args) {
        //定义账户对象
        Acount a = new Acount();
        User u_wechat = new User(a,2000);
        User u_zhi = new User(a,2000);

        //多线程对象
        Thread wechat = new Thread(u_wechat,"微信");
        Thread zhi = new Thread(u_zhi,"支付宝");

        wechat.start();
        zhi.start();
    }

}
class Acount{
    public static int money = 3000; //全局变量,所有线程共享

    /**
     * 提款,判断账户余额是否充足
     * 多线程调用这个方法,就有问题,线程共享资源时,一个线程在执行这个方法没有完毕时,另一个线程又开始执行这个方法。
     * @param m
     */
    public void drawing(int m){
        String name = Thread.currentThread().getName();
        if (money<m){
            System.out.println(name+"操作,账户金额不足:" + money);
        }else {
            System.out.println(name + "操作,账户原有金额: " + money);
            System.out.println(name + "操作,取款金额: " + m);
            money -= m;
            System.out.println(name + "操作,取款后的金额: " + money);
        }
    }
}

class User implements Runnable{
    Acount acount;
    int money;
    public User(Acount acount, int money){
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run() {
        acount.drawing(money);
    }
}
/*运行结果:
支付宝操作,账户原有金额: 3000
微信操作,账户原有金额: 3000
支付宝操作,取款金额: 2000
支付宝操作,取款后的金额: 1000
微信操作,取款金额: 2000
微信操作,取款后的金额: -1000
*/

造成问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

2 synchonized同步锁!!

通过synchronized同步锁来完成!

只需在上述案例中Acount类的drawing方法前加上synchronized即可!即:

public synchronized void drawing(int m){}

结果显示:

{...}
/*
微信支付操作,账户原有金额: 3000
微信支付操作,取款金额: 2000
微信支付操作,取款后的金额: 1000
支付宝支付操作,账户金额不足:1000
*/

以上问题解决。

synchronized同步锁注意!!!

  • synchronized放在普通方法前,仅能锁当前对象(实例),不同对象有不同的锁。

  • synchronized放在静态方法前,锁整个类,所有类对象公用一个锁。

  • 也可对代码块加入同步锁。实现如下:

    public void drawing2(int m, Acount a){
      //使用this锁代码块是代表当前的对象,如果在其他方法中也有synchronized(this)代码块使用的都是同一个同步锁
      synchronized(this){//表示对整个代码块加锁
        ...
      }
      synchronized(a){ //锁传入的对象,并对代码块加锁
        ...
      }; 
    }
    

3 线程的死锁问题

  • 死锁

    不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

    例:线程a0,需要执行f0;线程a1,需要执行f1;f0和f1都有同步锁的方法,现在出现一个问题:a0调用f1方法但一直没有执行完f1,a1调用f0方法但一直没有执行完f0。导致a0和a1线程都在等待对方释放方法,而对方都不释放,这样就形成了线程的死锁。

  • 解决办法

    专门的算法、原则,比如加锁的顺序一致。

    尽量减少同步资源的定义,尽量避免锁未释放的场景。

六、线程通信

1 线程通信方法

注:以下三种方法只能用在有同步锁的方法或代码块中!

方法名称 功能
wait() 另当前线程挂起并放弃CPU、同步资源,使别的线程可以访问并修改共享资源,而当前线程排队等候再次对资源的访问。
notify() 唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
notifyAll() 唤醒正在排队等待资源的所有线程结束等待。

现在想要实现以下功能:

如果是微信操作的,先不执行,等待支付宝操作,支付宝操作完成后,微信在继续操作。

方法案例展示:

package com.thread;

public class Demo03 {
    public static void main(String[] args) {
        //定义账户对象
        Acount a = new Acount();
        User u_wechat = new User(a,2000);
        User u_zhi = new User(a,2000);

        //多线程对象
        Thread wechat = new Thread(u_wechat,"微信");
        Thread zhi = new Thread(u_zhi,"支付宝");

        wechat.start();
        zhi.start();
    }

}
class Acount{
    public static int money = 3000; //全局变量,所有线程共享

    public void drawing(int m, Acount a){
        synchronized (a){ //对实例加同步锁
            String name = Thread.currentThread().getName();

            //如果是微信操作的,先不执行,等待支付宝操作,支付宝操作完成后,微信在继续操作。
            if(name.equals("微信")){
                try {
                    a.wait();//当前进程进入等待状态!
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (name.equals("支付宝")) {
                try {
                    a.notify(); //唤醒当前优先级最高的线程,进入就绪状态!
//                    a.notifyAll(); //唤醒所有线程,进入就绪状态!
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (money<m){
                System.out.println(name+"操作,账户金额不足:" + money);
            }else {
                System.out.println(name + "操作,账户原有金额: " + money);
                System.out.println(name + "操作,取款金额: " + m);
                money -= m;
                System.out.println(name + "操作,取款后的金额: " + money);
            }

        }
    }
}

class User implements Runnable{
    Acount acount;
    int money;
    public User(Acount acount, int money){
        this.acount = acount;
        this.money = money;
    }
    @Override
    public void run() {
        acount.drawing(money,acount);
    }
}
/*运行结果:
支付宝操作,账户原有金额: 3000
支付宝操作,取款金额: 2000
支付宝操作,取款后的金额: 1000
微信操作,账户金额不足:1000
*/

2 生产者/消费者问题

**问题描述:**生产者将产品交给店员,而消费者从店员处取走产品,店员一次只能持有固定数量的产品(20),如果生产者试图生产更多的产品,店员会叫生产者停一下;如果店中有空位放产品了在通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

此处有两个问题:

  1. 生产者比消费者更快时,消费者会漏掉一些数据没有取到。

  2. 消费者比生产者快时,消费者会取相同的数据。

代码实现:(此处使用两个匿名内部类实现!)

package com.thread;

/**
 * 生产者与消费者
 */
public class Test3 {
    public static void main(String[] args) {
        //店员
        Clerk c = new Clerk();
        //消费时不生产,生产时不消费

      	//生产者线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (c){
                    while (true){ //无限循环代表无限的生产次数
                        if (c.productNum == 0) { //产品数量为0,开始生产
                            System.out.println("产品数量为0,开始生产");
                            while (c.productNum < 4){
                                System.out.println("库存:" + c.productNum++);
                            }
                            System.out.println("产品数为:" + c.productNum + ",结束生产");

                            c.notify(); //唤醒消费者线程
                        }else {
                            try {
                                c.wait(); //生产者线程等待
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }, "生产者").start();

      	//消费者线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (c){
                    while (true) { //无限循环代表无限的生产与消费
                        if (c.productNum != 0) { //产品数为4,开始消费
                            c.notify(); //唤醒生产者线程
                            System.out.println("产品数为4,开始消费");
                            while (c.productNum > 0) {
                                System.out.println("库存:" + c.productNum--);
                            }
                            System.out.println("产品数为:" + c.productNum + ",结束消费");
                        } else {
                            try {
                                c.wait(); //消费者线程等待
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }, "消费者").start();
    }
}

class Clerk{
    public static int productNum;
}

写在最后

行百里者半九十!

To Demut and Dottie!

发布了25 篇原创文章 · 获赞 19 · 访问量 1142

猜你喜欢

转载自blog.csdn.net/qq_44958172/article/details/104909978