1.进程可以理解成线程的容器,一个进程执行必须至少要一条线程。
2.进程可以理解为一个程序的执行
1.CPU分配资源是以线程为单位
2.
1.进程间不能相互通信,但是同一个进程的多个线程之间可以共享数据。
1.并发:单核CPU同一时间只能处理一条线程,但是速度非常快,造成并行的假象(例子:开车打电话)
2.并行:多核CPU,每个核心单独执行一个线程。
1.多线程可以提高CPU的利用率
注意:单核CPU不能提高,反而降低
2.可以实现异步调用
线程在Java中也是一种对象
1.继承Thread,重写Run方法
创建线程对象
调用线程对象的Start方法将当前线程加入到CPU的执行任务队列
注意:不要调用线程对象的RUN方法,这个方法是给CPU来调用的,我们只负责把当前线程加入到CPU的执行队列中。
*问题:run与Start:总结起来就是run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码
package MyThread;
public class MyThread01 extends Thread{
//第一种创建线程的方法:new Thread 继承Thread
@Override
public void run() {
System.out.println("Thread hello");
}
}
class MyThread02{
public static void main(String[] args) {
MyThread01 m1 = new MyThread01();
m1.start();
}
}
2.Runnable接口
第二种方式:
1.线程类实现Runnable接口
2.重写Run方法
3.new线程类对象
4.new Thread对象,把线程类对象加载进去,再调用Start
package MyThread;
public class MyRunnable01 implements Runnable{
@Override
public void run() {
System.out.println("Hello,Runnable");
}
}
class MyRunnable02{
public static void main(String[] args) {
MyRunnable01 m01 = new MyRunnable01();
Thread thread = new Thread(m01);
thread.start();
}
}
对比:
1.Thread和Runnable 推荐第二种 因为Runnable没有继承父类
2.Thread将线程对象和线程所有执行的任务耦合在一起了,不符合面向对象的规范
约等于使用构造器解耦
还可以使用匿名内部类的方式
//匿名内部类
class MyRunnable03{
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("你好,Runnable");
}
}).start();
}
}
第三种方式:Callable接口:jdk5新增的
1.创建线程类,实现Callable接口,重写Call方法,设置泛型返回值
2.实现逻辑代码,并返回
3.使用FutureTake 把Callable转成Runnable
4.将参数传给Thread
注意:为什么用FutureTake 把Callable转成Runnable就可以了?
Thread可以接收Runnable是因为构造方法重载了一个有Runnable接口的参数
package MyThread;
import java.lang.reflect.Member;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable01 implements Callable {
@Override
public Object call() throws Exception {
int sum = 7;
System.out.println("Hello Callable");
return sum;
}
}
class MyCallable02{
public static void main(String[] args) throws Exception {
MyCallable01 c1 = new MyCallable01();
FutureTask ft = new FutureTask(c1);
Thread t1 = new Thread(ft);
t1.start();
System.out.println(ft.get());
}
}
currentThread():这是线程类的一个静态方法,获取执行当前任务的线程对象
getName():获取线程的名称,每个线程都有自己的名字【唯一标识】
系统默认的线程名称规则:主线程就叫main 其他线程:thread-index
setName(name):设置线程对象的名称 也可以通过构造器初始化name的值
getPriority():获取线程对象的执行优先级
setPriority():设置线程对象的执行优先级 默认为5
sleep(毫秒数):也是一个线程类的静态方法,让调用这个方法的当前线程休眠
join():线程合并 整个JAVA程序只要还有线程没结束,程序就不会结束
stop():强制结束线程【过于暴力 不安全】
interrupt():打断指定的线程
isInterrupted():获取线程是否被打断的标记
(1)sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法。
(2)sleep() 方法不会释放 lock,但是 wait() 方法会释放,而且会加入到等待队列中。
(3)sleep() 方法不依赖于同步器 synchronized(),但是 wait() 方法 需要依赖 synchronized 关键字。
(4)线程调用 sleep() 之后不需要被唤醒(休眠时开始阻塞,线程的监控状态依然保持着,当指定的休眠时间到了就会自动恢复运行状态),但是 wait() 方法需要被重新唤醒(不指定时间需要被别人中断)。
通过方法Thread.setDaemon();
package MyThread;
public class MyDaemon extends Thread{
@Override
public void run() {
for (int i = 0;i<10;i++) {
System.out.println("我是主线程");
}
}
}
class Mydaemon02 implements Runnable{
@Override
public void run() {
while (true){
System.out.println("我是守护线程");
}
}
}
class MyDaemon01{
public static void main(String[] args) {
new MyDaemon().start();
Mydaemon02 mydaemon02 = new Mydaemon02();
Thread thread = new Thread(mydaemon02);
thread.setDaemon(true);
thread.start();
}}
1.新建状态(NEW)
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配 内存,并初始化其成员变量的值 。
2.就绪状态(RUNNABLE)
当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和 程序计数器,等待调度运行。
3.运行状态(RUNNING)
如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
4.阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。 直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状 态。阻塞的情况分三种:
(1)等待阻塞 ( o.wait-> 等待对列 )
运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue) 中。
(2)同步阻塞 (lock-> 锁池 )
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线 程放入锁池(lock pool)中。
(3)其他阻塞 (sleep/join)
运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时, JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O 处理完毕时,线程重新转入可运行(runnable)状态。
5.线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
正常结束: run()或call()方法执行完成,线程正常结束。
异常结束: 线程抛出一个未捕获的Exception或Error。
调用 stop: 直接调用该线程的stop()方法来结束该线程— 该方法通常容易导致死锁,不推荐使用。
线程安全发生的条件:
1.线程并发
2.多线程之间要有数据共享【每个线程独享栈,共享堆】
3.要有对共享数据的读写操作
4.发生读写的指令交错【可能发生也可能不发生】
*解决线程安全:
1.Synchronize
2.Lock
使用方法:
线程创建栈
为什么需要线程通信:
线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。
notify()
方法或 notifyAll()
方法这三个方法不属于线程,所有对象都可以调用
Java1.5后的接口
线程池的使用:
顶级接口:execute里面之定义了一个方法,开发中不会直接使用
我们一般使用他的一个子接口:ExecutorService
重要方法:submit():向线程池提交任务对象
shutdown():关闭线程池
shutdownNow():立刻马上关闭
isTerminated():判断线程池是否关闭
实现类:ThreaddPoolExecutor
创建线程池核心方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
线程池的工作流程:
1.启动核心线程
2.如果任务太多并且核心线程全部占满(先压任务,如果还是解决不了再创建救急线程)
3.如果队列满了,创建救济线程
4.如果救济线程达到最大数量则执行拒绝策略