zoukankan      html  css  js  c++  java
  • 线程(一)

    线程安全:StringBuilder非线程 StringBuffer线程 /  Vector线程 ArrayList非线程 /  快速迭代时不能有其他线程进行操作

    进程:操作系统结构的基础:是一个正在执行的程序,计算机中正在运行的程序实例

    线程:线程(thread)是进程中某个单一顺序的控制流,是程序运行的基本执行单元,任何一个程序中至少需要一个主线程

    线程优缺点:
    优点:程序执行思路步骤清晰,步骤分明
    缺点:一次只能做一件事情,必须做完意见事后才能继续做另一件事

    可以使用线程让多条业务逻辑同时处理成为可能!
    使用线程的好处:异步处理,简化编程模型,提高CPU使用

    线程同步:只有单一的线程,按照既定的程序流依次执行代码

    线程异步:多个线程同时执行代码

    线程的创建两种方式:

    继承Thread类  :public class MyThread extends Thread{}

    实现Runnable接口 :public class MyThread implements Runnable(){}

    两种方法都要重写run方法,该方法是线程的核心方法,用于封装线程要处理的业务内容

    run方法是线程类中最重要的方法,该线程中所有需要单独执行的业务逻辑都需在run方法中运行

    public class MyThread extends Thread{
        public void run(){
            //线程执行的内容
        }
    }

    两种方法的区别:
    继承自thread类是得到真正的一个线程对象,可以通过start方法开启线程
    实现runnable接口,仅仅是将线程中要实现逻辑进行了抽离,同时也便于多线程的并发处理。

    启用线程:使用start()方法启动线程

    线性远程:run()方法不需要我们自己调用,当调用start()方法时将自动调用run方法,直到run方法执行完毕或人为停止或挂起

    多线程的创建步骤: 建立多个类继承Thread类或实现Runnable接口

                                     实现run方法

                                     实例化线程类并调用start方法

    如果要多个线程实现同样的功能:
    1.写一个runnable中,封装至不同的线程中(这种方式更方便)
    2.继承thread,多次new可以实现相同功能(这种方式不方便,但是线程内容不同的时候这种方法更方便)

    线程的生命周期:线程和人一样也会生老病死经理各个阶段

    主要经历如下阶段
      准备就绪:  new
      运行时 :  runnable   //调用start后
      阻塞等待:   wait   (blocked   timed_waiting    waiting)
      终止完成:  terminated

    获取线程的当前状态:     getState()
    判断线程的状态:        isAlive()

    获得线程的名称:  getName()

    控制线程的主要方法
      start() 启动线程
      stop() 终止当前线程 (过时方法)
      suspend() 挂起线程
      resume() 继续挂起的线程
      join() 等待线程执行完毕
      yield() 暂缓线程
      sleep() 线程等待
      wait() 线程等待
      notify() 唤醒线程

    yeild、 join、suspend、sleep、wait等方法的调用,都可是程序进入阻塞等待状态,若一致处于阻塞状态,则成为线程死锁
    线程死锁:线程处于阻塞状态后无法恢复,该情形成为线程死锁(会导致程序无法继续进行,所以所有处于阻塞状态的线程最后都应恢复运行,恢复后继续进入运行时状态)

    例子:编写两个线程,并在各自线程中打印1-10的数值,观察结果

    通过继承获得的线程

    /**
     * 自定义线程,通过继承
     */
    public class MyThread1 extends Thread{//继承中有很多方法
        /**
         * 构造方法
         * 传入线程的名称
         * @param threadName
         */
        public MyThread1(String threadName){
            super(threadName);
         //构造方法中的状态是new,等待就绪
    //获取线程的状态 new System.out.println(this.getState()); //判断线程是否存活 false,只有调用了start之后才会为true System.out.println(this.isAlive()); }
    @Override
       //都要对run方法进行重写
    public void run() { //获得线程的名称 System.out.println("当前线程:"+getName()); System.out.println(this.getState()); System.out.println(this.isAlive());
         //循环打印
    for(int i = 1; i <= 10; i++){ System.out.println("线程A:"+i); } } }

    通过实现接口获得的线程

    /**
     * 使用Runable接口实现线程
     */
    public class MyThread2 implements Runnable{//runnable中只有一个run方法
    
        @Override
      //都要对run方法进行重写
    public void run() {
         //进行循环打印
    for(int i = 1; i <= 10; i++){ System.out.println("线程B:"+i); } } }

    测试类

    public class RunThead {
    
        /**
         * main方法的执行就会启动一条主线程
         * 该主线程通常也将其称为守护线程或幽灵线程
         * @param args
         */
        public static void main(String[] args) {
            System.out.println("程序开始运行");
            //实例化继承了Thead线程
            MyThread1 th1 = new MyThread1("线程A");
            //开启线程,不能直接访问run方法,不然就不是开启一个线程
            th1.start();
            try {
                //将线程加入线程队列,使线程阻塞
                th1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
         //若在外部打印状态和是否存活,状态则不一定,因为主线程和其余线程是同时进行的,不一定哪个线程先结束
         //但是加了th1.join()方法后线程阻塞了以后,则会先将线程走完,再进行主线程后面的代码,则会看到结束后的状态和不存活
    System.out.println(
    "main中的线程状态:"+th1.getState()); System.out.println("main中的线程是否存活:"+th1.isAlive()); //线程如果处于结束状态,无法再次启动,如果需要在启动,则需要重新创建
         
    th1 = null;//赋值为null意思是这个线程结束以后要弃用了 th1 = new MyThread1("");//重新创建启用 th1.start(); //实例化实现了Runnable接口的实例 MyThread2 runnable = new MyThread2();
         //如果实现接口后直接调用,则只能调用run方法,不能算一个线程
         //th2.run(); //可以将Runnable实例封装至线程中,才是线程 Thread th2 = new Thread(runnable); th2.start();
         //由主线程进行运行 System.out.println("程序运行结束"); } }

     例子:时间窗体实现长时间的走动

    /**
     * 通过窗体实现时间的走动
     */
    public class TimeFrame extends JFrame {
    //都写成属性,等到用的时候再new出来
    //标签,用于呈现文字 private JLabel lbl; //开始按钮 private JButton btnStart; //停止按钮 private JButton btnStop;
       //挂起按钮
    private JButton btnSuspend;    //恢复按钮 private JButton btnResume; //时间线程 private TimeThread thead;

       //构造方法
    public TimeFrame(){ //设置窗体大小和位置 this.setBounds(600, 400, 600, 300); //设置窗体的关闭策略,关闭窗体时程序同时关掉 this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //设置窗体的布局 this.setLayout(null); //初始化组件 initCompoent(); } /** * 初始化组件 */ private void initCompoent(){
         //初始化标签 lbl
    = new JLabel();
         //再标签中添加数据 lbl.setText(
    "这是一个标签,用于呈现文本");
         //设置标签的大小和位置 lbl.setBounds(
    100,100,400,25); //将标签添加至窗体 this.add(lbl);
         //初始化按钮 btnStart
    = new JButton("start");
         //设置按钮的大小和位置 btnStart.setBounds(
    100, 150, 100, 25);
         //将按钮添加至窗体
    this.add(btnStart); //绑定按钮的监听器 btnStart.addActionListener(new ButtonStartListener());

         //相同的操作设置按钮 btnStop
    = new JButton("stop"); btnStop.setBounds(200, 150, 120, 25); this.add(btnStop); //绑定按钮的监听器 btnStop.addActionListener(new ButtonStopListener());

         //相同的操作设置按钮 btnSuspend
    = new JButton("suspend"); btnSuspend.setBounds(300, 150, 120, 25); this.add(btnSuspend); //绑定按钮的监听器 btnSuspend.addActionListener(new ButtonSuspendListener());

         //相同的方法设置按钮 btnResume
    = new JButton("resume"); btnResume.setBounds(400, 150, 120, 25); this.add(btnResume);
         //绑定按钮的监听器 btnResume.addActionListener(
    new ButtonResumeListener()); } /** * start按钮监听器 */ public class ButtonStartListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //判断条件是为了按钮只能操作一次,多次重复点击是无用的
           //线程已经开始且线程不存活了,则可以新建一个线程
    //if(thead != null && !thead.isAlive()){
           // if(thead != null && thead.getState() == Thread.State.TERMINATED){ System.out.println("前一条线程已经结束,创建了新的线程"); //创建一条新的线程 thead = new TimeThread(); //启动线程 thead.start(); } //第一次运行线程,也可以新建一个线程 else if(thead == null){ System.out.println("第一次创建新的线程"); //创建一条新的线程 thead = new TimeThread(); //启动线程 thead.start(); } } } /** * Stop按钮监听器 */ public class ButtonStopListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //如果线程已被创建并且处于运行时状态 if(thead != null && thead.isAlive()){ //使线程停止,提前进入销毁状态 thead.stop(); } } } /** * Suspend按钮监听器 */ public class ButtonSuspendListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println(thead.getState()); //如果线程处于运行时状态(不能用isAlive判断,因为阻塞的状态下也是alive) if(thead != null && (thead.getState() == Thread.State.RUNNABLE || thead.getState() == Thread.State.TIMED_WAITING)){ //将线程挂起,线程进入阻塞状态;处于suspend阻塞下的线程只能通过resume方法恢复 System.out.println("线程挂起"); thead.suspend(); } } } /** * Resume按钮监听器 */ public class ButtonResumeListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //对suspend线程进行恢复 if(thead != null){ thead.resume(); } } } /** * 用于刷新时间的线程 */ public class TimeThread extends Thread{ private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void run() { while(true){ //获取系统时间 Date date = new Date(); //将日期格式化并写入标签 lbl.setText(sdf.format(date)); try { //使线程休眠,进入到阻塞的状态,当到达指定时间后,线程将自动恢复至运行时状态
                //1000毫秒是1s Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { //实例化窗体 TimeFrame frame = new TimeFrame(); //显示窗体 frame.setVisible(true); } }

    stop方法需要慎用,可能会引发多线程的数据安全问题,多线程使用过程中如果产生了并发访问(多个线程对同一个对象进行了访问:比如A线程往C集合中添加数据,同时B线程也要往C集合中添加数据),需要用到线程锁(对象锁)

    比如:A线程要访问C对象进行数据处理并将该C对象进行了锁定,另一个线程B也要访问C对象,但是需要等待A线程完成计算后,根据计算的结果再接着处理。此时如果A线程执行了stop方法,该方法将会释放对象锁,B线程此时会提前访问到C对象,B线程接下来的数据处理可能会出现问题

    stop方法再多线程开发过程中,如果没有涉及到并发处理的情形,是没有隐患问题的

    suspend将线程挂起,线程会进去阻塞状态,被挂起的线程只能同构resume进行恢复,两者必须一起进行使用
    suspend方法可能会引起死锁,使用suspend方法调用的线程必须要通过resume方法才能恢复(图形的suspend可以通过异步调用resume,但是如果不是图形状态,再本线程中suspend后无法对自身调用resume方法,就会无限阻塞引起死锁。除非用main方法主线程进行恢复,主线程中通过调用resume)

  • 相关阅读:
    高斯滤波
    最短路径与贪婪
    应该要知道的几个统计学定义.
    Linux network setting.
    Windows与自定义USB HID设备通信说明.
    Win32 WriteFile and ReadFile
    C语言原码反码补码与位运算.
    Linux下查看显示器输出状态以及修改显示器工作模式(复制 or 扩展)
    Web通信协议:OSI、TCP、UDP、Socket、HTTP、HTTPS、TLS、SSL、WebSocket、Stomp
    如何用搜狗拼音输入法输入希腊字母及各种上下标
  • 原文地址:https://www.cnblogs.com/gfl-1112/p/12650002.html
Copyright © 2011-2022 走看看