说线程之前,先说说进程
进程
一、什么是进程
进程是系统进行资源分配和调用的独立单元,每一个进程都有它的独立内存空间和系统资源。
二、单进程操作系统和多进程操作系统的区别
a)多进程操作系统是指在同一个时段内可以执行多个任务(时间片)
b)边听音乐边敲代码,CPU切换非常快
c)Windows操作系统-多进程操作系统
d)Dos操作系统-单进程的操作系统
e)Linux操作系统-多用户多进程的操作系统
思考:现在的多核CPU是否可以让系统在同一个时刻可以执行多个任务吗?
理论上可以,但实际上还是同一时刻只能执行一个任务。
线程
三、什么是线程,理解线程和进程的关系
线程是进程里面的一条执行路径,每个线程同享进程里面的内存空间和系统资源
多线程在进程中会互抢资源(抢占资源具有随机性)
四、编写多线程的应用程序?
创建线程方式1: 创建一个类,继承Thread
说明:线程资源存放在寄存器区;(五大区域)
案例: 主线程和子线程各打印200次---互抢资源
创建线程方式1: 创建一个类,继承Thread
*/
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("子线程"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();//启动线程:将线程对象放入线程组,供cpu调度,cpu调度到你,则执行该对象的run方法
//thread.start(); //能否创建线程对象,启动多次? 不行
new MyThread().start(); //能否创建多个线程对象,启动多次? 不会, 每个对象的线程状态属性都初始为0
for(int i=0;i<10;i++){
System.out.println("主线程"+i);
}
}
}
创建线程方式2:实现runnable接口
class Task implements Runnable{
@Override
public void run() {
for(int i=0;i<200;i++){
System.out.println("子线程:"+i);
}
}
}
public class RunnableTest {
public static void main(String[] args) {
//new Thread(new Task()).start(); //接口实现多态方式
//thread.start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++)
System.out.println("子线程"+i);
}
}).start();
for(int i=0;i<10;i++)
System.out.println("主线程"+i);
}
}
两种实现方式之间的区别:
第一种方式每个线程对象都有一份成员属性
第二种方式所以线程对象共用一份成员属性
经典面试题:请问当我们编写一个单纯的main方法时,此时该程序是否为多线程的?为什么?
是多线程的,至少有一个垃圾回收线程(守护线程)。
五、线程的优先级
注:改变线程的优先级,只是修改执行线程的概率而已,线程执行还是随机的。
/**
* 如何给线程取名?
* 1.带参构造,调成员属性接受姓名
* 2.带参构造,调父类带参构造:super(name)
* 3.带参构造,调Thread.currentThread().getName()方法获取名字
*
*练习:在主线程中创建两个以上的子线程,并且设置不同优先级,观察其优先级对线程执行结果的影响。
*/
class MyThread extends Thread{
//private String name;
/*public MyThread(String name) {
super(name);
}*/
@Override
public void run() {
for(int i=0;i<20;i++)
//System.out.println(super.getName()+"子线程"+i);
System.out.println(Thread.currentThread().getName()+"子线程"+i);
}
}
public class Test {
public static void main(String[] args) {
MyThread myThreadA = new MyThread();
myThreadA.setName("a");
myThreadA.setPriority(Thread.MIN_PRIORITY);//设置最大优先级
myThreadA.start();
MyThread myThreadB = new MyThread();
myThreadB.setPriority(Thread.MAX_PRIORITY);
myThreadB.start();
}
}
六、让线程休眠 sleep
/**
练习:编写一个抽取学员回答问题的程序,要求倒数三秒后输出被抽中的学员姓名
i.采用数组存储6个学员姓名
ii.生成0-5之间的随机数,确定被抽中的学员
iii.在屏幕每隔一秒,依次输出3,2,1,然后输出学员姓名
*/
import java.util.Random;
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
String[] a = {"zz","xx","cc","dd","ee","ff"};
for(int i=3;i>=1;i--){
System.out.println(i);
Thread.sleep(1000);
}
int index = new Random().nextInt(a.length);
System.out.println(a[index]);
}
}
七、线程的礼让 yield
执行后会发现,B线程礼让A线程(B线程抢到资源后,放弃资源,再次和A线程争抢资源),但是A线程还是抢不过B线程。
/**
* 线程的礼让:礼让的线程抢到资源后,停止该次线程执行,继续开始争抢
*
*从概念分析:礼让的线程会执行的慢些,但不是绝对性的
*
*练习:创建两个线程A,B,分别各打印100次,从1开始每次增加1,其中B一个线程,每打印一次,就yield一次,观察实验结果
*/
class ThreadA extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("A"+i);
}
}
}
class ThreadB extends Thread{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("B"+i);
ThreadB.yield();
}
}
}
public class Test {
public static void main(String[] args) {
new ThreadB().start();
new ThreadA().start();
}
}
八、线程的合并 join
/**
* 合并(插队) 主线程和子线程同时运行,满足一定条件后,让子线程先运行至结束
*
*练习:主线程和子线程各打印200次,从1开始每次增加1,当主线程打印到10之后,让子线程先打印完再打印主线程
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++)
System.out.println("子线程2:" + i);
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程1:" + i);
if (i == 5) {
try {
myThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//子线程插主线程的队
}
}
}
}).start();
myThread.start();
}
}
九、线程的中断
/**
*2.设置开关变量进行中断应用
*
*案例:一个一直执行死循环的子线程,三秒后调用中断函数
*
*/
class MyThreadA extends Thread{
private boolean isbreak; //设置是否中断的开关
@Override
public void run() {
while(!isbreak){
System.out.println("一直执行的子线程");
}
}
public void setBreak(boolean b) {
this.isbreak = b;
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThreadA myThreadA = new MyThreadA();
myThreadA.setBreak(false);
myThreadA.start();
Thread.sleep(3000);
myThreadA.setBreak(true);
}
}
十、守护线程
Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。
只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
/**
* 守护线程:默默为用户线程服务的,只要用户线程还存在,则守护线程也存在;
* 只要用户线程都关闭了,则守护线程自动关闭
*
* 典型的守护线程---jvm中的垃圾回收器
*
*/
class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("一直执行的子线程");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setDaemon(true); //设置为守护线程
myThread.start();
for(int i=0;i<100;i++){
System.out.println("主线程。。。。");
}
}
}
十一、经典面试题:线程的生命周期
a)新建状态
i.在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时,它已经有了相应的内存空间和其它资源,但还处于不可运行状态。新建一个线程对象可采用线程构造方法来实现。
ii.例如:Thread thread=new Thread();
b)就绪状态
i.新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待CPU调用,这表明它已经具备了运行条件。
c)运行状态
i.当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。
d)阻塞状态
i.一个正在执行的线程在某些特殊情况下,如被人为挂起,将让出CPU并暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(2000)、wait()等方法,线程都将进入阻塞状态。阻塞时,线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
e)死亡状态
i.线程调用stop()方法时或run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。