1.进程与线程
进程:指OS中一个程序的执行周期
线程:一个程序同时执行多个任务,其中每个任务都是一个线程
二者区别:每个进程拥有自己的一整套变量,线程则共享数据。共享变量是的线程之间通信比进程间的更有效、方便
2.线程的五种状态:
创建–>就绪–>阻塞–>运行–>终止
3.Java多线程
3.1通过继承Thread类覆写run()方法来实现多线程
启动多线程:public synchronized void start() ,用此方法来自动调用线程的run()方法
线程的核心类包: java.lang.Thread
public class MyThread extends Thread{ //线程主体类
private String line;
public MyThread (String line){
this.line = line;
}
@Override
public void run(){ //覆写run()方法,线程从此开始执行
for(int i = 0; i<10; i++){
System.out.println(this.line+",i="+i);
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread("thread1") ;
MyThread myThread2 = new MyThread("thread2") ;
MyThread myThread3 = new MyThread("thread3") ;
//顺序执行,非多线程
myThread1.run();
myThread2.run();
myThread3.run();
//通过启动多线程:public synchronized void start()此方法来自动调用线程的run()方法
myThread1.start();
myThread2.start();
myThread3.start();//此时三个线程是同时、交替执行的
}
}
3.2通过实现Runnable()接口
Thread类的构造方法:public Thread(Runnable target) ,用来接收Runnable接口对象
//Runnable()接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
//利用Runnable接口实现多线程
class MyThread implements Runnable{
private String line;
public MyThread(String line){
this.line = line;
}
@Override
public void run(){
for(int i = 0; i<10 ;i++){
System.out.println(this.line+",i="+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread myThread1= new MyThread("Thread1");
MyThread myThread2= new MyThread("Thread2");
MyThread myThread3= new MyThread("Thread3");
new Thread(myThread1).start();
new Thread(myThread2).start();
new Thread(myThread3).start();
}
}
//利用Runnable接口可实现共享,实现一个简单的卖票流程
class MyThread1 implements Runnable{
private int tickets = 5;
public void setTickets(int tickets){
this.tickets = tickets;
}
@Override
public void run(){
while(tickets>0){
System.out.println(Thread.currentThread().getName()+"剩余票数:"+tickets--);
}
}
}
public class SellTickets {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread1 myThread2 = new MyThread1();
MyThread1 myThread3 = new MyThread1();
new Thread(myThread1).start();
new Thread(myThread2).start();
new Thread(myThread3).start();
}
}
Runnable接口实现多线程效果图:
卖票窗口效果图:
- 通常采用匿名内部类或Lambda表达式来创建Runnable对象
//使用匿名内部类创建Runnable对象
public class Thread1 {
public static void main(String[] args) {
//Runnable runnable = new Runnable();
//Thread thread = new Thread(runnable,"子线程1");
//thread.start();----->new Thread (thread).start();
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("HelloWorld");
}
}).start(); //注意!!!
}
}
//使用Lamdba表达式创建Runnable对象
public class Thread1 {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("HelloWorld");
new Thread(runnable).start();
}
}
3.3Thread类和Runnable区别
Thread:类,单继承,是Runnable接口的子类,覆写了Runnable接口的run()方法
Runnable:接口,可被多个类调用
4.Callable实现多线程
Runnable中的run()方法无返回值,用Callnable来实现带有返回值的多线程
Callable接口存在于java.util.concurrent包中
//Callable接口
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
//使用Callable来定义卖票窗口
class MyThread2 implements Callable<String> {
private int tickets = 10;
public void setTickets(int tickets){
this.tickets = tickets;
}
@Override
public String call() throws Exception {
while(this.tickets>0){
System.out.println(Thread.currentThread().getName()+"剩余票数:"+this.tickets--);
}
return "票卖完啦!欢迎下次光临";
}
}
Callable实现卖票窗口效果图:
- 无论何种情况,启动线程只能调用Thread类中的start()方法。观察下面Callable继承树:
注意Thread和FutureTask两大板块!
//启动并获得多线程的执行结果
public class SellTickets {
public static void main(String[] args)throws ExecutionException, InterruptedException {
//1.实例化多线程对象
MyThread2 myThread1 = new MyThread2();
MyThread2 myThread2 = new MyThread2();
MyThread2 myThread3 = new MyThread2();
//注意!!2.通过FutureTask类来包装Callable对象
FutureTask<String> task1 = new FutureTask<>(new MyThread2());
FutureTask<String> task2 = new FutureTask<>(new MyThread2());
FutureTask<String> task3 = new FutureTask<>(new MyThread2());
//3.启动线程
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
//4.获取多线程的执行结果
System.out.println(task1.get());
/*在调用task.get()方法时抛出异常ExecutionException, InterruptedException*/
}
}
注意:
- Future代表一个异步的计算,可以从中得到计算结果,查看计算状态,它实现FutureTask可以被提交给Executor执行,多个线程能从中得到计算结果
- Callable和Future要配合使用,Future给出的get结果有两种状态:当结果还未被计算出来,线程被挂起,FutureTak内部使用一个单链表维持等待的线程;当结果已被计算出来,解除线程挂起,得到计算结果
- Future接口:代表一个异步的计算结果,接口中的方法用来检查计算是否完成、等待完成以及得到计算结果。其中get()方法得到结果
- FutureTask类:提供异步计算的结果的任务。它实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口。所以FutureTask可以用来包装Callable或者Runnbale对象
4.多线程的常用操作方法
1.线程的命名和获取
//命名线程
MyThread myThread = new MyThread();
new Thread(myThread,"子线程A").start();
//获取线程名
Thread.currentThread().getName();
主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的
2.线程休眠sleep()
线程休眠:指是让线程暂缓执行一下,等到了预计时间之后再恢复执行
线程休眠时会交出CPU,让CPU去执行其他任务,但是sleep不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
调用sleep()会抛出异常:InterruptedException
线程休眠时间以毫秒作为单位
//处理线程休眠操作
class MyThread3 implements Runnable{
private boolean flag = true;
public void setFlag(boolean flag){
this.flag = flag;
}
@Override
public void run(){
int i = 0;
while(this.flag){
System.out.println("线程"+Thread.currentThread().getName()+"当前执行次数i:"+ ++i +"次");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SleepThread {
public static void main(String[] args) {
MyThread3 mt1 = new MyThread3();
MyThread3 mt2 = new MyThread3();
MyThread3 mt3 = new MyThread3();
new Thread(mt1,"子线程A").start();
new Thread(mt2,"子线程B").start();
new Thread(mt3,"子线程C").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
注意!此处三个线程并非同时休眠,所有代码一次进入run()方法中,真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行。
*/
效果图:
3.线程让步yield()
线程让步是指暂停当前正在执行的线程对象,并执行其他线程
yield()与sleep()相似,但yield不能控制交出CPU的具体时间且只能让拥有相同优先级的线程有获取CPU执行时间的机会
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间
//yield()下的线程休眠:休眠时间不确定,同一优先级获取CPU执行机会
class MyThread4 implements Runnable{
@Override
public void run(){
for(int i = 0 ;i<5; i++){
Thread.yield();
System.out.println(Thread.currentThread().getName()+"循环执行第"+ ++i +"次");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class YieldThread {
public static void main(String[] args) {
MyThread4 mt1 = new MyThread4();
MyThread4 mt2 = new MyThread4();
MyThread4 mt3 = new MyThread4();
new Thread(mt1,"子线程A").start();
new Thread(mt2,"子线程B").start();
new Thread(mt3,"子线程C").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
效果图:
4.join()方法
join():等待该线程终止。如果在主线程中调用就会让主线程休眠,让调用该方法的线程run方法先执行后再开始执行主线程。
//join()在线程中的使用方法--等待该线程停止
class MyThread5 implements Runnable{
@Override
public void run(){
try {
System.out.println("主线程睡眠前的时间:");
JoinThread.printTime();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
System.out.println("睡眠结束的时间:");
JoinThread.printTime();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class JoinThread {
public static void main(String[] args) throws InterruptedException {
MyThread5 mt1 = new MyThread5();
Thread thread = new Thread(mt1,"子线程A");
thread.start();
System.out.println(Thread.currentThread().getName());
thread.join();
System.out.println("代码结束");
}
public static void printTime(){
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(date);
System.out.println(time);
}
}
效果图:
5.线程停止
多线程中有三种方式可以停止线程:
- 设置标记位,可以使线程正常退出
- 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了
- 使用Thread类中的一个 interrupt() 可以中断线程
/**
*1.设置标记为使线程退出
**/
class MyThread6 implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
try {
Thread.sleep(2000);
System.out.println("第" + i + "次执行,线程名称:" + Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class StopThread {
public static void main(String[] args) throws InterruptedException {
MyThread6 mt1 = new MyThread6();
Thread thread = new Thread(mt1,"子线程A");
thread.start();
Thread.sleep(1000);
mt1.setFlag(false);
System.out.println("线程结束");
}
}
效果图:
/**
*2.调用stop()方法使线程退出
**/
MyThread6 mt1 = new MyThread6();
Thread thread = new Thread(mt1,"子线程A");
thread.start();
Thread.sleep(1000);
thread.stop();
System.out.println("线程结束");
/*为什么stop()不安全?
因为stop()方法会解除由线程获取的所有锁定,当调用此方法时,线程对象会立即停止正在执行的方法,若此方法为某种同步方法,如synchronized void { x = 3; y = 4;} ,线程会由于突然停止而产生不完整的残废数据
*/
/**
*3.使用Thread.interrupt()使线程退出
**/
class MyThread7 implements Runnable{
private boolean flag = true;
@Override
public void run(){
int i = 1;
while(flag){
try {
Thread.sleep(1000);
boolean bool = Thread.currentThread().isInterrupted();//判断当前线程中断情况
if(bool){
System.out.println("线程处于非阻塞状态"+bool);//非阻塞状态
break;
}
System.out.println("第"+i+"次执行,线程名称:"+Thread.currentThread().getName());
/*阻塞状态,线程被调用了interrupt()方法,清除中断标志后抛出异常java.lang.InterruptedException*/
i++;
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("退出阻塞");//退出阻塞状态,且中断标志被系统自动清除,并将bool重新设置为false
boolean bool = Thread.currentThread().isInterrupted();
System.out.println("阻塞中断:" + bool);
return;//退出run()方法,中断进程
}
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}
public class InterruptThread {
public static void main(String[] args) throws InterruptedException {
MyThread7 mt1 = new MyThread7();
Thread thread = new Thread(mt1,"子线程A");
thread.start();
Thread.sleep(3000);
thread.interrupt();
System.out.println("线程结束");
}
}
效果图:
- interrupte()方法并不会立即执行中断操作,而是给线程设置一个为true的中断标志(boolean型),根据线程状态进行不同操作
- 如果线程状态为非阻塞,则仅仅是将线程的中断标志设为true
- 阻塞状态:将中断标志设为true,如果是wait、sleep以及join三个方法引起的阻塞,则将线程的中断标志重新设置为false,并抛出InterruptedException;
完整代码移步:https://github.com/Loinbo