Day 25开篇:
"今天java基础主要学习了多线程的死锁问题,同步资源,线程安全解决,同步代码块,同步方法,线程加入,线程礼让,线程守护,线程优先级设置,线程终止,Runnable接口等"
知识点反馈:
今天的知识点总结的思维导图
一.死锁
1.Java的同步机制,虽然可以解决线程安全的问题,但是也有比较严重的弊端:
(1)效率极低
(2)使用不当可能造成死锁
2.死锁如何解决?
没法解决(当今世界上根本没有任何办法解决的一个问题)
死锁是一个没有解决方案的问题,只能说尽量避免(破坏死锁的任意一个必备条件即可),千万不要因为好奇心去写出一个死锁代码出来,因为很危险
3.死锁的产生:
(1)互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
(2)请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
(4)循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
二.同步资源
1.我们一般使用synchronized的时候,他有个弊端:
(1)需要同步锁资源
(2)他的代码结构不健壮
2.JDK5的的版本提供一个新的锁对象:Lock
(1)void lock(): 获取锁。
(2)void unlock(): 获取锁。
例子:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankThread01 extends Thread {
BankThread01(String name){
super(name );
}
static int count = 5000;
//锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//锁开始
lock.lock();
if(count>0){
count-=100;
System.out.println(Thread.currentThread().getName()+"取出100元钱,还剩余:"+count);
}else{
System.out.println("不好意思无余额");
break;
}
//锁结束(释放锁)
lock.unlock();
}
}
}
三.线程安全解决
1.线程安全的问题解决:
(1)同时存在两个以上的线程可能造成线程安全
(2)多个线程共享一个资源,可能造成线程安全
2.根据原因解决问题:
两个线程,这个是不能动的,因为需求就是如此,所有只能共享的地方入手。
3.同步代码块:
synchronized (同步监视器){
需要被同步的代码
}
(1)同步监视器是一个什么东西?
就是一个对象(锁对象),任何的对象都存在一把锁,而且只有一把钥匙
也就是,这个地方的同步监视器,你只需要放一把锁就oK,任何锁都行。
new Object()行不行?
行,没有问题,但是每个线程是自己锁自己的。
这个锁对象,应该是所有线程都共享的一把锁。
(2)注意的事项:
<1>锁对象可以是任意对象
<2>锁对象必须是多个线程共享的对象
<3>如果调用sleep方法的线程,不会释放锁资源。
<4>如果你的代码不存在线程安全的问题,不要去使用同步块或者函数,因为效率特别低
4.同步方法:
(1)synchronized修饰的方法被称之为同步方法
(2)注意的事项:
<1>非静态的方法锁对象其实this对象,谁调用我 我代表哪个对象(不是持有同一把锁)
<2>静态方法锁的对象其实的class的文件对象(静态资源对象,持有的是同一把锁)
<3>一般,代码量比较少的 请尽量使用同步代码块,不要使用同步方法,一般来说,方法应该是一个灵活扩展的代码,不要使用任何形式去限制它
四.线程
1.线程加入:
(1)public final void join():等待该线程终止
(2)被join调出的线程,必须等本线程执行完毕之后,其他线程才能允许参与抢夺
注意调用和启动的位置。
例子:
public class Demo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
MyThread m3 = new MyThread();
m1.setName("小花");//二媳妇
m2.setName("小翠");//大媳妇
m3.setName("小李");//3000万
m3.start();
try {
//等待m3终止后,其他线程才能参与抢夺
m3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
m1.start();
m2.start();
}
}
2.线程礼让:
(1)public static void yield():线程礼让(先暂时停止当前正在执行的线程对象,并执行其他线程对象)
(2)线程礼让,并不能保证多个线程一定是执行一次就放出执行权,只能尽可能保证多个线程的执行更加的和谐。
例子:
public class Demo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
MyThread m3 = new MyThread();
m1.setName("小花");
m3.setName("小李");
m1.start();
m3.start();
}
}
3.线程守护:
(1)public final void setDaemon(boolean on):线程守护(被标记为守护线程的线程,当被守护线程死亡后,守护线程也死亡)
例子:
public class Demo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
MyThread m3 = new MyThread();
m1.setName("小花");//二媳妇
m2.setName("小翠");//大媳妇
m3.setName("小李");//3000万
//当小李死亡的时候,小花和小翠也不想活了
m1.setDaemon(true);
m2.setDaemon(true);
m1.start();
m2.start();
m3.start();
}
}
4.线程休眠:
(1)public static void sleep(long millis):线程休眠
(2)所谓的线程休眠,其实就是当前现在执行到某一个片段的时候进行睡眠,并且让出时间片段(执行权)
(3)其他线程就会拿到执行权。(如果多个线程休眠的时间是一致的时候,每个线程的执行片段就会相对和谐)
例子:
public class Demo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("小花");
m2.setName("小翠");
m1.start();
m2.start();
}
}
5.线程优先级设置:
(1)假如我们的计算机只有一 CPU,那么CPU在某一个时刻只能执行一条指令,
线程只有得到CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
(2)线程有两种调度模型:
<1>分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
<2>抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
(3)Java使用的是抢占式调度模型。
演示如何设置和获取线程优先级
public final int getPriority():获取当前线程的优先级【默认优先级为5】
mian方法的优先级仍然5,那么为啥经常优先其他线程运行呢?
谁先开启的问题,一般来说 先运行的线程具备先天的优势,有几率可能优先运行
public final void setPriority(int newPriority)
优先级的范围从1-10,1的优先级为最高,10的优先级为最低
如果线程的优先级超如如上范围,则产生程序异常:IllegalArgumentException
线程的优先级设置,并不能准确的提高线程的运行的优先程度,只能是尽可能的让线程优先级高的线程先运行(还是存在很大的随机性)
例子:
public class Demo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("小花");
m2.setName("李老师");
m2.setPriority(1);//
m1.start();
m2.start();
System.out.println(m1.getPriority());
System.out.println(m2.getPriority());
System.out.println("-----"+Thread.currentThread().getPriority());//mian方法的优先级仍然5,那么为啥经常优先其他线程运行呢?
}
}
6.线程终止:
(1)void stop():线程终止
不推荐使用:某一个线程的终止不应该造成整个JVM被关掉(stop一旦运行会终止掉整个虚拟机不合理)
(2)void interrupt():线程中断
推荐使用:某一个线程产生意外,只是中断此次线程的运行,并不会干扰到其他线程的运行,并且抛出一个InterruptedException异常(这个异常不会造成JVM终止,只是给一个提示警告的作用)
例子:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws FileNotFoundException {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("小花");
m2.setName("李老师");
m1.start();
m2.start();
}
}
7.Runnable接口:
(1)创建线程的两种方式:
<1>将线程类作为Thread的具体子类,重写run方法(不强制)
< 2>将线程类作为Runnable的接口实现类,重写run(强制)
1)自己定义一个线程类,实现Runnable
2)在线程类里面重写run方法
3)创建一个Runnable子实现类对象
4)创建一个Thread的具体类,并且将Runnable具体实现类对象传入
5)调用Thread对象的start方法,由他来调用MyRunnable的run方法
(2)两种方式,我们一般使用哪一种?
一般我们优先选择Runnable。
1)因为使用Runnable可以打破继承的局限性,因为对于线程类来说,不一定只是实现run方法,有可能还要继承其他类,那么Thread就无法实现了。
2)代码更加合理,因为一个类如果要实现线程就必须重写run方法,而接口更加合理的强制了我们必须重写
3)当多个线程需要共享线程对象的某一个数据的时候,Runnable不需要使用静态修饰,因为Runnable的实现类只有一个,而所有Thread都是共享一个Runnable实现类对象
例子:
public class Demo{
public static void main(String[] args) {
//创建Runnable实现类对象
MyRunnable mr = new MyRunnable();
//创建一个Thread的具体类,并且将Runnable具体实现类对象传入
//直接对象构造设置线程姓名
Thread t = new Thread(mr,"小明");
//t.setName("小明");
t.start();
Thread t2 = new Thread(mr,"小花");
//t2.setName("小花");
t2.start();
}
}