Java线程
一、进程与线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间;线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。线程实际上是进程基础上的进一步划分,一个进程启动后,里面的若干执行路径又可以划分若干个线程;
总而言之,一个程序至少有一个进程,一个进程至少有一个线程;进程在执行过程中拥有独立的内存单元,多个线程共享内存,提高了程序的运行效率
二、同步与异步,并发与并行
1.同步与异步:
java多线程中,经常涉及到数据共享的我问题,多个线程同时访问同一个数据资源,同步是指:线程排队执行,数据安全,但效率低;而异步是指:线程可以同时执行访问,效率高,但是数据不安全。
2.并发与并行:
从宏观的角度来说,也就是一段时间内,并发和并行都是在处理多个事件,但是这两者是有区别的,并行是指两个或者多个事件在同一个时间段发生,注意:这是时间段发生,而并发是指两个或者多个事件在同一时刻发生,这是在同一时刻。
三、实现方式
1.继承Thread类
通过继承Thread类,重写run方法,创建对象后,调用start()启动线程。
public class Demo1 {
/**
* 通过继承Thread来创建新线程
* */
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
System.out.println("我是主线程"+Thread.currentThread().getName());
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是新线程"+Thread.currentThread().getName());
}
}
}
执行结果
我是主线程main
我是新线程Thread-0
Process finished with exit code 0
2.实现Runnable接口
public class Demo2 {
/**
*多线程技术
* 实现Runnable与继承Thread相比有如下优点
* 1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
* 2.可以避免单继承所带来的局限性
* 任务与线程本身是分离的。提高了程序的健壮性
*
*
* */
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
for (int i = 0; i < 3; i++) {
System.out.println("主线程"+Thread.currentThread().getName());
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <3 ; i++) {
System.out.println("我是新线程"+Thread.currentThread().getName());
}
}
}
}
执行结果
主线程main
我是新线程Thread-0
主线程main
我是新线程Thread-0
主线程main
我是新线程Thread-0
Process finished with exit code 0
3.实现Callable接口(不常用)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo13 {
public static void main(String[] args) {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> f = new FutureTask<>(c);
new Thread(f).start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(100);
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
return 100;
}
}
}
执行结果
main0
main1
main2
main3
main4
Thread-00
Thread-01
Thread-02
Thread-03
Thread-04
Process finished with exit code 0
值得注意的是Callalble接口支持返回执行结果,当调用FutureTask.get()方法时,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞
Runnable 与 Callable的异同点
相同点:
1.都是接口
2.都可以编写多线程程序
3.都采用Thread.start()启动线程
不同点:
1.Runnable没有返回值;Callable可以返回执行结果
2.Callable接口的call()允许抛出异常;Runnable的run()不能抛出
四、线程安全
线程不安全的原因:多个线程同时执行,同时操作一个数据,导致某个数据的实时性不一致,例如在某个线程,操作数据时,另外的线程改变了此时的数据,导致了数据不一致的情况,为了解决此类问题,使线程排队执行,也就是线程同步synchronized,通过加锁的机制来实现
一、同步代码块
public class demo14 {
/**
* 线程同步:synchronized
* 解决方案一:同步代码块:
* 格式:synchronized(锁的对象){}
*java中任何对象都可以作为锁的存在,线程会判断这个对象是否被打上了锁的标记
* 如果打上了锁的标记,表示有线程正在执行,其他线程就会等带这个对象是否解锁,锁的标记是否消失
*当打上锁的线程执行结束了,锁就会解开,锁的标记就会清除,等待的线程就会同时争抢做个解开的锁,那个线程抢到就会
* 给锁打上标记,由谁来执行,而需要注意的是:锁的对象必须共享的。
* */
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (count > 0) {
//买票
System.out.println("正在准备卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
}else{
break;
}
}
}
}
}
}
执行结果:
正在准备卖票:
Thread-0出票成功,余票:9
正在准备卖票:
Thread-0出票成功,余票:8
正在准备卖票:
Thread-0出票成功,余票:7
正在准备卖票:
Thread-0出票成功,余票:6
正在准备卖票:
Thread-0出票成功,余票:5
正在准备卖票:
Thread-0出票成功,余票:4
正在准备卖票:
Thread-0出票成功,余票:3
正在准备卖票:
Thread-0出票成功,余票:2
正在准备卖票:
Thread-0出票成功,余票:1
正在准备卖票:
Thread-0出票成功,余票:0
Process finished with exit code 0
注意的是:锁的对象必须是同一个,也就是只有一把锁,而不能每个线程私用的,每个线程都要一把锁。
二、同步方法
public class demo15 {
/**
* 线程同步:synchronized
* 解决方案一:同步方法:同步方法的锁就是this,同步方法倘若是静态的
* 锁的对象是类名.class,所以需要注意的是同步方法中的线程的对象必须是
* 同一个,
* 在方法的返回值前面加synchronized修饰
*
* */
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
@Override
public void run() {
while (true) {
boolean flag = sale();
if (!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//买票
System.out.println("正在准备卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
return true;
}else{
return false;
}
}
}
}
执行结果
正在准备卖票:
Thread-0出票成功,余票:9
正在准备卖票:
Thread-0出票成功,余票:8
正在准备卖票:
Thread-0出票成功,余票:7
正在准备卖票:
Thread-0出票成功,余票:6
正在准备卖票:
Thread-0出票成功,余票:5
正在准备卖票:
Thread-0出票成功,余票:4
正在准备卖票:
Thread-0出票成功,余票:3
正在准备卖票:
Thread-0出票成功,余票:2
正在准备卖票:
Thread-0出票成功,余票:1
正在准备卖票:
Thread-1出票成功,余票:0
Process finished with exit code 0
线程同步:synchronized * 解决方案一:同步方法:同步方法的锁就是this,同步方法倘若是静态的 * 锁的对象是类名.class,所以需要注意的是同步方法中的线程的对象必须是 同一个, * 在方法的返回值前面加synchronized修饰
三、显示锁Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class demo16 {
/**
* 线程同步:
* 解决方案三:显示锁Lock子类ReentrantLock,自己创建锁的对象,自己锁和解锁
* 同步代码块和同步方法属于隐式锁,
*
* */
public static void main(String[] args) {
Runnable r = new Ticket();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
//显示锁
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
//上锁
l.lock();
if(count>0){
//买票
System.out.println("正在准备卖票:");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
}else{
break;
}
//解锁
l.unlock();
}
}
}
}
执行结果
正在准备卖票:
Thread-1出票成功,余票:9
正在准备卖票:
Thread-1出票成功,余票:8
正在准备卖票:
Thread-1出票成功,余票:7
正在准备卖票:
Thread-1出票成功,余票:6
正在准备卖票:
Thread-1出票成功,余票:5
正在准备卖票:
Thread-1出票成功,余票:4
正在准备卖票:
Thread-1出票成功,余票:3
正在准备卖票:
Thread-1出票成功,余票:2
正在准备卖票:
Thread-1出票成功,余票:1
正在准备卖票:
Thread-1出票成功,余票:0
公平锁与非公平锁:公平锁,线程谁先来先执行,非公平锁,就是全部线程一起争抢这把锁,这三种锁默认都是非公平的锁,显示锁的构造方法中,传递个参数位true,创建的就是公平锁
线程死锁
比如A进程等B进程解锁,而B进程等A进程解锁,就是死锁,解决的方式:当调用有锁的方法时,不要在调用其他有锁的方法。
多线程通信
生产者与消费者问题:当生产者唤醒时时,消费者等待,当唤醒消费者时,生产者等待。保证生产者生产时,消费者没有在消费,消费者消费时,生产者没有被生产,确保数据安全
线程的状态
-
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。-
TERMINATED
已退出的线程处于此状态。
线程池
流程:创建线程->创建任务->执行任务->关闭线程,花费时间的是创建线程和关闭线程,如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源
线程池的好处:
1.降低资源消耗
2.提高响应速度
3. 提高线程的可管理性
Java中的四种线程池
1. 缓存线程池:长度无限制
任务加入后的执行流程
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在,则创建线程并放入线程池,然后使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo8 {
/**
* 缓存线程池
* (长度无限制)
* 任务加入后的执行流程
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在,则创建线程并放入线程池,然后使用
*
* */
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
执行结果
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
Process finished with exit code 0
2. 定长线程池
长度时指定的数值) 任务加入后的执行流程: 1.判断线程池是否存在空闲线程 2.存在则使用 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用 4.不逊在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo9 {
/**
* 定长线程池
* (长度时指定的数值)
* 任务加入后的执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4.不逊在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* */
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
执行结果:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
3. 单线程线程池
执行流程: 1.判断线程池的那个线程是否空闲 2.空闲则使用 3、不空闲则等待,池中的单个线程空闲后使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo10 {
/**
* 单线程线程池
* 执行流程:
* 1.判断线程池的那个线程是否空闲
* 2.空闲则使用
* 3、不空闲则等待,池中的单个线程空闲后使用
*
* */
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
执行结果
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
4. 周期性任务定长线程池
周期任务 定长线程池 执行流程: 1.判断线程池是否存在空闲线程 2.存在则使用 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用 4.不存在空闲线程,且线程已满的情况下,则等待线程空闲 周期性任务执行时: 定时执行,当某个时机触发时,自动执行某任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo11 {
/**
* 周期任务 定长线程池
* 执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
* 4.不存在空闲线程,且线程已满的情况下,则等待线程空闲
*
* 周期性任务执行时:
* 定时执行,当某个时机触发时,自动执行某任务
*
* */
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
*1.定时执行一次
*参数1:定时执行的任务
* 参数2:时常数字
* 参数3:时常数字的时间单位Timeunit的常量指定
*
* */
/* service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},5, TimeUnit.SECONDS);*/
/**
* 周期性执行任务
* 参数1:任务
* 参数2:延续时长数字(第一次执行在什么时间以后)
* 参数3:周期性时长数字
* 参数4:时长数字的单位
*
*
*
* */
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},5,1,TimeUnit.SECONDS);
}
}
执行结果:每5秒执行执行一次
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1