文章目录
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);
}
}
}
关于多线程并发环境下,数据的安全问题
当我们编写的代码放到一个多线程的环境下运行时,我们需要关注的是数据的安全问题。
什么时候数据在多线程并发的环境下数据安全会有问题?
下面有一个例子
我们就能知道发生数据不一致的三个条件
- 多线程并发
- 有共享数据
- 共享数据有修改行为。
满足以上三个条件就会有线程安全问题。
怎么解决线程安全问题
线程排队执行:(不能并发)这种机制称为线程同步机制。但是此机制就会牺牲一部分效率,但是数据安全。
异步编程模型: 线程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在这里只是简略的介绍了一下,后面还有更详细的篇章进行讲解。
总结
手打不易。。。。。给个关注给个赞呗~