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