Java进阶篇多线程详解(线程终止三大方法、线程调度、线程安全(同步锁synchronized))

Java进阶篇多线程详解(线程终止三大方法、线程调度、线程安全(同步锁synchronized))

一些有关的多线程基础小编就不过多赘述了,在前面的java基础篇(网络编程多线程)有详细的介绍,这篇主要是讲解一下我们面试中需要知道的一些要点。

线程睡眠Thread.sleep()方法

经过查jdk1.8帮助文档,我们发现此方法具有以下特性

/*
  关于线程的sleep方法
  static void sleep(long millis)
  1.静态方法
  2.参数是毫秒
  3.作用:让当前线程进入休眠,进入阻塞状态,放弃占有CPU时间片,让给其他线程使用。
     此代码出现在哪个线程哪个线程就进入休眠。
   4.Thread.sleep方法可以做到这个效果,指定特定代码每隔多久执行一次

 */
 public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
//        try {
//            Thread.sleep(1000*5);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        //5秒之后执行此代码
//        System.out.println("hello world");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            Thread.sleep(1000);
        }
    }
}

当我们对sleep()方法有了一个初步的了解后,那么问题来了当我们执行以下这个代码时,我们新定义的线程t会被阻塞吗?


/**
 * 关于Thread.sleep()方法的面试题
 *
 */
public class ThreadTest02 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.setName("t");
        t.start();
        //此代码会让线程t进入休眠状态吗
        t.sleep(1000);
        System.out.println("hello,world");
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

答案是不会的,为什么勒?不是说 此代码出现在哪个线程哪个线程就进入休眠吗。这样不就和我们的定义相违背了吗?

这个时候我们就要注意了,这个方法是一个(static)静态方法,那么这个static又有什么作用呢?在之前的基础篇中,对于static关键字有详细的描述 final,static,权限修饰符,内部类

看完之后我们知道,被static修饰的方法一般都会直接使用本类来进行直接调用(Thread.sleep(1000)),就算是你定义了一个此类对象进行调用时(t.sleep()),它也会直接向上转型为(Thread.sleep()),这样我们就很明白了,原来t.sleep()阻塞的并不是我们的t线程,而是主(main)线程。

所以在一秒后我们打印的hello,world才会显示出来。

线程唤醒interrupt()方法

当一个线程睡眠时我们又如何去唤醒他呢,这个时候就可以去调用我们的interrupt()方法了。这个方法的机制是什么呢?

通过查jdk1.8帮助文档我们发现这里面只说了一句话
在这里插入图片描述
点进去一看
在这里插入图片描述
是不是有点看不懂,没关系,我们可以通过以下代码分析出来

/**
 * 怎么叫醒一个正在睡眠的线程呢
 */
public class ThreadTest03 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyRunnable());
        thread.setName("t");
        thread.start();
        //希望5秒之后主线程手里的活干完了
        Thread.sleep(1000*5);
        thread.interrupt();//此中断方式靠的是异常处理机制,此方法调用后,会被异常捕获从而终止睡眠
    }
}

class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"---> begin");

        try {
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"---> end");
    }
}
结果:
t---> begin
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.zhou.thread.MyRunnable.run(ThreadTest03.java:26)
	at java.lang.Thread.run(Thread.java:748)
t---> end

可以发现原理调用了此方法后向虚拟机抛出了一个异常被“t”线程捕获后,从而叫醒了整个线程。
注意是唤醒而不是终止当前线程,当前线程后续代码依旧会执行

线程终止stop()方法以及布尔值标记法

说到线程终止不得不提的是stop()方法,为啥会被弃用呢?帮助文档中给出了解释。
在这里插入图片描述
就是说这个方法很容易丢失线程中的数据,就比如:你在写论文没保存的时候你电脑突然断电那样,所以呢此方法就被弃用了,通常呢我们去终止一个线程都是采用布尔值标记的方法,代码如下:

/**
 * 怎么终止一个线程的执行呢?
 */
public class ThreadTest04 {
    public static void main(String[] args) throws InterruptedException {
        MyRunnable2 r = new MyRunnable2();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        Thread.sleep(1000*5);
        //5秒之后终止t线程
        //t.stop();//已弃用
        //想要什么时候终止线程t就直接把r.run改成false
        r.run=false;
    }

}

class MyRunnable2 implements Runnable{
    boolean run = true;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run) {
                System.out.println(Thread.currentThread().getName() +"---->"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            else
            {
                //在return前可以保存你为保存的数据
                return;
            }
        }
    }
}

线程调度问题

常见的线程调度模型有哪些?

  • 抢占式调度模型: 哪个线程的优先级高,抢到的时间片的概率就高一些,多一些。Java采用的就是抢占式调度模型
  • 均分式调度模型: 平均cpu时间片。每个线程占用的cpu时间片时间长度一样。平均分配、一切平等。

Java中提供的线程调度方法

  • 实例方法
    • void setPriority(int newPriority) 设置线程的优先级
    • int getPriority() 获取线程优先级
    • 最低优先级是1,最高优先级是10,默认是5
/*
关于线程的优先级
 */
public class Threadtest05 {
    public static void main(String[] args) {
       // System.out.println("最高优先级"+Thread.MAX_PRIORITY);
       // System.out.println("z最低优先级"+Thread.MIN_PRIORITY);
        // System.out.println("默认优先级"+Thread.NORM_PRIORITY);
        Thread thread = Thread.currentThread();
        thread.setPriority(1);
        //System.out.println(thread.getName()+"线程默认优先级是"+thread.getPriority());
        Thread t = new MyThread3();
        t.setPriority(10);
        t.start();
        //优先级高的,只是抢到的cpu时间片相对多一些。
        //t线程抢到的时间片概率更高
        for (int i = 0; i < 10000; i++) {
            System.out.println(thread.getName()+"--->"+i);
        }
    }
}
class  MyThread3 extends Thread{
    @Override
    public void run() {
        //System.out.println(Thread.currentThread().getName()+"线程默认优先级是"+Thread.currentThread().getPriority());
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

  • 静态方法
    • static void yield() 让位方法 让当前线程让位,让给其他线程使用
    • 能让当前线程从运行状态回到就绪状态
    • 优先级高的获取cpu时间片可能会多一些。
    • 注意:再回到就绪之后,有可能再会抢到时间片。
/*
 当前线程暂停回到就绪状态,让给其他线程
 静态方法: Thread.yield();
 */
public class ThreadTest06 {
    public static void main(String[] args) {
        Thread t =new Thread(new MyRunnable1());
        t.setName("t");
        t.start();
        for (int i = 1; i <= 10000; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <=10000; i++) {
            if(i%100==0)
            {
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
  • 实例方法
    • void join() 合并线程
/**
 * 线程合并
 */
public class ThreadTest07 {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main begin");
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();
        //合并线程
        t.join();//t线程合并到当前线程中,当前线程阻塞,t线程先执行完,当前线程才执行
        System.out.println("main over");
    }
}

class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }

    }
} 

关于多线程并发环境下,数据的安全问题

当我们编写的代码放到一个多线程的环境下运行时,我们需要关注的是数据的安全问题。

什么时候数据在多线程并发的环境下数据安全会有问题?

下面有一个例子
在这里插入图片描述
我们就能知道发生数据不一致的三个条件

  1. 多线程并发
  2. 有共享数据
  3. 共享数据有修改行为。

满足以上三个条件就会有线程安全问题。

怎么解决线程安全问题

线程排队执行:(不能并发)这种机制称为线程同步机制。但是此机制就会牺牲一部分效率,但是数据安全。

异步编程模型: 线程t1和线程t2独自执行,也就是线程独立。(效率高),异步就是并发

同步编程模型: 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,反过来也一样,简而言之,就是两个线程之间发生了等待。(效率低),同步就是排队

银行账户取款案例模拟(synchronized 解决数据不一致问题)

银行账户类

/**
 * 银行账户
 * 多线程对同一个账户取款出现数据不一致问题
 * 使用线程同步机制解决线程安全问题
 */
public class Account {
    private String acton;
    private double balance;

    public Account() {
    }

    public Account(String acton, double balance) {
        this.acton = acton;
        this.balance = balance;
    }

    public String getActon() {
        return acton;
    }

    public void setActon(String acton) {
        this.acton = acton;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款的方法
    public void  withdraw(double money,Account account)
    {
        /*以下这几行代码必须时线程排队的,不能并发
        加入线程同步锁
        小阔号传的参数必须是多线程共享的数据,才能达到多线程排队
        括号中的内容就是你想同步的线程的共享对象
        在Java语言中,任何一个对象都有一把锁,其实这把锁就是标记
        以下代码的执行原理:
        1、假设t1和t2线程并发执行,开始执行以下代码的时候,肯定有一个先一个后
        2、假设t1先执行了,遇到了synchronized,这个时候自动找”后面共享对象“的对象锁,
        找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。
        知道同步代码块代码结束,这把锁才会释放。
        3、假设t1已经占有了这把锁,此时t2也遇到了synchronized,发现被t1占有,所以t2就进入等待,直到t1把
        这个共享对象的锁释放,t2才能执行同步代码块里面的代码。这就达到了线程排队的效果。
        */
        synchronized (account)
        {
            //t1和t2并发这个方法(两个栈操作堆中的同一个对象)
            //取款之前的余额
            double before = this.getBalance();
            //取款之后的余额
            double after =  before-money;
            //更新余额
            this.setBalance(after);
            System.out.println("账户余额为:"+this.getBalance());
        }
    }
}

线程类


public class AccountThread extends Thread{
    //两个线程必须共享一个账户对象
    private Account act;

    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        double money = 5000;
        //取款操作
        act.withdraw(money,act);
    }
}

测试类

public class Tset {
    public static void main(String[] args) {
        //创建账户对象
        Account account = new Account("001", 10000);
        AccountThread t1 = new AccountThread(account);
        AccountThread t2 = new AccountThread(account);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

synchronized在这里只是简略的介绍了一下,后面还有更详细的篇章进行讲解。

总结

手打不易。。。。。给个关注给个赞呗~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值