JAVA线程基础看这篇文章就够了
进程和线程
-
进程
-进程是程序的一次动态执行过程,每个进程都有自己独立的内存空间。由操作系统的知识我们知道了,单核CPU一般通过时间片轮转的方法,将CPU的不同使用时间段分配给不同的进程,这样可以使得所有程序都在同时运行一样。 -
线程
线程是进程中的一个执行流程 。进程可以包含多个线程,可以理解未线程组成进程。 -
进程与线程的关系
线程组成进程,二者是局部(线程)与整体(进程)的关系
并行:多个进程同时运行。
并发:多个线程同时运行。 -
人生感慨
一个人可以在一个时间段(片)内做多件事情。但是终归一心不可二用,只能专心于一件事情做完再做下一个事情,做这件事情的时候可以眼、口、手、脑并用。宏观多进程并行,微观多线程并发。
生命周期和五种状态:
一个线程的生命周期有五种状态:
- 新建
- 就绪
线程具备运行条件,进入线程队列,等待系统分配CPU资源,一经分配进入运行状态。 - 运行
线程的创建有很多种方法(可以自行了解),但是源根头是运行Thread类中的run方法。 - 阻塞
一个正在执行的线程如果执行suspend、join、sleep方法或者再等待I/O设备,这个线程将会让出CPU使用权限进入阻塞状态。 - 死亡
线程完成任务或者中途被强制性的终止(比如废弃的stop方法,废弃原因:可能造成数据的丢失)
多线程的实现
继承Thread和实现Runnable接口的俩种常规实现多线程方法
其它还有比如:线程池创建、timer定时器创建等等不累赘阐述,实质上只有一种实现方法:继承Thread,其它方法源码里面都是构造一个Thread类对象,要不你怎么使用run方法呢😁。Thread源码中也是实现了Runnable接口的。
多线程运行机制
- 启动多线程不能直接调用run方法,必须要用start方法,要不然调用run跟使用普通方法没有区别(尽管主体代码就在run方法中)。线程的调用需要JVM调用本地系统为线程分配资源:比如CPU、I/O设备等,而start方法调用了本地线程实现交互运行,你使用run的话就按顺序执行了呗~(论JVM学习的重要性)。
资源共享
- 实现Runnable方法可以实现资源共享。同时相对于继承Thread方法而言,避免了单继承带来的局限性和可以共享代码(RUN)方法的优势。
- Runnable对象如果里面有构造方法,只会执行一次,执行对象为构造Runnable对象的线程(下面代码为main)。在用其创建Thread的时候,并不会再次构造。
class MyRunnable implements Runnable {
private int tickets = 3;
@Override
public void run() {
// TODO Auto-generated method stub
while(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
}
}
}
MyRunnable my = new MyRunnable();
Thread thr1 = new Thread(my, "my装饰的Runnale壹号窗口");
Thread thr2 = new Thread(my, "my装饰的Runnale二号窗口");
Thread thr3 = new Thread(my, "my装饰的Runnale三号窗口");
thr1.start();
thr2.start();
thr3.start();
Thread的常用方法
注意到直接调用Thread.xx()方法是控制当前代码所在线程。
-
Thread.currentThread()
获取当前线程,后续可.getName()和.setName对当前线程名字进行获取和设置。 -
isAlive()
判断线程是否启动或存在。 -
join()
在其它线程运行的时候,强制运行线程获取CPU资源。 -
sleep()
暂时休眠,释放资源,参数为毫秒,1000ms=1s -
interrupt()
强制中止,注意是中止不是终止,详细实现原理请看:Volatile和interrupt原理 -
yield()
礼让线程:让当前正在执行的线程暂停而不是阻塞线程,将线程从运行状态入就绪状态让cpu调度器重新调度然后继续执行,注意礼让不一定都成功 。
礼让不一定都成功原因(源码注释): A hint to the scheduler that the current thread is willing to yield.its current use of a processor. The scheduler is free to ignore this hint.
翻译一下:线程调用yeild方法后向调度程序提示愿意放弃占有资源进行礼让,调度程序可以忽略该提示。。。😂笑哭
class RunnableImp implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<4;i++) {
System.out.println("执行线程:" + Thread.currentThread().getName());
if(i==2) {
System.out.println(Thread.currentThread().getName() + "已经执行了俩次进行礼让");
Thread.yield();
}
}
}
}
public class DoYeild {
public static void main(String[] args) {
RunnableImp myRunnable = new RunnableImp();
Thread t1 = new Thread(myRunnable,"A");
Thread t2 = new Thread(myRunnable,"B");
Thread t3 = new Thread(myRunnable,"C");
Thread t4 = new Thread(myRunnable,"D");
Thread t5 = new Thread(myRunnable,"E");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
- 优先级
class ThreadDemo extends Thread{
public void run() {
System.out.println("运行线程:" + Thread.currentThread().getName());
}
}
//优先级数:1到10,默认线程优先级为5
public class DoPriority {
public static void main(String[] args) {
ThreadDemo t1 = new ThreadDemo();
ThreadDemo t2 = new ThreadDemo();
ThreadDemo t3 = new ThreadDemo();
ThreadDemo t4 = new ThreadDemo();
ThreadDemo t5 = new ThreadDemo();
t1.setPriority(1); //Thread-0
t2.setPriority(4); //Thread-1
t3.setPriority(6); //Thread-2
t4.setPriority(8); //Thread-3
t5.setPriority(10); //Thread-4
t1.start();t2.start();t3.start();t4.start();t5.start();
}
}
同步和死锁
同步
- 同步是指多个操作在同一个时间段内只能有一个线程对共享资源进行操作,其它线程只有等到此线程对该资源的控制完成之后才能对共享资源进行操作。
- 举反例,上面Runnable接口实现了资源共享,但是并未解决同步问题: 将票数提升至5张,并增加延迟(实际操作必不可免的),出现了
while (tickets > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
}
运行不同次结果不同,且票数不符合预期。
- 举例解释一种-1和0可能出现的情况,eg:在卖出第5张票的时候,上俩个窗口在票数自减前,读取到票数剩余1张,符合tickets>0条件,进入循环,后三个线程都运行了tickets–,造成0和-1出现。
- 同理第二张图出现重复的2,是因为几个线程几乎同时进行了tickets–操作,内存中数据同步延迟造成的结果。
快捷键目录标题文本样式列表链接代码片表格注脚注释自定义列表LaTeX 数学公式插入甘特图插入UML图插入Mermaid流程图插入Flowchart流程图
注释复制
[^d1]
如何解决?
采用同步关键字:synchronized
,具体用法如下:
- 同步代码块
使得被synchronized关键字包围({… …})的代码块部分只能同时运行一个对象访问。我们先用同步代码块解决第二张结果图出现俩个2的问题,用关键字锁住包含了tickets–操作的代码块:
public void run() {
// TODO Auto-generated method stub
while (tickets > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//this指的是操作代码块的对象,使得一个时间段只有一个对象操作该代码块
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
}
}
}
结果是依旧会出现-1和0,但是不会出现重复的票数。说明我们让线程们排队进行tickets–操作,即数据读出,写回一气呵成后,其它进程才能再读出和写回。
- 同步方法
在方法面前直接使用synchronized
关键字修饰,即整个方法都是被synchronized
包括的代码块:
public synchronized void run()
结果是,控制台依次打印票数,不再出现-1和0以及重复数据:
死锁
- 死锁是指俩个线程都在等对方释放所需要的资源,从而导致不断的等待,造成程序停滞。举例:
收银员要求你先付钱再给你发票,而你要求先给发票再付钱。俩个人都不退让,就是这么任性,所以死锁。
再举例:
荣耀和小米俩个人同时过独木桥,桥只能一个人过无法做除了向前的动作外的其它动作,桥长10000米,二者可见度100米,他们俩终将会再桥中间碰面,谁也无法让谁,死锁。