JavaSE-线程线程的同步机制

Posted Recently 祝祝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE-线程线程的同步机制相关的知识,希望对你有一定的参考价值。

今天内容: (1)线程 (2)线程的同步机制

1.线程

1.1 基本概念
程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
进程 - 主要指运行在内存中的程序。

目前主流的操作系统都支持多进程,为了使得操作系统能够同时执行多个任务(核心),但进程是重量级的,新建进程对系统的资源消耗比较大,因此进程的数量比较局限。
while(true){fork();}—》fork炸弹,不过2019年之后就不太好用了

线程是进程内部的程序流,也就是操作系统中支持多进程,而每个进程的内部又可以支持多线程,线程是轻量级的,新建线程会共享所在进程的系统资源,因此以后的开发中都采用多线程技术。
多线程技术采用时间片轮转法实现并发执行,所谓并发就是指宏观并行微观串行

1.2 线程的创建(重中之重)
(1)创建和启动的方式
java.**lang.**Thread类用于描述线程,Java 虚拟机允许应用程序并发地运行多个执行线程,而线程的创建和启动方式两方法如下:
(1)自定义类继承Thread类并重写run方法(不重写它什么也不干,重写之后它在才能干我们自己想让它干的事情),创建该类的实例调用start方法。
SubThreadRunTest类:

package cn.learn.day17;

public class SubThreadRunTest extends Thread {

	@Override
	public void run() {
		System.out.println("这就是重写以后的run方法,"
				+ "该线程需要执行的任务可以写在这里哦!");
	}
	
	public static void main(String[] args) {
		
		// 声明Thread类型的引用指向子类的对象,形成多态
		Thread t1 = new SubThreadRunTest();
		// 调用run方法,编译阶段调用父类的,运行阶段调用子类的
		t1.run();
	}

}

重写:

package ThreadTest1;

public class SubThreadRunTest  extends Thread {
//重写run方法
    @Override
    public void run(){
        System.out.println("这就是重写以后的run方法," +
                "该线程需要执行的任务可以写在这里哟");
    }
    public static void main(String[] args) {
        // 声明Thread类型的引用指向子类的对象,形成多态
         Thread t1=new SubThreadRunTest();
        // 调用run方法,编译阶段调用父类的,运行阶段调用子类的
        t1.run();
    }
}

在这里插入图片描述

多态:引用指向子类的对象,形成多态,编译阶段调用父类的,运行阶段调用子类的

(2)自定义类实现Runnable接口并重写run方法,创建该类的实例作为实参来构造 Thread类型的对象,然后使用Thread类型的对象调用start方法。

package ThreadTest1;

public class SubRunnableTest implements Runnable{

    @Override
    public void run(){
        for(int i = 0; i < 20; i++) {
            System.out.println("run方法中:i = " + i);
        }
    }

    public static void main(String[] args) {
        // 声明自定义类的引用指向该类的对象
        SubRunnableTest srt=new SubRunnableTest();
        // 使用上述引用作为实参构造Thread类型的对象
        // 由源码可知:srt作为实参一路传递,最终赋值给了Thread类中成员变量target
        Thread t1=new Thread(srt);
        // 使用Thread类型的对象调用start方法
        //srt.start(); error start方法来自于Thread类而不是实现类
        // 启动该线程,由Java虚拟机自动调用该线程的run方法,调用Thread类的run方法
        // 由run方法源码可知:target != null的条件成立,执行target.run()的代码
        // 而此时target已经变成了srt,因此最终调用上面重写的run方法。
        t1.start();
        for(int i = 0; i < 20; i++) {
            System.out.println("------主方法中:i = " + i);
        }

    }
}

在这里插入图片描述

(2)相关方法的解析
不传引用:
Thread() - 使用无参方式构造对象。
run方法什么也不干:

package ThreadTest1;

public class ThreadrunTest {
    public static void main(String[] args) {
//       1:声明Thread类型的引用指向改类型的对象
//        由构造方法的源码可知:Thread类中成员变量target的数值为null
        Thread t1=new Thread();
//        2:调用run方法进行测试
//        由run方法发源码可知:成员变量target的数值为空,导致条件不成立
//        由此{}括起来的代码不会执行,因此run方法确实啥也不干
        t1.run();
//        3:打印一句话
        System.out.println("山无棱,天地合,才敢与君绝");
    }
}

在这里插入图片描述

Thread(String name) - 根据参数指定的名称来构造对象。
传引用:
Thread(Runnable target) - 根据参数指定的接口引用来构造对象。

Thread(Runnable target, String name) - 根据参数指定的引用和名称构造对象。

void run() - 若使用Runnable类型的引用构造出来的对象调用该方法,则最终调用
引用所指向对象的run方法,否则调用该方法啥也不做。
void start() - 用于启动线程,Java虚拟机会自动调用该线程的run方法。

package ThreadTest1;

public class SubThreadstartTest extends Thread {
    @Override
    public void run(){
        for (int i=0;i<10;i++){
            System.out.println("run方法中:i="+i);
        }
    }

    public static void main(String[] args) {
        // 声明Thread类型的引用指向子类的对象,形成多态
        Thread t1=new SubThreadstartTest();
        // 调用run方法就是成员方法的调用,根据方法名跳转过去执行完再跳转回来
        // 调用run方法没有交错的效果
//        t1.run();
        // 调用start方法后两个方法的输出语句会发生交错打印,每次交错的效果可能不一样
        t1.start();
        for (int i=0;i<10;i++){
            System.out.println("----主方法中---:i="+i);
        }

    }


}

在这里插入图片描述
(3)原理分析
a.执行main方法的线程叫做主线程,执行run方法的线程叫做子线程。
b.main方法是程序的入口,最开始只有主线程来依次执行main方法中的代码,当
start方法调用成功后,线程的个数瞬间由1个变成了2个,其中子线程去执行run
方法,主线程继续执行main方法的代码,两个线程各自独立运行互不影响。
c.当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束,但两个
线程谁先执行没有明确的规定,取决于操作系统的调度算法。

注意:思考:线程创建和启动两种方式的优缺点是什么???
线程创建和启动的方式一相对来说代码简单,但Java语言中只支持单继承,若该类继承Thread类后则无法继承其它类;而方式二相对来说代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后开发中推荐方式二。

1.3 线程的编号(PNO)和名称(会用即可)
线程的编号相当于:人的身份证号
线程的名称相当于:人的名字
名字可以改,编号不能改
成员方法中有一个隐藏的this.的作用
在这里插入图片描述

1.4常用方法:
在这里插入图片描述

测试:

package ThreadTest1;

public class SubThreadIdTest extends Thread{
    public SubThreadIdTest(String string) {
        super(string);
    }
    @Override
    public void run(){
        System.out.println("当前线程的编号是:"+getId()+",名称是:"+getName());
        setName("zhangfei");
        System.out.println("修改后线程的编号是:" + getId()
                + ",名称是:" + getName());

    }

    public static void main(String[] args) {
        Thread t1=new SubThreadIdTest("shimmer");
        t1.start();
        System.out.println("---------------------");

        // 获取当前正在执行线程的引用,而当前正在执行的线程是主线程
        // 获取主线程的引用
        Thread t2= Thread.currentThread();
        // 获取主线程的编号和名称
        System.out.println("主线程的编号是:"+t2.getId()+",名称是:"+t2.getName());


    }
}

package ThreadTest1;

public class NonameTest {

    public static void main(String[] args) {
        //匿名内部类的语法格式:父类/接口类型 变量名 = new 父类/接口类型(){重写};
        // 1.使用继承和匿名内部类的方式创建和启动线程
        /*Thread t1=new Thread(){
        @Override
        public void run(){
            System.out.println("张三:在吗?");
            }
        };
        t1.start();*/
        new Thread(){
            @Override
            public void run(){
                System.out.println("shimmer:在吗?");
            }
        }.start();


    /*Runnable rr=new Runnable() {
        @Override
        public void run() {
            System.out.println("李四:在呀,咋啦?");
        }
    };
    Thread t2=new Thread(rr);
    t2.start();*/
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("祝祝:在的呢");
            }
        }).start();


    }
}

在这里插入图片描述

package ThreadTest1;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SubsleepThreadTest extends Thread{
    private boolean flag =true;
    // 子类重写的方法不能抛出更大的异常
    @Override
    public void run(){
        while (flag){
            // 获取当前系统时间
            Date d1=new Date();
            // 调整一下时间的格式
            SimpleDateFormat sdf=new
                    SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println("当前系统时间:"+sdf.format(d1));
            // 睡眠一秒后再次打印系统时间  1秒 = 1000毫秒
            try{
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 让主线程睡眠5秒钟,5秒后将子线程停止
        Thread t1=new SubsleepThreadTest();
        t1.start();
        System.out.println("主线程开始睡眠....");
        try{
            Thread.sleep(5000);

        }catch (Exception e){
            e.printStackTrace();
        }
        ((SubsleepThreadTest)t1).flag=false;
        System.out.println("子线程终止了!");
    }
}

方法重写不能抛出更大的异常

在这里插入图片描述
DEMO1:
Daemon

package ThreadTest1;

public class ThreadDaemonTest extends Thread{
    @Override
    public void run() {
        // 对于新建的线程来说,默认不是守护线程
        // 当子线程不是守护线程时,主线程先结束,但子线程依然会打印完毕所有数据
        // 当子线程是守护线程时,守护线程通常为其它线程服务,此时会发现若主线程
        //     结束,则子线程会随之结束
        System.out.println(isDaemon() ? "是守护进程" : "不是守护进程");
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程中:i = " + i);
        }
    }

    public static void main(String[] args) {
        Thread t1=new ThreadDaemonTest();
        // 设置线程为守护线程
        t1.setDaemon(true);
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程中:i = " + i);
        }


    }
 }


Join:

package ThreadTest1;

public class Threadjoin extends Thread{

    @Override
    public void run(){
        System.out.println("倒计时开始...");
        for (int i=0;i<10;i++){
            System.out.println(i);
            try{
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        System.out.println("happy every day,learning for everyday");
    }

    public static void main(String[] args) {
        Thread t1=new Threadjoin();
        t1.start();
        System.out.println("主线程开始的等待");
        try {
            // 表示当前正在执行的线程等待调用对象所表示的线程终止
            // 也就是主线程等待子线程终止
            // t1.join();  海枯石烂
            t1.join(3000);//等待3秒

        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("Say Goodbye");
    }

}

在这里插入图片描述

Priority:

package ThreadTest1;

public class ThreadPriorityTest extends Thread{
    @Override
    public void run(){
        // 5 一般的优先级  ctrl+/ 采用单行注释
        // 优先级越高的线程不一定先执行,但该线程获取到时间片的机会越多
        // 也就是越有可能它先执行完毕
//		System.out.println("当前线程的优先级是:" + getPriority());
        for (int i=0;i<30;i++){
            System.out.println("子线程中:i="+i);
        }

    }

    public static void main(String[] args) {
        Thread t1=new ThreadPriorityTest();
        // 设置子线程的优先级为最高优先级
        t1.setPriority(MAX_PRIORITY);
        t1.start();
        for (int i=0;i<30;i++){
            System.out.println("-----主线程中:i="+i);
        }

    }
}

作业:

1.重点掌握线程创建的两种方式和sleep以及join方法的使用。
2.编程创建两个线程,线程一负责打印1 ~ 100之间的所有奇数;
其中线程二负责打印1 ~ 100之间的所有偶数;
在main方法启动上述两个线程同时执行,主线程等待两个线程终止;
3.编程实现Account类的封装,特征有:账户余额;
编程实现AccountTest类,在main方法中创建对象并传入1000元,最后打印余额

作业代码,重点实例:https://shimo.im/docs/QxxXKhPj6hVg3GVX

以上是关于JavaSE-线程线程的同步机制的主要内容,如果未能解决你的问题,请参考以下文章

JavaSE基础(十 一 )--<线程>线程同步,死锁,Lock锁,线程通信,生产消费问题,新增的线程创建方式

[javaSE] 多线程通信(等待-唤醒机制)

JavaSE

线程同步-使用ReaderWriterLockSlim类

多线程 Thread 线程同步 synchronized

线程同步机制