线程(一)

线程安全: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)

 

posted @ 2020-07-24 12:06  小小野生程序员sunny  阅读(146)  评论(0编辑  收藏  举报