线程
线程路程的关系
“线程和进程的关系:每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。进程是资源分配的最小单位,线程是程序执行的最小单位。”
概述
状态
新建(NEW):新创建了一个线程对象。
可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生
实现方式
在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口 。
线程启动的方式是调用start方法,start方法会调用run方法。
Runnable
Runnable是一个接口,里面就只有run方法。
public interface Runnable { public abstract void run(); }
使用案例
public class MyRunnable implements Runnable{ @Override public void run() { try { System.out.println("重写了runable"); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { Runnable myRunnable0 = new MyRunnable(); Thread thread0 = new Thread(myRunnable0); Thread thread1 = new Thread(myRunnable0); //Runnable myRunnable1 = new MyRunnable(); //Thread thread1 = new Thread(myRunnable1); thread0.start(); //thread1.start(); } }
Thread
概念
hread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的
run()方法就可以实现多线程操作了,但是一个类只能继承一个父类。
案例
/** * @author xing * 2021年9月27日下午12:11:11 * 继承Thread的用法 */ public class MyThread extends Thread { String str; public void run() { this.show(); } public void show() { while(true) { try { Thread.sleep(1000);//当前线程睡一秒 System.out.println(str+" :-----------MyThread-----------"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyThread mt0 = new MyThread(); MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); mt0.str = "mt0"; mt0.start(); mt1.str = "mt1"; mt1.start(); mt2.str = "mt2"; mt2.start(); } }
注:每new一个thread就是开辟了一个新的线程,start就是启动了线程。main所运行的就是主线程,主线程可以开启子线程,各个线程干自己的事情。
Callable
当我们执行线程需要返回值的时候那么就必须选用实现Callable类的方式,因为目前只有这种方式能返回值。当然这种方式我们也可以不需要获取返回值。
这种方式是通过FutureTask的get()方法(下面代码的第22行)或者get(long timeout, TimeUnit unit)(下面代码的第28行)方法获取返回值。当我们看Callable的接口定义的源码会发现“public interface Callable<V> ” ,我们实现的时候是需要定义返回类型,如下面代码所示。
除此之外我们还需要注意的是:当我们通过FutureTask的get()方法去获取线程的返回值的时候是要等到线程call()内容都执行完毕之后才能获取得到,并且get()方法后面的代码必须等待,说明这一定是同步的,所以我们可以在真正需要线程返回值的时候才通过get()方法去获取,以免被阻塞。当我们通过get(long timeout, TimeUnit unit)方式去获取的时候可以设置超时时间,如果超过所设置的超时时间都没有获取到线程返回的值则会抛出 java.util.concurrent.TimeoutException 异常,当然如果在get(long timeout, TimeUnit unit)之前用get()方式获取了的话就不会抛异常。
实现Callable还有个好处就是可以线程可以抛异常,如果我们需要在线程里抛出异常的话也可以选用这种方式,其他两种方式只能捕获异常信息。
public class ThreadCallableDemo implements Callable<Integer>{ /** 计数变量 */ private int count = 0; public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { ThreadCallableDemo threadCallableDemo = new ThreadCallableDemo(); //通过FutureTask获取返回值 FutureTask<Integer> taskA = new FutureTask<>(threadCallableDemo); //实例化线程 Thread thread = new Thread(taskA, "threadCallableDemoA"); System.out.println(String.format("线程状态preStart: %s", thread.getState())); //启动线程 thread.start(); System.out.println(String.format("线程状态afterStart: %s", thread.getState())); //通过FutureTask的get()方法获取返回值 int result = taskA.get(); System.out.println("是否同步测试...."); System.out.println(String.format("result: %s", result)); System.out.println(String.format("线程状态afterGetResult1: %s", thread.getState())); //通过FutureTask的get()方法获取返回值 设置超时时间 单位为ms int resultWithTime = taskA.get(100, TimeUnit.MILLISECONDS); System.out.println(String.format("resultWithTime: %s", resultWithTime)); System.out.println(String.format("线程状态afterGetResult2: %s", thread.getState())); } /** * 实现Callable的call类 */ @Override public Integer call() throws Exception { //自增 count++; System.out.println(String.format("线程名称:%s, 线程状态:%s, count:%s", Thread.currentThread().getName(), Thread.currentThread().getState(), count)); System.out.println("休眠1000ms...."); Thread.currentThread().sleep(1000); return count; } }
synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
-
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
-
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
synchronized修饰方法案例:
窗口售票实例:
源码
package Dome1; public class TicketDome extends Thread{ private static int sum=100; private static Object obj=new Object(); @Override public void run() { while (true){//同步代码块 sum.sleep(1000); synchronized (obj){ if (sum>0) { System.out.println(Thread.currentThread().getName()+"售出的票号是"+sum); sum--; }else { System.exit(0); } } } } public synchronized void mp(){ } public static void main(String[] args) { TicketDome ticketDome1 = new TicketDome(); ticketDome1.start(); TicketDome ticketDome2 = new TicketDome(); ticketDome2.start(); TicketDome ticketDome3 = new TicketDome(); ticketDome3.start(); } }
结果
不加入重度锁
源码:
package Dome1;
public class TicketDome extends Thread{
private static int sum=100;
private static Object obj=new Object();
@Override
public void run() {
while (true){//同步代码块
sum.sleep(1000);
if (sum>0) {
System.out.println(Thread.currentThread().getName()+"售出的票号是"+sum);
sum--;
}else {
System.exit(0);
}
}
}
public synchronized void mp(){
}
public static void main(String[] args) {
TicketDome ticketDome1 = new TicketDome();
ticketDome1.start();
TicketDome ticketDome2 = new TicketDome();
ticketDome2.start();
TicketDome ticketDome3 = new TicketDome();
ticketDome3.start();
}
}