进程和线程
进程:一个内存中运行的应用程序,每个进程都有一个独立内存。
线程:进程的一个执行路径,共享一个内存空间,线程之间可以自由切换,互不影响,并发执行,一个进程至少有一个线程。
线程的调度
分时调度:
所有线程轮流使用cpu,平均分配每个线程占用cpu的时间
抢占式调度:
优先让优先级高的西安城使用cpu,如果线程优先级相同,则随机选择一个线程使用cpu,Java里用的是抢占式调度。
多线程的优势:
并不能提高程序运行速度,但是能提高程序运行效率,使cpu使用率更高。
同步和异步
同步:排队执行,效率低但是安全(Vector,Hashtable)
异步:同时执行,效率高但是数据不安全(ArrayList,HashMap)
并发和并行
并发:指两个或多个事件在同一时间段内发生
并行:指两个或多个事件在同一时刻发生(同时发生)
用户线程和守护线程
用户线程(非守护线程):当一个进程不再包含任何的存活的用户线程时,进程结束
守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程死亡(main线程不能设置为守护线程)
多线程的实现
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口(带有返回值)
Thread类
使用Runnable接口和继承thread实现多线程都要用到Thread类
构造方法:
Thread (Runnable target,String name)//参数1:传入一个Runnable对象,分配任务,参数2:设置此线程名称
常用方法:
setDaemon(boolean on) //将此线程设置为守护线程或用户线程,值为true则设置为i守护线程
start()//线程执行,java虚拟机调用此线程的run方法
interrupt()//中断此线程
sleep(long millis)//让当前正在执行的线程休眠指定毫秒数,毫秒结束之后,继续执行
setPriority()//设置此线程的优先级
获取此线程的线程名称
Thread.currentThread().getName();//currentThread()方法返回对当前正在执行的线程对象的引用,getName()方法获取名称
继承Thread类实现多线程
实现步骤:
1. 创建一个Thread类的子类
2. 在Thread类的子类中重写Thread类中的run方法,设置线程任务
3. 创建Thread类的子类对象
4. 调用Thread类中的方法 start,开启新的线程,执行run方法 void start()使线程开始执 行:JVM会自动调用该线程的 run 方法
格式:
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
}
MyThread对象只是用了一次,可以使用匿名内部类方式直接写一个线程任务
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
new Thread("我叫thread"){
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"Thread线程执行了"+i);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程执行了" +i);
Thread.sleep(1000);
}
}
实现Runnable接口
实现步骤:
1. 创建一个Runnable接口的实现类(可以使用匿名内部类)
2. 实现类中需重写run方法,设置线程任务
3. 创建一个Runnable接口的实现类对象
4. 创建Thread类对象,构造方法中传递Runnable对象
5. 调用Thread类的start方法,开启run方法
格式1:
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
MyRunnable mr = new MyRunnable();
Thread t =new Thread(mr);
t.start();
格式2:
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
实现Runnable接口比继承Thread类相比有以下优势:
1.通过创建任务,然后分配给现成的方式来实现多线程,更适合多个线程同时执行一个任务的情况
2.可以避免单继承带来的局限性
3.提高了程序的健壮性
4.线程池技术,可以接收Runnable类型的任务,而不能接收Thread类型的线程
线程安全
当多个线程并发执行访问一个共享资源时,一个线程可能还未结束执行,另一个线程已经抢占了cpu的使用权,导致数据变更或者丢失的情况,这样线程是不安全的。
解决方法
1.同步代码块
2.同步方法
3.Lock锁
(1)同步代码块
格式:
synchronized(锁对象){
可能会引发线程安全问题的代码块
}
注意:
锁可以是任何对象,线程本身也可以是锁对象
所有的线程都要关注同一把锁,锁对象创建不能写在run方法里这样会导致每个线程有一把琐,无法起到同步的效果
举例:三个站台共同卖10张票
public class Dem02 {
public static void main(String[] args) {
Ticket t = new Ticket();
//三个站台一起卖票
new Thread(t,"一号站台").start();
new Thread(t,"二号站台").start();
new Thread(t,"三号站台").start();
}
static class Ticket implements Runnable{
/**
* Synchronized(锁对象){ 需要线程排队执行的任务 }
*/
//票数
private int count = 10;
//创建锁对象
Object o = new Object();
@Override
public void run() {
//卖票
while(true) {
synchronized (o) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "准备卖票。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,还剩票数:" + count);
} else {
break;
}
}
}
}
}
}
(2)同步方法
格式:
把需要排队执行的任务代码块封装成方法,再用synchronized修饰方法,就完成了线程排队操作
public synchronized void Sale(){
public class Dem02 {
public static void main(String[] args) {
Ticket t = new Ticket();
//三个站台一起卖票
new Thread(t,"一号站台").start();
new Thread(t,"二号站台").start();
new Thread(t,"三号站台").start();
}
static class Ticket implements Runnable{
/**
* Synchronized(锁对象){ 需要线程排队执行的任务 }
*/
private int count = 10;
//创建锁对象
Object o = new Object();
@Override
public void run() {
//卖票
while(true) {
boolean flag = Sale();
if(!flag){
break;
}
}
}
public synchronized boolean Sale(){
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "准备卖票。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,还剩票数:" + count);
return true;
}
return false;
}
}
}
(3)显式锁Lock
同步代码块和同步方法都属于隐式锁,Lock属于显式锁
步骤:
1.在任务类里创建Lock对象
2.在需要上锁的代码块前面调用lock()方法获取锁
3.代码块结束的地方调用unlock()方法进行解锁
格式:
Lock lock = new ReentrantLock();//创建ReentraLock锁对象
lock.lock();
线程安全问题代码块
lock。unlock();
注意:lock 的构造方法里可以传入一个布尔类型的值,为true则是公平锁(先来先执行,排队),值为false,是不公平锁(线程随意争抢,谁先抢到谁先执行)
线程池
为什么要使用线程池
在 Java 语言中,创建一个线程看上去非常简单。实现Runnable接口,然后像创建一个对象一样,直接 new Thread 就可以了。
但实际上线程的创建和销毁远不是创建一个对象那么简单。线程的创建需要调用操作系统内核的 API,然后操作系统为其分配一系列资源,所以整个成本很高,导致线程是一个重量级的对象,应该避免频繁创建和销毁。
ThreadPoolExecutor类
ava.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,用于创建一个线程池对象
构造方法
public
ThreadPoolExecutor(
int
corePoolSize,
int
maximumPoolSize,
long
keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
CorePoolSize(核心线程数):当创建一个线程池以后,默认线程池内线程数为0,当有一个任务到达时,就会创建一个线程来执行任务,当线程数达到CorePoolSize时,接下来到达的任务会放在阻塞队列中。
maxmumpoolSize(线程池线程数):控制线程池最多创建线程的数量
keepAliveTime(最大存活时间):表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
-
TimeUnit.DAYS; //天
-
TimeUnit.HOURS; //小时
-
TimeUnit.MINUTES; //分钟
-
TimeUnit.SECONDS; //秒
-
TimeUnit.MILLISECONDS; //毫秒
-
TimeUnit.MICROSECONDS; //微妙
-
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务,线程池的排队策略与BlockingQueue有关
threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,
线程池的使用:
创建线程池,通过ThreadPoolExecutor类new一个线程池对象,或者通过来自java.util.concurrent.Executors: 线程池的工厂类,用来生成线程池,然后创建一个实现Runnable接口的实现类,重写run()方法,来设置任务,Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;或者 通过java.util.concurrent.ExecutorService: 线程池接口submit (Runnable task) 提交一个 Runnable 任务执行,shutdown()和shutdownNow()是用来关闭线程池的。
Executors类包含四种常用的线程池:
定长线程池(FixedThreadPool)周期定长线程池(ScheduledThreadPool)单线程线程池(SingleThreadExecutor)缓存线程池(CashedThreadPool)
Lambda表达式:
jdk1.8以后:
省略创建实现接口的类的操作,适用于只有一个抽象方法的接口
格式:
(参数列表)-> {重写的方法里的代码块};
注意:参数列表可以有,可以没有,多个参数用逗号分隔开,只有一行代码时可以省略大括号,多行代码加{}并用分号分开,
例子:
new Thread(() -> {
System.Out.Println("Hello world");
})