进程与线程的简单理解
进程是一个独立的执行单元,它拥有自己的内存空间、文件句柄和系统资源.进程是操作系统层面的,每个应用运行就是一个进程.进程之间通常是隔离的,它们不能直接访问对方的内存空间,必须通过进程间通信(IPC)机制来进行数据交换。
线程是进程内的执行单元,归属于进程,它共享进程的内存空间和资源。一个进程可以包含多个线程。但至少需要一个(普通)线程存活(守护线程不算).
并行与并发:
并行概念:并行是指多个进程或者多个线程同时执行任务,只是看起来并行,事实上没有真正的并行(暂时不存在真正意义上的并行,以后不知道).只是人类的感官觉得是并行.(即多核CPU中央处理器也无法达到真正并行)其实都是走走停停
并发:并发是指在相同的时间段内处理多个任务或操作的能力。这并不一定意味着这些任务实际上是同时执行的,而是它们在某个时间间隔内交替执行,以创建一种错觉,似乎它们在同时进行。可以是线程,也可以是进行,一般是指多个线程同时抢占同一个资源,导致错误结果.俗称并发安全问题
创建线程的第一种方式继承Thread,重写run方法
public class Thread1 {
public static void main(String[] args) {
Thread t1=new MyThread1();
Thread t2=new MyThread2();
t1.start();
t2.start();
}
}
//第一种方式:继承线程
class MyThread1 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("查水表的");
}
}
}
局部内部类写法
public class Thread1 {
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
};
t1.start();
t2.start();
}
}
匿名内部类写法
public class Thread1 {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
}.start();
new Thread(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
}.start();
}
}
继承Thread的优点是写起来方便(特别是匿名内部类方式),缺点是Java是单继承的.继承了Thread之后无法再继承其他类.
第二点是线程和线程要干的活有着强耦合关系
创建线程的第二种方式.实现Runable接口.定义线程要执行的任务,将任务交给线程去执行
public class Thread2 {
public static void main(String[] args) {
//创建任务
MyRunable1 my1=new MyRunable1();
MyRunable2 my2=new MyRunable2();
//创建线程 并让线程启动任务
Thread t1=new Thread(my1);
Thread t2=new Thread(my2);
t1.start();
t2.start();
}
}
class MyRunable1 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
}
class MyRunable2 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("查水表的");
}
}
}
内部类写法
public class Thread2 {
public static void main(String[] args) {
//创建线程 并让线程启动任务
Thread t1=new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("查水表的");
}
}
});
t1.start();
t2.start();
}
}
匿名内部类写法
public class Thread2 {
public static void main(String[] args) {
//创建线程 并让线程启动任务
new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("你是谁呀");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("查水表的");
}
}
}).start();
}
}
CurrentThread方法介绍:获取当前运行的线程
public class Thread3 {
public static void main(String[] args) {
/**
* 线程提供了一个静态方法
* static Thread currentThread()
* 该方法用来获取运行这个方法的线程
* main方法也是靠一个线程运行的.当JVM启动
* 后会自动创建一个线程来执行main方法.称之为主线程
*/
Thread thread = Thread.currentThread();
System.out.println(thread);//Thread[main,5,main]
dosome();
Thread t1=new Thread(){
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-0,5,main]
}
};
Thread t2=new Thread(){
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-1,5,main]
}
};
t1.start();
t2.start();
}
public static void dosome(){
Thread thread = Thread.currentThread();
System.out.println("运行dosome方法的线程:"+thread);//谁调用就是谁
}
}
线程相关信息的获取
//线程提供了获取自身信息的相关方法
public class Thread4 {
public static void main(String[] args) {
//获取线程名
Thread thread = Thread.currentThread();
String name = thread.getName();
System.out.println("name:"+name);
//获取线程的唯一标识(id) 线程名可以重 但是id不能重
long id = thread.getId();
System.out.println("该线程的id是:"+id);
Thread t1=new Thread(){
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-0,5,main]
System.out.println("该线程的id是:"+t.getId());
}
};
Thread t2=new Thread(){
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("运行dosome方法的线程:"+t);//Thread[Thread-0,5,main]
System.out.println("该线程的id是:"+t.getId());
}
};
t1.start();
t2.start();
//获取线程的优先级 1到10之间 默认都是5
int priority = thread.getPriority();
System.out.println("主线程的优先级:"+priority);
boolean alive = thread.isAlive();
System.out.println("线程是否还活者:"+alive);
boolean interrupted = thread.isInterrupted();
//中断就是让线程停了 一旦线程被中断(使用 interrupt() 方法中断),它的生命周期结束,不能重新启动.等待垃圾回收器回收
System.out.println("线程是否被中断:"+interrupted);
//守护线程或称之为后台线程
boolean daemon = thread.isDaemon();
System.out.println("线程是否为守护线程:"+daemon);
}
}
一个个讲上面提到的方法
首先线程优先级 int priority = thread.getPriority(); 1到10之间 默认都是5
线程优先级作用:干涉线程调度器工作 只能理论上尽量让该线程多获得CPU时间片,但是实际情况,说不好
理论上:线程优先级越高,获得CPU时间片的次数越多. 优先级用整数表示: 10最高 1最低 默认5
大于10或者小于1都会报错
理论来讲应该是优先级高的先输出完,然后是默认的,然后是优先级最低的,但是实际效果是不一定
调整线程优先级的结果未必理想,但是这是唯一可以改变争取或降低CPU时间片的方式
//线程优先级
public class PriorityDemo {
public static void main(String[] args) {
//线程优先级作用:干涉线程调度器工作 只能理论上尽量让该线程多获得CPU时间片,但是实际情况,说不好
//理论上:线程优先级越高,获得CPU时间片的次数越多. 优先级用整数表示: 10最高 1最低 默认5
Thread max=new Thread(){
@Override
public void run() {
for(int i=0;i<10000;i++){
System.out.println("max");
}
}
};
Thread min=new Thread(){
@Override
public void run() {
for(int i=0;i<10000;i++){
System.out.println("min");
}
}
};
Thread norm=new Thread(){
@Override
public void run() {
for(int i=0;i<10000;i++){
System.out.println("norm");
}
}
};
//如果设置的优先级>最高(10) 或者<(1)都会报错 抛IllegalArgumentException
//max.setPriority(10);
max.setPriority(Thread.MAX_PRIORITY);//用常量 设置优先级
min.setPriority(Thread.MIN_PRIORITY);
norm.setPriority(Thread.NORM_PRIORITY);
max.start();
min.start();
norm.start();
}
}
sleep阻塞
线程提供了一个静态方法:static void sleep(long ms)
使运行这个方法的线程阻塞指定毫秒.超时后该线程会自动回到Runnable状态,
等待再次获得CPU时间片运行 这个和计时器差不多
public class Thread5 {
public static void main(String[] args) {
System.out.println("程序开始了");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束了");
}
}
小案例:控制太输入倒计,每个1秒,到0停止
java中一般会引起线程阻塞的方法都要求捕获InterruptedException中断异常
public class Thread6 {
public static void main(String[] args) {
System.out.println("输入数字:");
Scanner scanner=new Scanner(System.in);
String line = scanner.nextLine();
Integer num=Integer.parseInt(line);
for(;num>0;num--){
System.out.println(num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束");
}
}
sleep阻塞demo2
sleep方法要求必须处理中断异常,原因在于当一个线程调用了sleep方法处于阻塞状态的过程中其他线程中若调用了它的interupt()方法中断时,它就会在sleep方法中抛出中断异常.这时并非是将这个线程直接中断,而是中断了它的阻塞状态.
public class Thread7 {
public static void main(String[] args) {
Thread lin=new Thread(){
@Override
public void run() {
//while(!Thread.currentThread().isInterrupted()) {
System.out.println("林:刚美完容,睡一觉");
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢?干嘛呢?");
}
//}
System.out.println("林:醒了");
}
};
Thread huang=new Thread(){
@Override
public void run() {
System.out.println("黄:开始砸墙!");
for(int i=0;i<5;i++){
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("咣当!");
System.out.println("黄:搞定!");
//中断lin线程
lin.interrupt();//这里注意:JDK8之前这样写报错,报错原因JDK8之前要求局部内部类中要使用方法内的局部变量,那个变量前要加final
}
};
lin.start();
huang.start();
}
}
守护线程
守护线程又称后台线程,默认创建的线程都是普通线程或称为前台线程,线程提供了一个
方法:void setDaemon(boolean on)
只有调用该方法并传入为true时,该线程才会被设置为守护线程.
守护线程在使用上于普通线程没有差别,但是在结束时机上有一个区别,即:线程结束时所有正在运行的守护线程都会被强制停止.
进程的结束:当一个进程中所有的普通线程都结束时,进程即结束
GC就是一个守护线程
public class Thread8 {
public static void main(String[] args) {
Thread rose=new Thread(){
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("rose:啊啊啊啊啊啊啊啊啊!!!!!");
}
};
Thread jack=new Thread(){
@Override
public void run() {
while(true){
System.out.println("jack:you jump! I jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread disanzhe=new Thread(){
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("disanzhe:吃瓜!看热闹");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
rose.start();
jack.setDaemon(true);//设置为守护线程,要在启动前设置 有几个线程启动就守护几个线程
jack.start();
disanzhe.start();
//原先的观点:main方法结束,程序退出.以后的观点就是:当所有前台线程结束,进程结束.才算结束
System.out.println("main方法结束");//这里是main先结束
//这样的话,守护线程永远不会结束,原因是main线程还一致活者
// while(true){
//
// }
}
}
join:协调线程之间的同步运行
线程提供了一个方法:
void join()
该方法可以协调线程之间的同步运行
同步与异步:
同步:运行有顺序
异步运行:运行代码无顺序,多线程并发运行就是异步运行
多线程是异步运行的,但是有时候我们也需要他们同步---->利用join
public class Thread9 {
private static boolean isFinish=false;//这个放在方法内编译不通过
public static void main(String[] args) {
int a=1;
Thread download=new Thread(){
@Override
public void run() {
System.out.println("down:开始下载图片");
for(int i=1;i<=100;i++){
System.out.println("down:"+i+"%");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("down:下载图片完毕");
isFinish=true;
}
};
Thread show=new Thread(){
@Override
public void run() {
System.out.println("show:开始显示图片");
//加载图片前应先等待下载线程将图片下载完毕
try {
/**
* show线程里调用download.join()方法后
* 就进入了阻塞状态,知道download线程的run方法执行完毕才会解除
*/
//这样可以使用应该和线程上下文有关
download.join();//在show的线程里写 download.join() 就是等待download线程完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!isFinish){
throw new RuntimeException("加载图片失败");
}
System.out.println("show:显示图片完毕");
}
};
download.start();
show.start();
}
}
wait 用锁对象.wait()
wait()方法其实主要做三件事:
①把当前线程放进等待队列中
②释放锁
③被其他线程唤醒时,尝试重新获取锁
wait() 无参数版本
wait(long) 指定最大等待时间,单位毫秒
wait(long,int) 精度更高,前面是毫秒,后面是纳秒
notifyAll():唤醒加了相同锁的所有线程
wait和sleep其实是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间。这两个方法设计的初心是不同的,一个是单纯为了让线程进行阻塞等待而休眠一段时间,而一个是为了解决线程的执行顺序的问题。最明显的区别就是wait需要搭配锁使用,而sleep不需要。wait 是 Object 的方法 sleep 是 Thread 的静态方法。他们唯一的相同点就是都可以让线程进入阻塞等待状态。
但是自测sleep无法唤醒,只能interrupt中断(不是很清楚)
notify:用锁对象.notify() 唤醒
public class Thread7 {
public static void main(String[] args) {
Object o=new Object();
Thread lin=new Thread(){
@Override
public void run() {
//while(!Thread.currentThread().isInterrupted()) {
System.out.println("林:刚美完容,睡一觉");
synchronized (o) {
try {
//Thread.sleep(1000000);
o.wait();
} catch (InterruptedException e) {
System.out.println("林:干嘛呢?干嘛呢?");
}
}
//}
System.out.println("林:醒了");
}
};
Thread huang=new Thread(){
@Override
public void run() {
System.out.println("黄:开始砸墙!");
for(int i=0;i<5;i++){
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("咣当!");
System.out.println("黄:搞定!");
//唤醒该锁线程,可以在线程里,方法里
synchronized (o){
o.notify();
}
//中断lin线程
//lin.interrupt();//这里注意:JDK8之前这样写报错,报错原因JDK8之前要求局部内部类中要使用方法内的局部变量,那个变量前要加final
}
};
lin.start();
huang.start();
}
}
public class Thread10 {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
System.out.println("Thread1被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("出错了");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
System.out.println("Thread2被唤醒了");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("出错了");
}
}
});
thread1.start();
thread2.start();
Thread.sleep(5000);
synchronized (lock){
//lock.notify();
lock.notifyAll();
}
}
}
并发安全问题
并发会出现的情况
多线程并发的安全问题
产生:当多个线程并发操作同一资源时,由于线程切换时机的不确定性,会导致
执行操作资源的代码顺序未按照设计顺序执行,出现操作混乱的情况.
严重时可能导致系统瘫痪.
解决:将并发操作同一资源改为同步操作,即:有先后顺序的操作
public class SyncDemo1 {
public static void main(String[] args) {
Table table=new Table();
Thread t1=new Thread(){
@Override
public void run() {
while(true){
int bean=table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
while(true){
int bean=table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
//20个豆子
private int bean=20;
public int getBean(){
if(bean==0){
throw new RuntimeException("没有豆子了");
}
//模拟线程执行到这里没有时间了 它是告诉线程调度器,愿意主动让出CPU时间片,不能确保线程一定会让出
Thread.yield();
return bean--;
}
}
解决办法:加同步锁
在getBean方法上加锁 synchronized 对方法枷锁 就是给对象枷锁
但是在方法上加上synchronized会降低效率
那么用同步块缩小控制范围
同步块:语法
synchronized(同步监视器对象){
需要同步运行的代码片段
}
同步块可以更精准的控制需要同步运行的代码片段.有效的缩小同步范围可以在保证并发安全
的前提下提高代码并发运行的效率
使用同步块控制多线程同步运行必须要求这些线程看到的同步监视器对象为[同一个]
有效缩小同步范围可以在保证
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop=new Shop();
Thread t1=new Thread(){
@Override
public void run() {
shop.buy();
}
};
Thread t2=new Thread(){
@Override
public void run() {
shop.buy();
}
};
t2.start();
}
}
class Shop{
public void buy(){
try {
Thread t=Thread.currentThread();
System.out.println(t.getName()+":正在挑衣服");
Thread.sleep(5000);
synchronized (this) {
System.out.println(t.getName() + ":正在试衣服");
Thread.currentThread();
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
静态方法加synchronized一定具有同步效果,锁的是类对象 Class的实例
当静态方法加了synchronized,已经和对象无关了,无论你创建了多少个对象,
必定会有同步效果
public class SyncDemo3 {
public static void main(String[] args) {
Foo f1=new Foo();
Foo f2=new Foo();
Thread t1=new Thread(){
@Override
public void run() {
//Foo.dosome();
f1.dosome();
}
};
Thread t2=new Thread(){
@Override
public void run() {
//Foo.dosome();
f2.dosome();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized static void dosome(){
Thread t=Thread.currentThread();
System.out.println(t.getName()+":正在运行dosome方法");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getName()+":运行dosome方法完毕");
}
}
synchronized的另外一种作用:
synchronized称为同步锁,还可以帮助维护一种关系,互斥关系
有你没我,有我没你.比如有A方法和B方法,我们希望出现的情况是:
调用A方法就不能调用B,调用B方法就不能调用A,就是不能同时干两个方法
比如咽下去东西和喘气就是互斥的,不能同时干
如何在程序里实现互斥关系,希望互斥的代码用synchronized锁上,然后互斥的代码使用的锁是同一个,他们之间就是互斥关系
互斥锁:
当多个代码片段被synchronized块修饰后,这些同步块的同步监听器对象又是同一个时,这些代码片段就是互斥的.多个线程不能同时在这些方法中运行.
注意:不一定是两个方法,可以是两个代码块,只要锁的对象是同一个,就有互斥效果
public class SyncDemo4 {
public static void main(String[] args) {
Boo boo=new Boo();
Thread t1=new Thread(){
@Override
public void run() {
boo.methodA();
}
};
Thread t2=new Thread(){
@Override
public void run() {
boo.methodA();
}
};
t1.start();
t2.start();
}
}
class Boo{
public synchronized void methodA(){
Thread t=Thread.currentThread();
try {
System.out.println(t.getName()+":正在运行A方法");
Thread.sleep(5000);
System.out.println(t.getName()+":运行A方法结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
Thread t=Thread.currentThread();
try {
System.out.println(t.getName()+":正在运行B方法");
Thread.sleep(5000);
System.out.println(t.getName()+":运行B方法结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
关于Thread的静态方法 static void yield()
该方法用于使当前线程主动让出当次CPU时间片回到Runable状态,等待分配时间片
上面这图就是线程运行状态
线程.start()之后进入可运行状态(并不是立刻执行,而是交给线程调度器,进入可运行状态,线程调度器把CPU时间分配给谁,谁先执行,不一定说先start的就一定先执行),当获得CPU时间之后开始运行,CPU时间片用完了,回到可运行状态,等待线程调度分配CPU时间片,任务完成(run方法执行完毕)等待被回收.线程调度器无序分配CPU时间片,只能说尽量分配获得CPU时间片的时间均匀,但不保证
线程有三种阻塞状态,分别是IO阻塞 Sleep阻塞 Wait阻塞 阻塞的直观表现就是卡了
IO阻塞常见的比如说Scanner Sleep阻塞比如说Thread.sleep
执行某些特殊代码,让线程进入阻塞状态