学习目录
一、线程基本概念
程序
其实就是我们写的代码
进程
进程是指正在运行的程序 如:电脑运行的QQ,就是一个进程
线程
线程由进程创建的,是进程的一个实际运作单位;一个进程可以拥有多个线程
单线程
同一时刻,只允许执行一个线程
多线程
同一个时刻,可以执行多个线程 如:QQ可以打开多个聊天窗口
并发
同一时刻,多个任务交替执行,有一种“貌似同时”的感觉,简单说就是单核cpu实现的多任务
并行
同一时刻,多个任务同时执行,多核cpu可以实现并行
二、线程基本使用☆
1.创建线程
线程结构图
线程创建
- 方式一:继承Thread类,重写run方法
- 方式二:实现Runnable接口,重写run方法
2.快速入门
继承Thread类
要求:编写一个程序,开启一个线程,每隔一秒,输出“JAVA”,当输出3次后结束该线程
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test1.start(); //启动线程 -> 执行test1的run方法
for (int i = 0; i < 3; i++) {
System.out.println("主线程 "+Thread.currentThread().getName()); //主线程和子线程是交替执行..不会出现阻塞现象
Thread.sleep(1000);
}
}
}
class test1 extends Thread{
int sum = 0;
//该run方法并不是Thread类中的,而是Thread实现的Runnable接口中的run方法
@Override
public void run() {
while (true){
sum++;
System.out.println("java 子线程 "+Thread.currentThread().getName());
try {
sleep(1000); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum==3){
break;
}
}
}
}
实现Runnable接口
由于java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时如果需要继承Thread类显然是不可能的,所以可以通过实现Runnable接口
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
Thread thread = new Thread(test1);
thread.start();
}
}
class test1 implements Runnable{
int sum = 0;
//该run方法并不是Thread类中的,而是Thread实现的Runnable接口中的run方法
@Override
public void run() {
while (true){
sum++;
System.out.println("java 子线程 "+Thread.currentThread().getName());
try {
Thread.sleep(1000); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum==3){
break;
}
}
}
}
应用案例-多线程执行
要求:创建两个线程,一个线程每隔一秒输出 “java” 输出10次退出,另一个线程每隔一秒输出 “scala” 输出5次退出
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test2 test2 = new test2();
Thread thread = new Thread(test1);
Thread thread1 = new Thread(test2);
thread.start();
thread1.start();
}
}
class test1 implements Runnable{
int sum = 0;
@Override
public void run() {
while (true){
sum++;
System.out.println("java "+Thread.currentThread().getName());
try {
Thread.sleep(1000); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum==10){
break;
}
}
}
}
class test2 implements Runnable{
int sum = 0;
@Override
public void run() {
while (true){
sum++;
System.out.println("scala "+Thread.currentThread().getName());
try {
Thread.sleep(1000); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum==5){
break;
}
}
}
}
Thread类和Runnable接口的区别
1.Thread类实际是实现了Runnable接口
2.实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
三、线程的同步
1.问题引入
使用多个线程模拟三个售票窗口,对100张票进行出售
Thread方式
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test1 test2 = new test1();
test1 test3 = new test1();
test1.start();
test2.start();
test3.start();
}
}
class test1 extends Thread{
private static int sum = 100;
@Override
public void run() {
while (true){
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票 剩余票数为"+ --sum);
try {
Thread.sleep(50); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum<=0){
System.out.println("售票结束");
break;
}
}
}
}
Runnable接口
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
new Thread(test1).start();
new Thread(test1).start();
new Thread(test1).start();
}
}
class test1 implements Runnable{
private static int sum = 100;
@Override
public void run() {
while (true){
System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票 剩余票数为"+ --sum);
try {
Thread.sleep(50); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (sum<=0){
System.out.println("售票结束");
break;
}
}
}
}
运行结果如下,可以看出当售票结束以后,其他窗口仍在售票,这里就出现了线程同步的问题
2.线程终止
当线程完成任务后,会自动退出
还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test1.start();
//让主线程休眠5秒
Thread.sleep(5*1000);
test1.setLoop(false);
}
}
class test1 extends Thread{
private int sum =0;
//设置一个控制变量
private boolean loop =true;
@Override
public void run() {
while (loop){
try {
Thread.sleep(50); //睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"..."+ ++sum);
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
四、线程的常用方法
1.第一组常用方法
方法名 | 功能 |
---|---|
setName | 设置线程名称 |
getName | 返回该线程的名称 |
start | 使该线程开始执行 |
run | 调用线程对象 |
setPriority | 更改线程的优先级 |
getPriority | 获取线程的优先级 |
sleep | 在指定的毫秒数内让当前正在执行的线程休眠 |
interrupt | 中断线程(注意:是中断而不是中止,相当于循环结构中continue) |
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test1.start();
test1.setName("张三");
//主线程执行五秒后,执行test1.interrupt();语句
//由于主线程和test1线程同时进行,此时test1线程正在休眠执行test1.interrupt();以后将被唤醒
for (int i = 0; i < 5 ; i++) {
Thread.sleep(1000);
System.out.println("主线程..."+i);
}
test1.interrupt(); //中断test1线程的休眠
}
}
class test1 extends Thread{
@Override
public void run() {
while (true){
//输出0到99
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"..."+i);
}
try {
Thread.sleep(20000); //休眠20秒
} catch (InterruptedException e) {
//当该线程执行到一个interrupt时,就会catch一个异常,可以加入自己的业务代码
System.out.println(Thread.currentThread().getName()+"被interrupt中断休眠了");
}
}
}
}
2.第二组常用方法
方法名 | 功能 |
---|---|
yield | 线程的礼让。让出cpu,让其它线程执行,但礼让的时间不确定,所以也不一定礼让成功 |
join | 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务 |
join案例:子线程和主线程同时运行,当主线程输出五次以后让子线程运行,子线程运行完以后再执行子线程
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test1.start();
for (int i = 0; i < 20; i++) {
Thread.sleep(1000); //休眠1秒
System.out.println("主线程..."+i); //此时子线程和同时运行
if (i==5){
System.out.println("子线程先执行完,然后主线程再执行");
test1.join();
}
}
}
}
class test1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"子线程..."+i);
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.用户线程和守护线程☆
用户线程:也叫工作线程,当线程的任务完或通知方式结束
守护线程:一般为工作线程服务的,当所有的用户线程结束,守护线程自动结束(常见的守护线程:垃圾回收机制)
public class test {
public static void main(String[] args) throws InterruptedException {
test1 test1 = new test1();
test1.setDaemon(true); //将子线程设置为守护线程,当主线程结束后子线程自动结束,如果不设置守护线程则子线程将一直无限循环
test1.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程...");
Thread.sleep(1000);
}
}
}
class test1 extends Thread{
@Override
public void run() {
for (;;) { //无限循环
System.out.println(Thread.currentThread().getName()+"子线程...");
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.注意事项
- start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
- 线程优先级的范围
- interrupt,中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠线程
- sleep线程的静态方法,使当前线程休眠
五、线程的生命周期
其中RUNNABLE:可以分为就绪状态和运行状态。