JavaSE进阶:多线程(二)

3.线程状态

五大状态

  • 创建状态(新生)
  • 就绪状态
  • 阻塞状态
  • 运行状态
  • 死亡状态

 

 

1.线程方法

线程方法
方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

2.停止线程

  • 不推荐使用JDK提供的stop()、
    destroy()方法。[已废弃]
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量 
    当flag=false,则终止线程运行。

建议使用步骤

  1. 建议线程正常停止-->利用次数,不建议死循环
  2. 建议使用标识位-->设置一个标识位
  3. 不要使用stop或destroy等过时或JDK不建议使用的方法
package java_se.java_jinjie.duoxiancheng.demo03;
//测试stop
//1.建议线程正常停止-->利用次数,不建议死循环
//2.建议使用标识位-->设置一个标识位
//3.不要使用stop或destroy等过时或JDK不建议使用的方法
public class TestStop implements Runnable {
    //1.设置一个标识位
    private boolean flag=true;

    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("run...Thread"+i++);
        }
    }

    //2.设置一个公开的方法停止线程,转换标识位
    public  void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
            //主线程到90的时候停止子线程
            if (i==90){
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

3.线程休眠

  • sleep(毫秒数)指定当前线程停止的实践
  • sleep()存在异常InteruptedException
  • sleep()实践到达后线程进入就绪状态
  • sleep()可以模拟网络延时,倒计时等
  • 一个对象都有一个锁,sleep不会释放锁
  • 可以放大问题的发生性

1.模拟网络延时

package java_se.java_jinjie.duoxiancheng.demo03;

//模拟网络延时:放大问题的发生性
public class TestSleep  implements Runnable{
    private  int ticketNums=10;

    @Override
    public void run() {
        while (true){
            if (ticketNums<=0){
                break;
            }
            try {
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printstacktrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {
        TestSleep text = new TestSleep();
        new Thread(text,"小明").start();
        new Thread(text,"老师").start();
        new Thread(text,"黄牛").start();
    }
}

2.倒计时

package java_se.java_jinjie.duoxiancheng.demo03;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep2 {
    public static void main(String[] args) {
//        //倒计时
//        try {
//            tenDown();
//        }catch (InterruptedException e){
//            e.printstacktrace();
//        }
        //获取系统当前时间
        Date date = new Date(System.currentTimeMillis());
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date = new Date(System.currentTimeMillis());//更新当前时间
            }catch (InterruptedException e){
                e.printstacktrace();
            }
        }
    }


    public static void tenDown()throws InterruptedException{
        int num=10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
}

4.线程礼让(放弃、谦让、避让)

礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
cpu重新调度,礼让不一定成功,看cpu心情

package java_se.java_jinjie.duoxiancheng.demo03;
//测试礼让线程
//礼让不一定成功,看cpu心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

5.线程加入(线程强制执行)

  • Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
  • 可以想象成插队
package java_se.java_jinjie.duoxiancheng.demo03;
//测试join方法:想象成插队
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程vip来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException{
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        //主线程
        for (int i = 0; i < 20; i++) {
            if (i==12){
                thread.join();//插队
            }
            System.out.println("main"+i);
        }
    }
}

6.线程状态观测

Thread.State
线程状态。线程可以处于以下状态之一

  • NEW
    未启动的线程处于此状态。
  • RUNNABLE
    在Java虛拟机中执行的线程处于此状态。
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态。
  • WAITING
    正在等待另-个线程执行特定动作的线程处于此状态。
  • TIMED WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED
    退出的线程处于此状态。

一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。

package java_se.java_jinjie.duoxiancheng.demo03;

public class TestState {
    public static void main(String[] args) throws InterruptedException{
        Thread thread =new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printstacktrace();
                }
            }
            System.out.println("");
        });


        //观测状态
        Thread.State state = thread.getState();
        System.out.println(state);//new

        //观测启动后
        thread.start();//启动线程
        state=thread.getState();
        System.out.println(state);//run

        while (state!=Thread.State.TERMINATED){//TERMINATED线程终止|只要线程不终止,就一直输出状态
            Thread.sleep(10);
            state=thread.getState();//更新线程
            System.out.println(state);//输出状态
        }
    }
}

7.线程优先级

  • java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程;线程调度器按照优先级决定该调度哪个线程来执行
  • 线程的优先级用数字来表示,范围从1~10
    • Thread.MIN_PRIORITY = 1        最小优先级
    • Thread.MAX_PRIORITY= 10      最大优先级
    • Thread.norM_PRIORITY = 5    认优先级
  • 使用getPriority()和setPriority()来获取或改变优先级

建议:优先级的设定建议在start()调度前

优先级低只是意味着被cpu的调度概率低,并不是优先级低就不会被调用了,这都是看cpu的调度

package java_se.java_jinjie.duoxiancheng.demo03;

public class TestPriority {
    public static void main(String[] args) {
        //主线程认优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);

        //先设置优先级
        t1.start();
        t2.setPriority(1);
        t2.start();
        t3.setPriority(4);
        t3.start();
        t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
        t4.start();
    }
}

class MyPriority implements  Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

    }
}

8.守护线程

  • 线程分为用户线程(前台线程)和守护线程(后台线程)
  • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 后台记录操作日志,监控内存,垃圾回收
  • 线程对象. setDaemon (true) ;设置为守护线程
package java_se.java_jinjie.duoxiancheng.demo03;

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread thread = new Thread(god);
        thread.setDaemon(true);//认是false表示用户线程,正常的线程都是用户线程
        thread.start();//上帝守护线程
        new Thread(you).start();//你  用户线程启动
    }
}

//上帝
class God implements Runnable{
    @Override
    public void run() {
        while (true){//虚拟机
            System.out.println("上帝忽悠着你");//用户线程结束,虚拟机结束需要一段时间,所以会继续运行一会
        }
    }
}
//你
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的或者");
        }
        System.out.println("==goodbye world==");
    }
}

4.线程同步

并发

多个线程操作同一资源

同一对象被多个线程操作

例:上万人抢100张票、两个银行同时取钱

线程同步

  • 处理多线程问题时,多个线程访问同一个对象(并发),并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程在使用
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制syncronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一下问题
    • 一个线程持有锁会导致其他所有需要此锁的进程挂起
    • 在多线程竞争的情况下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

线程安全

已知JDK中线程安全的类:

  • StringBuffer
  • Vector
  • Hashtable
  • 以上类中的公开方法,均为synchoni zed修饰的同步方法

三大不安全案例

三人抢票

package java_se.java_jinjie.duoxiancheng.demo04;
//不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"苦逼的我").start();
        new Thread(station,"牛逼的你们").start();
        new Thread(station,"可恶的黄牛").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;//外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printstacktrace();
            }
        }
    }
    private void buy()throws InterruptedException{
        //判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        }

        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);

    }
}

两人一起取钱

package java_se.java_jinjie.duoxiancheng.demo04;
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"你的妻子");

        you.start();
        girlFriend.start();
    }
}
//账户
class  Account{
    int money;//余额
    String name;//卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Drawing extends  Thread{
    Account account;//账户
    int drawingMoney;//取了多少钱
    int NowMoney;//现在有多少钱
    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney=drawingMoney;
    }

    @Override
    public void run() {
        //判断有没有钱
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }
        //sleep可以放大问题的发生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printstacktrace();
        }
        //卡内余额=余额-你取的钱
        account.money = account.money-drawingMoney;
        //你手里的钱
        NowMoney=NowMoney+drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        //Thread.currentThread().getName() = this.getName()  因为继承Thread所以两个getName相等
        System.out.println(this.getName()+"手里的钱为:"+NowMoney);
    }
}

不安全的集合

package java_se.java_jinjie.duoxiancheng.demo04;

import java.util.ArrayList;
//不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printstacktrace();
        }
        System.out.println(list.size());
    }
}

同步方法

  • 同步方法: public synchronized void method(int args) {}
  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块.
  • synchronized方法控制对“对象”的访问, 每个对象对应一把锁,每个synchronized方法都必须获得调用方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
  • 缺陷:若将一个大的方法申明为synchronized将会影响效率

同步方法实现抢票

package java_se.java_jinjie.duoxiancheng.demo04.demo2;
//不安全的买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"苦逼的我").start();
        new Thread(station,"牛逼的你们").start();
        new Thread(station,"可恶的黄牛").start();
    }
}
class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    boolean flag=true;//外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printstacktrace();
            }
        }
    }
    //synchronized 同步方法,锁的是this
    private synchronized void buy()throws InterruptedException{
        //判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        }

        //模拟延时
        Thread.sleep(100);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);

    }
}

同步块

  • 同步块: synchronized(obj){}
  • obj称之为同步监视器
    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个对象本身,或者是class,不能使用new的对象,因为这样会每一个对象都拿到一个锁,都用自己的锁进入了线程,那锁将变得没有意义(就相当于没有锁)
    • 线程退出同步代码块,会释放对应的锁
  • 同步监视器的执行过程
    • 一个线程访问,锁定同步监视器,执行其中的代码
    • 第二个线程访问,发现同步监视器被锁定,无法访问
    • 一个线程访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
  • 同步方法方法添加synchronized关键子,锁的是对象本身

同步块实现模拟取款

package java_se.java_jinjie.duoxiancheng.demo04.demo2;
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"你的妻子");

        you.start();
        girlFriend.start();
    }
}
//账户
class  Account{
    int money;//余额
    String name;//卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Drawing extends  Thread{
    Account account;//账户
    int drawingMoney;//取了多少钱
    int NowMoney;//现在有多少钱
    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney=drawingMoney;
    }
    //synchronized 认锁的是this.
    @Override
    public void run() {
        //锁的对象就是变化的量,需要增删改的对象
        synchronized (account){
            //判断有没有钱
            if (account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }
            //sleep可以放大问题的发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printstacktrace();
            }
            //卡内余额=余额-你取的钱
            account.money = account.money-drawingMoney;
            //你手里的钱
            NowMoney=NowMoney+drawingMoney;
            System.out.println(account.name+"余额为:"+account.money);
            //Thread.currentThread().getName() = this.getName()  因为继承Thread所以两个getName相等
            System.out.println(this.getName()+"手里的钱为:"+NowMoney);
        }

    }
}

同步块实现不安全的集合

package java_se.java_jinjie.duoxiancheng.demo04.demo2;

import java.util.ArrayList;
//不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printstacktrace();
        }
        System.out.println(list.size());
    }
}

copyOnWrite实现不安全的集合(JUC安全类型的集合)

package java_se.java_jinjie.duoxiancheng.demo04.demo2;

import java.util.concurrent.copyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args) {
        copyOnWriteArrayList<String> list =new copyOnWriteArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printstacktrace();
        }

        System.out.println(list.size());
    }
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...
win11本地账户怎么改名?win11很多操作都变了样,用户如果想要...