java多线程(一)
声明:本博客只用于本人笔记,不保证笔记正确性,但欢迎指出错误,如有遇喷子,直接反击加举报~~
进程、线程
进程是是一个应用程序(1个进程是一个软件)。
线程是一个进程中的执行场景/执行单元。
一个进程可以是多个线程。
例如:对于java程序来说,当在DOS命名窗口中输入:java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
进程和线程是什么关系?
举个例子:
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
华为:进程
任正非:华为的一个线程
进程可以看做是现实生活中的公司。
线程可以看做是公司当中的某个员工。
注意:
进程A和进程B的内存不共享。(阿里巴巴和京东资源不会共享!)
在java语言中线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。
假设启动10个进程,会有10个栈空间,栈和栈之间互不干扰,各自执行各自的,这个就是多线程并发。
火车站,可以看做是一个进程。火车站中的每一个售票可以看做是一个线程。就好比我在窗口1购票,你可以在窗口2购票,那你不需要等我,我也不需要等 你。所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
使用了多线程机制之后,main方法结束,其他线程也 不一定结束,因为main方法结束之时主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
真正的多线程并发:t1线程执行t1的,t2线程执行t2的,t1不会影响t2,但是t2也不会影响t1。这叫真正的多线程并发。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
单核的CPU表示只有一个大脑:对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人的感觉就是多个事情同时再做…
不能够做到真正的多线程并发,但是可以给人一种“多线程并发的感觉。”
java支持多线程。并且java已经将多线程实现了,我们只需要继承就行了。
实现线程的方式
第一种方式:编写一个类,直接继承java.langThread,重写run方法。
/*
注意:亘古不变的道理:代码永远都是自上而下的顺序一次逐行执行的
*/
public class Thread01
{
public static void main(String[] args){
//创建线程对象
MyThread t = new MyThread();
/*
start()作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,在任务完成之后,瞬间就结束了,
这段代码的任务只是为了开启一个新的一个栈空间,只要新的栈空间出来,start()方法瞬间结束,线程就启动成功了
启动成功后的线程会自动调用这个run()方法,并且run方法在分支栈的栈底部(压栈)。
run()方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
*/
t.start();//启动线程
for(int i = 0 ; i < 1000 ; i++){
System.out.println("主线程------->" + i);
}
}
}
//定义线程类
class MyThread extends Thread
{
public void run(){
for(int i = 0 ; i < 1000 ; i++){
System.out.println("分支线程------->" + i);
}
}
}
运行结果为:
但是如果这个地方没有启用分支栈(也就是没有启动除主线程以外的线程)而是直接的调用run()方法。程序是不会并发的执行的。观察以下的代码:
/*
注意:亘古不变的道理:代码永远都是自上而下的顺序一次逐行执行的
*/
public class Thread01
{
public static void main(String[] args){
//创建线程对象
MyThread t = new MyThread();
t.run();
//my.start();//启动线程
for(int i = 0 ; i < 1000 ; i++){
System.out.println("主线程------->" + i);
}
}
}
//定义线程类
class MyThread extends Thread
{
public void run(){
for(int i = 0 ; i < 1000 ; i++){
System.out.println("分支线程------->" + i);
}
}
}
运行的结果为:
下面用内存图的方式来分析分析。
执行run()方法的内存分析
先把main方法栈帧压进来,然后执行MyThread t = new MyThread()
,随后在堆空间中开辟一块内存空间用来存放MyThread对象,这段执行完以后开始执行run()方法,注意:这里只是调用run方法,并没有开启一个新的线程,把run方法压入主线程。
run方法开始执行,然后开始打印0–999
等run方法执行完以后,run方法栈帧就结果了不在执行,然后程序才会往下继续的执行
最后才执行下面的代码,也就是main方法主栈的0–999。
执行start()方法的内存图分析
前面和上面的一样,等到了执行到start()方法以后,会停下来
把start()方法压入主线程栈中
start()方法任务是开辟一个新的栈空间,称为分支栈,只要开完这个方法就结束。
开辟出新的空间,start()方法结束
然后两个分支栈和主栈各自执行(并发执行),互不干扰。
run()方法是不需要手动来调用的,它直接由JVM线程调度机制来运作的。
第二种方法:编写一个类实现java.lang.Runnable接口,实现run方法
public class RunnableTest{
public static void main(String[] args){
//把m包装成Runnable类型
MyRunnable m = new MyRunnable();
//然后传进thread()里面,因为Thread提供了一个构造方法,Thread(Runnable target)
Thread t = new Thread(m);
t.start();//启动一个分线程
for(int i = 0 ; i < 100 ; i++){
System.out.println("主线程----->"+i);
}
}
}
class MyRunnable implements Runnable{
//实现run()方法
public void run(){
for(int i = 0 ; i < 100 ; i++){
System.out.println("分支线程------->"+i);
}
}
}
运行结果:
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更加的灵活。
也可以用其他的方式实现,用匿名内部类方式实现
public class RunnableTest
{
public static void main(String[] args){
//使用匿名内部类
Thread t = new Thread(new Runnable(){
public void run(){
for(int i = 0 ; i < 100 ; i++){
System.out.println("分支线程------->"+i);
}
}
});
t.start();
for(int i = 0 ; i < 100 ; i++){
System.out.println("主线程-------->"+i);
}
}
}
运行结果:
多线程的生命周期
关于线程的生命周期:
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
刚 new 出来的线程对象处于新建状态,然后调用 start() 方法,进入到就绪状态以后就拥有了抢夺CPU的时间片的权利,抢到时间片以后执行 run() 方法,run() 方法执行标志着这个阶段进入到运行阶段,等到时间片用完以后又回到就绪状态继续抢夺,直到 run() 方法执行完成。等到 run() 结束以后这个线程就进入到了死亡状态。具体情况看一下图片
然后当线程遇到阻塞事件,例如接受用户输入,就会进入到阻塞状态,阻塞状态下,此线程会放弃之前占用的CPU时间片
当阻塞结束的时候,又回到就绪状态,继续抢夺CPU时间片
如果是多个线程抢夺时间片,抢夺到的进入到运行状态,等时间片用完又回到就绪状态,然后又开始抢夺时间片,就造成了并发的现象。
获取当前线程对象
- Thread.currentThread() (这个是静态的)获取当前线程对象
- 调用getName()获取当前线程的名字
- 调用setName()方法修改线程的名字
- 如果没有设置线程的名字,线程默认的名字是Thread-0、Thread-1、Thread-2…
public class Thread02 extends Thread
{
public static void main(String[] args){
//获取当前的线程的名字,在main中就是主线程
Thread currentThread = Thread.currentThread();
//调用getName()方法输出此线程的名字
System.out.println(currentThread.getName());
Thread02 t = new Thread02();
System.out.println(t.getName());
Thread02 t1 = new Thread02();
System.out.println(t1.getName());
t.setName("分支t");
System.out.println(t.getName());
t.start();
t1.start();
}
public void run(){
for(int i = 0 ; i < 100 ; i++){
//在分线程中调用就是分线程的名字,谁启动它就是谁的名字~~
Thread cc = Thread.currentThread();
System.out.println(cc.getName()+"----->"+i);
}
}
}
运行结果为:
关于线程的sleep()方法
- 静态方法Thread.sleep(1000);
- 参数是毫秒数
- 作用是:让当前线程进入到休眠状态,进入到“阻塞状态”,并且放弃CPU时间片,将CPU时间片让给其他线程
- 这代码出现在 A 的时候就让 A 线程睡眠,出现在 B 线程就会让 B 线程睡眠
- Thread.sleep()方法,可以做到这种效果,间隔特定的时间,去执行特定的代码,每隔多久执行一次
public class Thread03
{
public static void main(String[] args){
// try{
//让 main 线程睡眠1毫秒
Thread.sleep(1000);
/*}catch(InterruptedException e){
e.printStackTrace();
}*/
System.out.println("5秒以后执行这个语句");
}
}
注意:这个地方要抛异常,如果没有抛异常会出现以下情况:
关于Thread.sleep()的一个面试题
public class Thread04
{
public static void main(String[] args){
//多线程
Thread t = new MyThread();
t.setName("t");
t.start();
try{
t.sleep(1000*5);//这里调用会不会让t线程睡眠5秒????
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("hello world!");
}
}
class MyThread extends Thread{
public void run(){
for(int i = 0 ; i < 100 ; i++){
//输出当前线程的名字
System.out.println(currentThread().getName()+"----->"+i);
}
}
}
答案是不会~~
因为sleep()方法是一个静态方法,它和MyThread对象没有关系,也和t没有关系,就算是t去调也是没用,还是会转化为Thread.sleep(1000*5),关于静态方法是否被继承和重写可以参考一下我下篇(传送门)这行代码的作用是让当前代码进入睡眠5秒,在main线程里面所以会让main线程睡眠5秒…
终止正在睡眠的线程
/*注意:这不是终断线程的执行,而是终止线程的睡眠*/
public class ThreadInterrupted
{
public static void main(String[] args){
Thread t = new Thread(new Runnables());
t.setName("t");//将线程的名字设置为t
t.start();//开启一个新的线程
try{
Thread.sleep(1000*5);//让mian线程睡眠5秒以后醒来
}catch(InterruptedException e){
e.printStackTrace();//打印异常信息
}
//终断t的睡眠,这个利用了java的异常机制
t.interrupt();//相当于一盆冷水泼过去!!!是interrupt(),不是interrupted()
}
}
class Runnables implements Runnable{
//实现的接口的run方法
public void run(){
System.out.println(Thread.currentThread().getName()+"--------->begin");
try{
//sleep()方法是静态的,在哪个线程里面调,哪个线程就睡眠
Thread.sleep(1000*60*60*24*365);//让此线程睡眠1年
}catch(InterruptedException e){
e.printStackTrace();//打印异常信息
}
//1年以后才会执行到这里
System.out.println(Thread.currentThread().getName()+"--------->end");
}
}
运行结果为:
如果不想看到异常,可以把抛异常里面的输出语句打印出来。
强行终止一个线程的执行
public class EndThread
{
public static void main(String[] args){
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
//设置一个线程计数5秒
try{
Thread.sleep(1000*5);
}catch(InterruptedException e){
e.printStackTrace();
}
t.stop();// 已过时,强行终止线程
}
}
class MyRunnable implements Runnable
{
public void run(){
for(int i = 0 ; i < 10 ; i++){
System.out.println(Thread.currentThread().getName()+"------------>"+i);
try{
Thread.sleep(1000);//每一秒打印一个数
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
运行结果:
stop()方法的缺点:这种方式存在很大的缺点。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失,不建议使用。
那怎么合理的终止一个线程执行的呢?
public class Thread05
{
public static void main(String[] args){
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.setName("t");
t.start();
//计时5秒让线程终止
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("将flag设为false");
r.flag = false;
}
}
class MyRunnable implements Runnable
{
//打一个boolean标记
boolean flag = true;
public void run(){
for(int i = 0 ; i < 10 ; i++){
if(flag){
System.out.println(Thread.currentThread().getName()+"---->"+i);
//让线程每一秒输出一个
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}else{
//在这里面输入保存数据的代码保存数据保证数据不丢失
System.out.println("保存数据的代码.....");
return;
}
}
}
}
线程的调度
1.1 常见的线程调度模型有哪些?
- **抢占式调度模型:**哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些(多一些)。java采用的就是抢占式调度模型。
- **均分式调度模型:**平均分配CPU时间片。每个线程占有的时间片长度一样。平均分配,一切平等。
1.2 java中提供了哪些方法是和线程调度 有关系呢?
实例方法:
void setPriority(int newPriority)
:设置线程的优先级。int getPriority()
:获取线程优先级。- 最低优先级1
- 默认优先级5
- 最高优先级10
- 优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的)。
静态方法:
static void yield()
:让位方法。- 暂停当前正在执行的线程对象,并执行其他线程。
yield()
:方法不是阻塞方法,是让当前线程让位,让给其它线程使用。yield()
:方法的执行会让当前线程从“运行状态”回到“就绪状态”
实例方法:
void join()
合并线程
class MyThread1 extends Thread{
public void fun(){
MyThread t = new MyThread();
t.join();//当前线程进入阻塞,t线程执行知道t线程结束,当前线程才可以执行
}
}
class MyThread2 extends Thread{
}
1.3 线程的优先级
public class My_Thread
{
public static void main(String[] args){
Thread t = new Thread(new Threads());
t.setName("t");//将线程的名字设置为t
t.setPriority(10);//设置线程的优先级,(线程的优先级最高为10,最低为1,默认为5)
//Thread myCurrentThread = Thread.currentThread();//获取当前线程
Thread.currentThread().setPriority(1);//将main线程设置为优先级设置值为1、
System.out.println(Thread.currentThread().getName()+"的优先级是" + Thread.currentThread().getPriority());
t.start();//开启t线程
//main默认的线程是5。不过上面设置为了1
for(int i = 0 ; i < 10000 ; i++){
System.out.println(Thread.currentThread().getName()+"---->main"+i);
}
}
}
class Threads implements Runnable
{
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()+"---------------------->begin"+i);
}
}
}
1.4 线程让位
当前线程暂停,回到就绪状态,让给其他线程。
静态方法:Thread.yield();
public class ThreadYield
{
public static void main(String[] args){
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
for(int i = 0 ; i < 10000; i++){
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
class MyRunnable implements Runnable
{
public void run(){
for(int i = 0 ; i < 10000 ; i++){
if(i%100==0)//每让一次
Thread.yield();
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
static void yield()
让位方法
暂停当前正在实行的线程对象,并执行其他线程。
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
1.5线程合并
public class ThreadJoin
{
public static void main(String[] args){
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
try{
t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("main---over");
}
}
class MyRunnable implements Runnable
{
public void run(){
for(int i = 0 ; i < 1000 ; i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
运行结果:
注意:join()
方法只是当前线程受阻,他们两个然后让调用join方法线程的方法先执行,等调用join方法的线程执行完毕以后再执行当前线程。他们的栈没有合并,而是栈之间协调了。