1.什么是进程,什么是线程?
进程是一个应用程序,线程是进程的一次执行过程/执行单元。一个进程可以启动多个线程。
对java程序来说,在DOS窗口输入java helloworld回车之后,会先启动JVM,而JVM就是一个进程。JVM启动一个主线程执行main方法,在启动一个垃圾回收线程负责看护。现在的java程序中至少有两个线程并发。
注意:
1.进程A和进程B是独立的,资源不共享。淘宝是一个进程,qq音乐是一个进程。
2.线程A和线程B的堆内存和方法区内存共享;栈内存独立,一个线程一个栈。
java中之所以有多线程机制,目的就是为了提高程序的执行效率。经典案例:买票。
**思考:**使用多线程机制后,main方法结束,有可能程序不会结束。main方法结束只是主线程结束,主栈空了,其他栈(线程)可能还在压栈弹栈。
2.实现多线程的第一种方式
继承java.lang.Thread,重写run()方法。
public class ThreadTest01 {
public static void main(String[] args) {
//main方法,属于主线程
//新建一个分支线程
MyThread myThread = new MyThread();
//start方法启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码完成任务后,瞬间就结束了。
//启动成功的分支线程会自动调用run方法,run方法在栈底部(压栈),main方法也在栈底部。
//run方法在分支线程中的地位等同于main方法在主线程中
myThread.start();
//注意,如果直接调用run()方法,则不会开辟新的栈空间,运行时依旧是单线程模式
// myThread.run();
//主线程中运行
for (int i=0;i<1000;i++){
System.out.println("------>>>>主线程"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//这段代码运行在分支线程中
for (int i=0;i<1000;i++){
System.out.println("------>>>>分支线程"+i);
}
}
}
**注意:**直接调用run()方法以及是单线程,需要调用start()方法来开辟新的栈空间才能实现多线程。
3.实现多线程的第二种方式
实现java.lang.Runnable接口,重写run()方法。注意实现Runnable接口的类并不是一个线程,只是一个可运行的类,需要将这个类作为参数放进Thread中,启动线程。
public class ThreadTest02 {
public static void main(String[] args) {
//创建一个可运行的对象
MyRunnable myRunnable = new MyRunnable();
//将可运行的对象封装成一个线程对象
Thread thread=new Thread(myRunnable);
//启动线程
thread.start();
for (int i=0;i<1000;i++){
System.out.println("------>>>>主线程"+i);
}
}
}
//这并不是一个线程类,只是一个可运行的类,还不是一个线程 需要将这个类作为参数放进Thread中
class MyRunnable implements Runnable{
@Override
public void run() {
//这段代码运行在分支线程中
for (int i=0;i<1000;i++){
System.out.println("------>>>>分支线程"+i);
}
}
}
匿名内部类实现:
public class ThreadTest03 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("------>>>>分支线程"+i);
}
}
});
//启动分支线程
t.start();
//主线程
for (int i=0;i<1000;i++){
System.out.println("------>>>>主线程"+i);
}
}
}
4.实现多线程的第三种方式
实现java.util.concurrent包(JUC并发包)中的Callable接口。call()方法发与run()方法类似,但是call()有返回值。
public class MyThread {
public static void main(String[] args) throws Exception{
//第一步:创建一个“未来任务类”对象
//参数非常重要,需要给一个Callable接口的实现类对象
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call方法相当于run方法,只不过有线程的返回结果
System.out.println("call method begin");
Thread.sleep(1000*5);
System.out.println("call method end");
int a=100;
int b=200;
return a+b;
}
});
//创建线程对象
Thread t1=new Thread(task);
//启动线程
t1.start();
//在主线程中获取t1线程返回的值
Object o = task.get();
System.out.println(o);
//main方法这里的代码想要执行必须等待task.get()执行完毕。引起主线程阻塞
//因为get()方法拿的是t1线程的执行结果,而t1线程的执行是需要时间的
System.out.println("hello world");
}
}
5.线程的生命周期
Java线程具有五中基本状态
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
6.获取线程对象
获取线程对象,写在哪个线程里面获取的就是该线程的内存地址。
Thread thread = Thread.currentThread();
获取线程对象的名字:
String name = 线程对象.getName();
修改线程对象的名字:
线程对象.setName("xxx");
完整代码:
public class ThreadTest04 {
public static void main(String[] args) {
//获取当前线程对象 返回的是当前线程的内存地址 写在main方法中代表返回的是主线程的地址
Thread thread = Thread.currentThread();
//创建线程对象
MyThread2 thread2=new MyThread2();
//设置线程的名字
thread2.setName("Thread2");
//获取线程的名字
String name = thread2.getName();
System.out.println(name);
thread2.start();
}
}
class MyThread2 extends Thread{
@Override
public void run() {
//这段代码运行在分支线程中
for (int i=0;i<1000;i++){
System.out.println("------>>>>分支线程"+i);
}
}
}
7.线程的sleep方法
static void sleep(long millis)
1.静态方法
2.参数是毫秒
3.作用:让当前线程休眠,进入“阻塞状态”,放弃占有的CPU时间片,让其他线程使用。
public class ThreadTest05 {
public static void main(String[] args) {
for (int i=0;i<10;i++){
System.out.println("----每隔一秒输出一个数字----"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
面试题:以下代码是主线程进入休眠还是分支线程进入休眠
public class ThreadTest06 {
public static void main(String[] args) {
//创建线程对象
Thread t=new MyThread3();
t.setName("t");
t.start();
//调用sleep方法
try {
//问题:这行代码会让线程t进入休眠状态吗?
t.sleep(1000*5);//静态方法,在执行时会被转化成Thread.sleep(1000*5); 让当前线程(主线程)进入休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//五秒之后这里才会执行
System.out.println("hello world");
}
}
class MyThread3 extends Thread{
@Override
public void run() {
System.out.println("t线程");
}
}
8.中断线程睡眠
并不是中断线程,而是中断睡眠!!
public class ThreadTest07 {
public static void main(String[] args) {
Thread thread = new Thread(new myRunnable1());
thread.start();
//希望5s后 t线程醒来(5秒之后主线程活就干完了)
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程t的睡眠
thread.interrupt();//干扰 会让正在睡眠的线程出异常,直接进入catch语句块 采用的异常处理机制中断睡眠
}
}
class myRunnable1 implements Runnable{
//重点:在run里面调用sleep会有异常,该异常不能throws,只能try-catch捕捉
//因为在父类中run方法没有抛异常,子类不能比父类抛出更宽泛的异常
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"begin");
try {
Thread.sleep(1000*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"end");
}
}
9.中断线程的两种方法
1.thread.stop()直接中断线程(过时,不建议使用)
不建议使用的原因:没有保存的数据容易丢失
2.布尔标记做if判断,决定是否要结束run方法(常用,推荐)
public class ThreadTest07 {
public static void main(String[] args) {
myRunnable1 myRunnable1 = new myRunnable1();
Thread thread = new Thread(myRunnable1);
thread.start();
//希望5s后 t线程醒来(5秒之后主线程活就干完了)
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程t的睡眠
myRunnable1.run=false;
}
}
class myRunnable1 implements Runnable{
//标记
boolean run=true;
@Override
public void run() {
for (int i=0;i<10;i++){
if (run){
System.out.println(Thread.currentThread().getName()+"---->"+i);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//中断线程 结束run方法 return之前做好数据保存之类的工作
return;
}
}
}
}
10.线程调度
1.常见的线程调度模型
抢占式调度模型:根据线程的优先级(java采用),优先级越高抢占的时间片越多。
均分式调度模型:平均分配CPU时间片
2.java提供了哪些方法与线程调度有关?
实例方法:设置优先级
thread.setPriority(Thread.MAX_PRIORITY);//10
thread.setPriority(Thread.MIN_PRIORITY);//1
thread.setPriority(Thread.NORM_PRIORITY);//5
实例方法:获取优先级
thread.getPriority();
静态方法:暂停当前正在执行的线程对象,并执行其他线程,让当前线程从“运行状态”—>“就绪状态”,而sleep()方法是“运行状态”—>“阻塞状态”
Thread.yield();
实例方法:合并线程,当前线程从“运行状态”—>“阻塞状态”
thread.join();//当前线程进入阻塞,thread执行完毕后,当前线程才可以继续执行
public class ThreadTest07 {
public static void main(String[] args) {
myRunnable1 myRunnable1 = new myRunnable1();
Thread thread = new Thread(myRunnable1);
thread.start();
try {
thread.join();//当前线程进入阻塞,thread执行完毕后,当前线程才可以继续执行
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-main--->"+i);
}
}
}
class myRunnable1 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"-thread--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
11.Object类中的wait()和notify()【生产者消费者模式】
1.wait和notify不是线程对象的方法,任何一个java对象都拥有这两个方法,不能通过线程对象来调用。
2.wait()方法的作用?
Object o=new Object();
o.wait();//让正在o对象上活动的线程进入无限等待状态,直到被唤醒
3.notify()方法的作用?
Object o=new Object();
o.notify();//将正在o对象上等待的线程唤醒
12.生产者消费者模式代码实现
1.使用wait()和notify()实现生产者和消费者模式。
2.什么是“生产者和消费者模式”
生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡,这是一种特殊的业务需求,在这种特殊的情况下要使用wait()和notify()方法。
3.wait()和notify()方法不是线程对象的方法,是普通java对象都有的方法。
4.wait()和notify()方法建立在线程同步的基础上,因为多线程要同时操作一个仓库
生产者线程:
class Producer extends Thread{
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
//死循环,一直生产
while (true){
//给仓库对象List加锁
synchronized (list){
//说明仓库有值 生产一个消费一个
if (list.size()>0){
try {
//当前线程进入等待状态,并释放之前占有的对象锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序能够进行到这里说明仓库是空的,所以就可以生产
Object o=new Object();
list.add(o);
System.out.println(Thread.currentThread().getName()+"--->"+o);
//唤醒消费者进行消费
list.notify();
}
}
}
}
消费者线程:
class Custom extends Thread{
private List list;
public Custom(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
while (true){
synchronized (list){
//仓库已空,消费者等待
if (list.size()==0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//执行到这里 说明仓库有值,可以消费
Object remove = list.remove(0);
System.out.println(Thread.currentThread().getName()+"---->"+remove);
//唤醒生产者进行生产
list.notify();
}
}
}
}
测试:
public class Test {
public static void main(String[] args) {
List list=new ArrayList();
//创建两个线程对象
//生产者线程
Thread producer=new Producer(list);
//消费者线程
Thread custom=new Custom(list);
producer.setName("生产者线程");
custom.setName("消费者线程");
producer.start();
custom.start();
}
}
13.使用ExecutorService、Callable、Future实现由返回结果的多线程
可返回值的任务必须实现Callable接口,无返回值的任务必须实现Runnanle接口。执行Callable任务后,可以获取一个Future对象,在该对象调用get就可以获取到Callable任务返回的Object了。再结合线程池接口ExecutorService就可以实现传说中的有返回结果的多线程了。
public class MyCallable implements Callable<Object> {
private String taskSum;
MyCallable(String taskSum){
this.taskSum=taskSum;
}
@Override
public Object call() throws Exception {
System.out.println("==========taskSum任务启动===============");
Date date1 = new Date();
Thread.sleep(10000);
Date date2 = new Date();
long time = date2.getTime() - date1.getTime();
System.out.println("==========taskSum任务结束===============");
return taskSum+"任务返回执行结果,任务花费时间:"+time;
}
}
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("================程序开始运行===============");
Date date1 = new Date();
// 线程池的线程数量
int taskSize = 5;
// 创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建有多个返回值的任务
ArrayList<Future> futures = new ArrayList<>();
for (int i=0;i<taskSize;i++){
// 创建Callale对象
Callable myCallable = new MyCallable(i + " ");
//执行任务并获取Futrue对象
Future submit = pool.submit(myCallable);
futures.add(submit);
}
//关闭线程池
pool.shutdown();
// 获取所有并发任务的执行结果
for (Future future : futures) {
// 从Future对象上获取任务的返回值,并打印
System.out.println("=====返回值=====");
System.out.println(future.get().toString());
}
Date date2 = new Date();
System.out.println("程序运行结束,耗时:"+(date2.getTime()-date1.getTime()));
}
}