线程学习笔记
-
进程:是系统进行资源分配和调度的独立单元,每一个进程都有它的独立内存空间和系统资源。
-
线程:是进程里面的一条执行路径,每个线程共享进程里面的内存空间和系统资源。同一进程中的线程是互抢资源的竞争关系。
-
线程与进程:一个进程中可以有一个到多个线程,一个线程只属于一个进程。CPU时间片:内存空间和系统资源。
-
线程实现的三种方式。前两种是重点
- 继承thread类方式实现线程
如:
/** * 自定义线程类 * @author * @version 1.0 2019年9月9日 */ public class MyThread extends Thread{ /** * 设置线程名字 * @param s */ public MyThread(String s) { this.setName(s); } /** * 重写run方法 */ @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } } /** * 测试类 * @author * @version 1.0 2019年9月9日 */ public class Test { public static void main(String[] args) { // 创建线程对象 MyThread t1 = new MyThread("线程1"); MyThread t2 = new MyThread("线程2"); // 调用方法给线程设置名称 // t1.setName("线程A"); // t2.setName("线程B"); // 启动线程 t1.start(); t2.start(); } }
- 实现Runnable接口方式实现线程,如:
/** * 线程任务类 * @author sx * @version 1.0 2019年9月9日 */ public class MyRunnable implements Runnable{ /** * 重写父接口中任务方法 */ @Override public void run() { for (int i = 1; i <=10; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } } public static void main(String[] args) { //创建任务对象 MyRunnable r1=new MyRunnable(); MyRunnable r2=new MyRunnable(); //将任务对象封装成线程对象 Thread t1=new Thread(r1, "线程1"); Thread t2=new Thread(r2, "线程2"); //启动线程 t1.start(); t2.start(); } //测试类一样
- 实现Callable接口方式实现线程(少用)
- 继承thread类方式实现线程
-
给线程取名常用方式:
- 调用setName方法
- 使用构造方法
-
继承Thread类方法 VS 实现Runnable接口方式实现
- 代码简洁:继承Thread类实现线程代码更加简洁,实现Runnable接口方式代码稍微复杂一点。
- 可扩展性:继承Thread类不能再继承其他类,只能实现接口,所以扩展性更差;实现Runnable接口方式实现线程还可以在继承其他的类,实现其他的接口,所以扩展性更好。
- 资源共享性:继承Thread类方式,只能通过静态变量来实现资源共享,但是耗内存,实现Runnable接口方式,只要让多个线程共用一个任务对象就可以实现资源共享,所以不耗内存。
-
线程的优先级:优先级越高的线程抢占资源的概率越高,优先级低的线程抢占资源的概率越低。优先级高的线程不一定抢得到cpu时间片,优先级低的线程也不一定抢不到cpu时间片。设置线程优先级实现方式:要在启动线程之前设置线程的优先级:线程对象.setPriority(int newPriority):
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//设置线程的优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
//启动子线程
t1.start();
t2.start();
- 线程休眠:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),一旦休眠期到了,又重新参加资源抢占。设置线程睡眠:对象名.sleep(long millis)或者 Thread.sleep(long millis)(推荐后者):
public static void main(String[] args) throws InterruptedException {
//声明一个数组存6个学员的姓名
String[] names= {"aa","bb","cc","ddd","ee","ff"};
//生成随机数,作为抽取学员姓名的索引
int r=(int)(Math.random()*names.length);
for (int i = 3; i >=1; i--) {
System.out.println(i);
Thread.sleep(1000);
}
System.out.println("抽中的学员为:"+names[r]);
}
- 线程礼让:暂停当前正在执行的线程对象,并执行其他线程。使用对象名.yield();或者Thread.yield();实际上当一个线程礼让,另外一个线程还是有可能抢占不到资源,有点类似于线程的优先级,没有什么太大的效果。
/**
* 线程任务类
* @author sx
* @version 1.0 2019年9月9日
*/
public class MyRunnable implements Runnable{
/**
* 重写父接口中任务方法
*/
@Override
public void run() {
for (int i = 1; i <=100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
//线程B每运行一次就礼让 一次
if (Thread.currentThread().getName().equals("线程B")) {
Thread.yield();
}
}
}
}
public static void main(String[] args) {
//创建任务对象
MyRunnable r1=new MyRunnable();
MyRunnable r2=new MyRunnable();
//将任务对象封装成线程对象
Thread t1=new Thread(r1, "线程A");
Thread t2=new Thread(r2, "线程B");
//启动线程
t1.start();
t2.start();
}
- 线程合并将一个线程合并到另一个线程中,全成一条执行路线,合并过来的线程先执行完,再执行原来的线程。线程合并的使用:线程对象.join();
- 子线程合并到主线程中:
public static void main(String[] args) throws InterruptedException { //创建子线程对象 MyThread1 t1=new MyThread1(); //启动线程 t1.start(); //主线程执行任务 for (int i = 1; i <=200; i++) { System.out.println(Thread.currentThread().getName()+":"+i); //当主线程运行到10时, 子线程插队 if (i==10) { t1.join(); } } }
- 子线程A合并到子线程B中:
/** * 线程类 * @author * @version 1.0 2019年9月9日 */ public class MyThread2 extends Thread{ //声明一个线程对象 MyThread2 t; public MyThread2() { } /** * 将线程对象作为参数传过来构造方法 * @param t */ public MyThread2(MyThread2 t) { this.t=t; } /** * 重写父类的任务方法 */ @Override public void run() { for (int i = 1; i <=200; i++) { System.out.println(Thread.currentThread().getName()+":"+i); //当线程B运行到10时,线程A合并过来 if (i==10&&Thread.currentThread().getName().equals("线程B")) { try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public static void main(String[] args) throws InterruptedException { //创建子线程对象 MyThread2 t1=new MyThread2(); //用构造方法将线程A对象传线程B对象作属性 MyThread2 t2=new MyThread2(t1); //设置线程的名称 t1.setName("线程A"); t2.setName("线程B"); //启动线程 t1.start(); t2.start(); }
- 线程中断:
- 用中断方法来中断线程:
//interrupt()中断线程。 //interrupted() 测试当前线程是否已经中断 /** * 线程类 * @author * @version 1.0 2019年9月9日 */ public class MyThread1 extends Thread{ /** * 重写父类的任务方法 */ @Override public void run() { for (int i = 1; i <=200; i++) { //判断中断状态值,跳出循环结构 if (Thread.currentThread().isInterrupted()==true) { break; } System.out.println(Thread.currentThread().getName()+":"+i); } } } public static void main(String[] args) throws InterruptedException { //创建线程对象 MyThread1 t1=new MyThread1(); //启动线程 t1.start(); for (int i = 1; i <=200; i++) { System.out.println(Thread.currentThread().getName()+":"+i); //当主线程运行10时,中断子线程 if (i==10) { t1.interrupt();//改变子线程的中断状态值 //主线程睡眠让出cpu时间片让子线程中断 Thread.sleep(2000); } } }
- 作标记方法:
public class MyThread2 extends Thread{ //声明一个成员变量作标记 boolean flag=false; /** * 重写父类的任务方法 */ @Override public void run() { for (int i = 1; i <=200; i++) { //判断中断状态值,跳出循环结构 if (flag==true) { break; } System.out.println(Thread.currentThread().getName()+":"+i); } } } public static void main(String[] args) throws InterruptedException { //创建线程对象 MyThread2 t1=new MyThread2(); //启动线程 t1.start(); for (int i = 1; i <=200; i++) { System.out.println(Thread.currentThread().getName()+":"+i); //当主线程运行10时,中断子线程 if (i==10) { t1.flag=true;//改变子线程的中断状态值 //主线程睡眠让出cpu时间片让子线程中断 Thread.sleep(2000); } } }
- 线程的生命周期
- 新建状态:当一个线程对象被New出来后,它就处于新建状态.
- 就绪状态:当一个线程调用start()方法后或者阻塞状态的线程解除阻塞时,它就处于就绪状态.
- 运行状态:当一个就绪状态的线程抢到cpu时间片时,运行run()时间时,它就处于运行状态.
- 阻塞状态:当一个线程调用sleep(时间)或wait()时,它就处于阻塞状态.
- 死亡状态:当一个线程调用stop()方法时或run()方法执行结束后,线程即处于死亡状态。处于 死亡状态的线程不具有继续运行的能力。
- 守护线程(精灵线程):守护线程守护的是所有非守护线程,一旦所有非守护线程死亡,守护线程自动死亡.在线程启动之前设置线程是否为守护线程:线程对象.setDaemon(boolean on)