进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分
成若干个线程
同步与异步&并发与并行
-
同步:排队执行 , 效率低但是安全.
-
异步:同时执行 , 效率高但是数据不安全.
-
并发:指两个或多个事件在同一个时间段内发生
-
并行:指两个或多个事件在同一时刻发生(同时发生)
多线程实现方法
线程的创建方式有多种,其中直接继承Thread类并直接运行和实现Runnable接口并委线程以任务的方式两种最为常见
- 继承Thread
public class MyThreea extends Thread{
/**
* run方法就是线程需要执行的任务方法
*/
@Override
public void run() {
//这里的代码 就是一条新的执行路径。
//这个执行路径的触发方式,不是调用run方法,二十通过thread对象的start()来启动任务
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
main方法中使用调用
public static void main(String[] args) {
MyThreea myThreea = new MyThreea();
myThreea.start();
for (int i=5;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
运行结果:(若想看到main线程和Thread-0线程交叉执行结果,可加以sleep方式)
- 实现Runnable接口
Runnable是一个接口,需要通过implement实现
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"i);
}
}
}
public static void main(String[] args) {
//实现Runnable
//1. 创建一个任务对象
MyRunnable mr = new MyRunnable();
//2. 创建一个线程,病危其分配一个任务
Thread thread = new Thread(mr);
//3. 执行这个线程
thread.start();
for (int i=5;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"i);
}
}
运行结果:
* 多线程技术
* 实现Runnable 与是继承Thread相比有如下优势:
* 1.通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同人物的情况
* 2.可以避免单继承带来的局限性
* 3.任务与线程本身是分离的,提高了程序的健壮性
* 4.后续学习地线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
线程休眠sleep
public static void main(String[] args) throws InterruptedException {
//线程的休眠 sleep
//每隔一秒输入一个i
for (int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000);//指定时间休眠
}
}
sleep方法有两种形式
1.sleep(long millis):导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
2.sleep(long millis, int nanos):导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
线程阻塞
线程阻塞指所有比较消耗时间的操作,不仅仅是线程休眠这种情况,比如读取文件、接收用户输入等也称为耗时操作。
简单理解就是,只要使线程非死亡等待都属于线程阻塞
线程的中断
public class ThreadInterrupt {
public static void main(String[] args) {
//线程的中断
//一个线程是一个独立的执行路径,他是否结束,应该由其自身绝对
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t1.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=5;i<10;i++){
System.out.println(i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("发现中断标价,线程自杀");
return; //结束run方法 即线程自杀,否则线程继续运行
}
}
}
}
}
运行结果:
守护线程
线程:分为守护线程和用户线程
用户线程:当一个进程不包含任何一个存活的用户线程时,进程结束
守护线程:守护用户线程,当最后要给用户结束时,所有守护线程自动死亡
守护线程生成:thread.setDaemon(true);
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(500);//为了突出抢占时间片,线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//展示t1刚创建的线程状态
System.out.println(t1+":"+t1.getState());
t1.setDaemon(true); //设置t1为守护线程
t1.start();
//展示t1的start后的线程状态
System.out.println(t1+":"+t1.getState());
for (int i=5;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(200);//为了突出抢占时间片,线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//展示t1在用户线程结束后的运行状态
System.out.println(t1.getName()+":"+t1.getState());
}
运行截图:
线程安全问题
线程不安全:多个线程同时执行,争抢并操作同一个资源
//线程不安全用例
public class ThreadSafety {
public static void main(String[] args) {
//线程不安全:多个线程同时执行,争抢并操作同一个资源
Runnable runnable = new MyRunnable();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class MyRunnable implements Runnable{
//剩余票数
private int count = 10;
@Override
public void run() {
while (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}
}
}
}
运行结果:
出现原因:当count==1时,由于“卖票”时线程休眠1s,所以三个线程都成功判定条件进入while循环。
**
那怎么才能解决线程不安全呢?
Java提供了同步代码块、同步方法和Lock的子类ReentrantLock三种方式解决线程不安全问题
-
同步代码块:
格式:synchronized(锁对象){} 任何对象都可作为锁存在 必须看同一把锁
public class ThreadSafety3 {
/**
* 线程同步:synchronized
* @param args
*/
public static void main(String[] args) {
Runnable runnable = new MyRunnable();//所有线程必须使用同一个对象,即使用同一把锁
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class MyRunnable implements Runnable{
//剩余票数
private int count = 5;
private 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 {
System.out.println(Thread.currentThread().getName()+"发现票卖光了");
break;
}
}
}
}
}
}
运行结果:
-
同步方法
同步方法于同步代码块类似,只需在需要锁住的方法中添加synchronized修饰便可
public class ThreadSafety2 {
public static void main(String[] args) {
//解决方法2.同步方法
Runnable runnable = new MyRunnable();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class MyRunnable implements Runnable{
//剩余票数
private int count = 10;
@Override
public void run() {
while (true){
boolean flag = sale();
if (!flag){
System.out.println(Thread.currentThread().getName()+"发现票卖完啦");
break;
}
}
}
public synchronized boolean sale(){ //在方法中加入sychronized,同步方法
if (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
return true;
}
return false;
}
}
}
运行结果:
- ReentranLock
public class ThreadSafety1 {
/**
* 同步代码块 和 同步方法都属于隐式锁
* @param args
*/
public static void main(String[] args) {
//线程不安全:多个线程同时执行,争抢并操作同一个资源
//解决方法3.显示锁Lock子类ReentrantLock
Runnable runnable = new MyRunnable();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class MyRunnable implements Runnable{
//剩余票数
private int count = 10;
//显示锁 lock
// fair参数为true就是公平锁
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
lock.lock();
// System.out.println(Thread.currentThread().getName());
if (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count);
}else {
System.out.println(Thread.currentThread().getName()+"发现票卖完了");
break;
}
lock.unlock();
}
}
}
}
运行结果:
公平锁与非公平锁
公平锁: 被锁起来的资源只需要线程排队获取
非公平锁: 被锁起来的资源需要各个线程抢占获取 Java默认锁方式都是不公平锁
线程死锁
线程死锁:是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。
死锁避免方法:不要在任何有可能导致锁产生的方法调用另一个需要产生锁的方法
死锁演示:
public class ThreadDeadLock {
public static void main(String[] args) throws InterruptedException {
//线程死锁:
Culprit culprit = new Culprit();
Police police = new Police();
new MyThread(culprit,police).start();
culprit.say(police);
}
static class MyThread extends Thread{
private Culprit culprit;
private Police police;
public MyThread(Culprit culprit,Police police){
this.culprit = culprit;
this.police = police;
}
@Override
public void run() {
try {
police.say(culprit);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//罪犯
static class Culprit{
public synchronized void say(Police p) throws InterruptedException {
System.out.println("罪犯:你放了我,我放了人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被放走了,罪犯也放了人质");
}
}
//警察
static class Police{
public synchronized void say(Culprit c) throws InterruptedException {
System.out.println("警察:你放了人质,我放了你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察救了人质,但是罪犯跑了");
}
}
}
运行结果:
线程的六种状态
线程状态。 线程可以处于以下状态之一:
- NEW 尚未启动的线程处于此状态。
- RUNNABLE 在Java虚拟机中执行的线程处于此状态。
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
- WAITING 无限期等待另一个线程执行特定操作的线程处于此状态
- TIMED_WAITING 正在等待另一个线程执行最多指定等待时间的操作的线程处于此 状态。
- TERMINATED 已退出的线程处于此状态
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态
附一张多线程超经典图(这张图搞懂了,java多线程就懂了差不多了)
带返回值的线程Callable
java中第三种线程实现方式
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
Integer j = task.get();
System.out.println("返回值内容:"+j);
for (int i=5;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
static class MyCallable implements Callable{
@Override
public Object call() throws Exception {
for (int i=0;i<5;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
return 100;
}
}
}
运行截图:
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率因为频繁创建线程和销毁线程需要时间.
线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
- 缓存线程池
public class CacheThreadPool {
//线程池
/**
* 缓存线程池(无限制长度)
* 任务加入后执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程,并加入线程池,然后使用
* @param args
*/
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()+"锄禾日当午");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
//一秒后,上面的任务已经完成,新的任务进来,会使用之前创建的空闲线程
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
- 定长线程池
public class FixThreadPool {
/**
* 定长线程池(长度是指定的数值)
* 任务加入后执行流程:
* 1. 判断线程吃是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池,然后使用
* 4. 不存在空i西安线程,且线程池已满的情况下,则等待线程池存在空闲线程
* @param args
*/
public static void main(String[] args) {
//指挥线程池中执行新的任务
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
- 单线程线程池
public class SingleThreadPool {
/**
* 单线程线程池
* 执行流程:
* 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()+"锄禾日当午");
}
});
}
}
- 周期定长线程池
public class CycleThreadPool {
/**
* 周期任务 定长线程池:
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行,当某个实际触发时,自动执行某任务
* @param args
*/
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("锄禾日当午");
// }
// },5, TimeUnit.SECONDS);
/**
* 2.周期执行任务
* 参数1.任务
* 参数2.延时执行数字(第一次执行在什么时间后)
* 参数3.周期时长数字(每隔多久执行一次
* 参数4.时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("旱地和下图");
}
},5,1,TimeUnit.SECONDS);
}
}