并行:多个cpu同时执行多个任务,cpu并行
并发:一个cpu同时执行多个任务,进程并发
程序:是一段静态的代码
进程:是程序的一次动态运行过程,对应从代码加载、执行到执行完成的整个过程。进程是系统资源调度的最小单位。进程拥有独立的内存单元(代码段、数据段)。
线程:线程是更小的执行单位,一个进程在运行过程中可以创建多个线程。线程开销较小,是cpu资源分配的最小单位。多个线程共享进程的内存空间,但线程独立拥有栈空间和寄存器组。
1、线程创建方式1-继承Thread类
步骤描述:创建类继承类Thread->重写run方法,定义线程需要进行的操作->创建该类(Thread类的子类)的对象->对象调用start方法(启用当前线程,调用当前线程的run方法执行操作)。代码演示如下:
public class ThreadTest {
public static void main(String[] args) {
//2、线程调用
TThread tThread = new TThread();
tThread.start();
}
}
//继承Thread类的方式创建线程
//1、定义线程类
class TThread extends Thread{
@Override
public void run() {
System.out.println("线程1创建啦!!!");
}
}
java8之后,也可以使用lambda表达式创建线程
new Thread(()->{
System.out.println("线程创建了");
}).start();
}
2、线程创建方式2->实现Runnable接口
步骤描述:创建类实现Runnable接口->该类实现抽象方法run->创建实现类的对象1->将此对象1作为参数传递给Thread类的构造器中创建Thread类的对象2->通过Thread类的该对象2调用start方法。代码演示如下:
public class ThreadTest {
public static void main(String[] args) {
TThhread2 t2 = new TThhread2();//2
Thread thread2 = new Thread(t2);//3
thread2.start();//4
}
}
//实现Runnable接口
//1、定义线程类
class TThhread2 implements Runnable{
@Override
public void run() {
System.out.println("线程3创建成功啦!!!!!!");
}
}
3、线程创建方式3->实现Callable接口
步骤描述:类实现Callable接口->该类实现call方法(可有返回值)->创建实现类的对象1->将此对象作为参数传递到FutureTask的构造器中创建对象2->将对象2作为参数传递到Thread类的构造器中,创建对象3->对象3调用start方法。
class TThread3 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("线程创建");
return null;
}
}
public class ThreadTest {
public static void main(String[] args) {
TThread3 tThread3 = new TThread3();
FutureTask task = new FutureTask<>(tThread3);
new Thread(task).start();
}
}
4、方式4->使用线程池
可创建多个线程放入线程池中,使用线程时直接从线程池中获取,使用完放回线程池,可有效提高响应速度,降低资源消耗,便于线程管理。使用步骤描述:创建线程池->执行指定线程的操作,提供Runnable/Callable对象->关闭连接池,使用示例如下:
//使用线程池创建对象
ExecutorService executor=Executors.newFixedThreadPool(5);//创建固定大小为5的线程池
/*
常用线程池还有
Executors.newCachedThreadPool(); //可缓冲线程池
Executors.newSingleThreadExecutor();//单线程贤线程池
* */
Thread t = new Thread(() -> {
System.out.println("线程1创建");
});
Thread t1 = new Thread(() -> {
System.out.println("线程2创建");
});
//将线程放入池中,并执行线程的操作
executor.execute(t);
executor.execute(t1); //当线程创建方式为callable时,此处使用submit()
//关闭连接池
executor.shutdown();
}
ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService
线程池真正的实现类是 ThreadPoolExecutor。
上图中的构造器只涉及所有必需指定的参数,完整参数的构造器结构如下
下面将介绍涉及的参数及其含义:
int corePoolSize(必需参数):线程池中的核心线程数,默认情况下核心线程一直存活
int maximumPoolSize(必需):线程池能够容纳的最大线程数
long keepAliveTime(必需):存活时间,当核心线程的空闲时间超过该时常时被回收
TimeUnit unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。非空对象,但不是必须参数。
BlockingQueue<Runnable> workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中,其采用阻塞队列实现。非空
ThreadFactory threadFactory(可选参数): 用于指定为线程池创建新线程的方式;非空
RejectedExecutionHandler handler(可选):拒绝策略,当达到最大线程数时需要执行的饱和策略。非空
且各参数需满足如下条件:
corePoolSize >= 0
keepAliveTime >= 0
maximumPoolSize >0
maximumPoolSize >= corePoolSize
workQueue 、 threadFactory 、handler 对象不能为 null
使用示例:
关于参数 workQueue 、 threadFactory 、handler详解见文章:
5、线程常用方法
public synchronized void start() ;//启动当前线程并执行线程run方法定义的操作。
public void run();//定义线程执行的操作,执行完成之后线程进入死亡状态
public final synchronized void setName(String name);//设置当前线程的名字
public final String getName();//返回当前线程名
public static native Thread currentThread();//返回当前使用cpu的线程
public static native void yield();//当前线程释放对cpu的占用
public final void join();//当前线程(调用该方法)抢先占用cpu执行操作,先于正在执行的线程执行(线程联合)
public final void stop();//强制结束当前线程的生命周期,一般情况下不建议采用
public static native void sleep(long millis);//当前线程进入阻塞状态,时间为millis
public final native boolean isAlive();//判断当前线程是否存活
6、线程的生命周期
Thread类的内部枚举类State中定义了线程的几种生命状态,线程状态存储在threadStatus属性中。
线程有新建、运行、阻塞、等待、超时等待和死亡6种状态。转换关系如下:
7、线程同步问题
多个线程对同一段代码进行操作时,需要考虑线程安全问题,当前线程操作时其他线程只能等待。借助以下方式实现线程同步
1、同步代码块
即使用synchronizd关键字,将操作共享数据的代码放在代码块中,实现线程同步控制,但操作代码块时同一时间只允许单个线程进行操作,效率不高。
public class synchronizedTest {
public static void main(String[] args) {
new SynThread("A").start();
new SynThread("B").start();
}
}
//定义线程类
class SynThread extends Thread{
//共享数据
static int count=0;
public SynThread(String name){
super(name);
}
@Override
public void run() {
//共享操作,对变量count进行加一操作
//使用同步代码块进行多线程同步
/*
格式 synchronized (同步锁/同步监视器)-->任何类的对象都可充当锁
{
同步代码块
}
*/
synchronized (SynThread.class){
for (int i = 0; i <5 ; i++) {
System.out.println(currentThread().getName()+"当前count="+count++);
}
}
}
}
运行结果如下:
不加锁时运行结果如下:
2、同步方法
如果操作数据共享数据是一个方法,此方法可声明为同步方法。此时的同步监视器无需显示声明,如果为静态同步方法,则同步监视器为当前类本身;如果为非静态方法,同步监视器为this;锁的对象不同,锁粒度不同,执行性能也不同。
权限修饰符 synchronized 返回值类型 方法名(参数列表){
方法体
}
3、Lock锁
lock锁为java5之后的新特性,在使用时需要显式加锁和解锁,使用示例如下:
class TThread2 implements Runnable{
int count=0;
//实例化ReentrantLock
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
//加锁lock()
lock.lock();
for (int i = 0; i <5 ; i++) {
System.out.println(currentThread().getName()+"当前count="+count++);
}
//释放锁unlock()
lock.unlock();
}
}
public class synchronizedTest {
public static void main(String[] args) {
TThread2 tThread2 = new TThread2();
new Thread(tThread2,"C").start();
new Thread(tThread2,"D").start();
}
}
运行结果如下图
4、synchronized和lock的区别
- 使用方式不同
synchronized可作用在静态方法、示例方法和代码块上,是一种隐式锁,无需显式获取和释放;Lock是一种显式锁,需要调用其内部方法显式地获取和释放锁,提供了更好的灵活性,在这种同步方式下可通过condition实现线程通信。
- 功能特性不同
synchronized是早期api,java5之后引入locc在设计上弥补synchronized的不足,这些新特性包括:lock可中断、非阻塞、可超时地获取锁。未获得锁时返回false,反之返回true。
- 实现机制不同
synchronized底层采用java对象头存储信息,并支持锁升级;
lock实现类基于AQS(是一个双向链表)实现的,AQS(Abstract QueueSynchronizer)队列同步器,用来构造锁的框架,AQS内部定义了一个FIFO队列实现线程同步,还定义同步状态存储锁的信息。
- 通信api
synchronized: wait(),notify()/notifyAll();(同步监视器调用)
lock: await(),signal()/signalAll():(lock.newCondition()对象调用)