进程:
线程:
线程和进程的关系:一个进程由多个线程支撑运行,
分时调度
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调用。
CPU使用抢占式调度模式在多个线程间进行着高速的切换,对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU在多个线程间切换的速度相对于我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能提高程序的运行效率,让CPU的使用率更高。
同步:排队执行,效率低但是安全。
异步:同时执行,效率高但是数据不安全。
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
public static void main(String[] args){
MyThread m = new MyThread();
m.start();
for(int i=0;i<10;i++){
System.out.println("汗滴禾下土"+i);
}
}
class MyThread extends Thread{
//run方法就是线程要执行的任务方法
@Override
public void run(){
//这里的代码 就是一条新的执行路径
//这个执行路径的触发方式,不是调用run方法,而是通过thread对象的start来启动任务
for(int i=0;i<10;i++){
System.out.println("锄禾日当午"+i);
}
}
}
//结果主程序和线程的输出语句同时执行。
执行流程图如下所示:
注意:每个线程都拥有自己的栈空间,共用一份堆内存。
public static void main(String[] args){
//实现Runnable
//1.创建一个任务对象
MyRunnable r = new MyRunnable();
//2.创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
//3.执行这个线程
t.start();
for(int i=0;i<10;i++){
System.out.println("疑是地上霜"+i);
}
}
//这是用于给线程执行的任务
class MyRunnable implements Runnable{
@Override
public void run(){
//线程的任务
for(int i=0;i<10;i++){
System.out.println("床前明月光"+i);
}
}
}
实现Runnable与继承Thread相比有如下优势:
public static void main(String[] args){
new Thread(){
//通过匿名内部类的方式创建一个线程
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println("一二三四五")
}
}
}.start();
for(int i=0;i<10;i++){
System.out.println("六七八九十"+i);
}
}
获取线程名称与设置线程名称:
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());//输出:main
new Thread(new MyRunnable()).start();//输出:Thread-0
new Thread(new MyRunnable()).start();//输出:Thread-1
new Thread(new MyRunnable()).start();//输出:Thread-2
new Thread(new MyRunnable(),"锄禾日当午").start;//输出:锄禾日当午 注意,这个方法是给线程命名
Thread t = new Thread(new MyRunnable());//另一种命名的方法
t.setName("锄禾日当午");
t.start();
}
static class MyRunnable implements Runnable{
@Override
public void run(){
Sysmte.out.println(Thread.currentThread().getName());
}
}
线程的休眠:
public static void main(String[] args){
for(int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000);//每隔1秒输出一个数
}
}
线程阻塞不光指的是线程休眠,线程是一条执行路径,比如说:线程在执行代码时,它的执行路径有100行,从第一行到第一百行是它的整体执行路径,这100行中某10行可能是为了读取某个文件。这文件读取可能耗时1秒钟,那么这1秒钟也是阻塞的,停在那读文件,后面读完才会执行。可以把阻塞理解成所有消耗时间的操作。就像上面的读取文件,它会使线程等待在那个位置,直到读取完毕,不会往下执行,除非文件读完。就像控制台等待用户输入,用户不输入,程序就不会继续往下执行。这就是线程阻塞,我们也称其为耗时操作。
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。线程启动过程中可能会使用N多资源,也涉及到N多需要释放的资源,那么这时如果由外部直接把线程掐死,那么极有可能导致线程占用的资源来不及释放而一直占用,从而产生内存垃圾,这个垃圾是你不能回收的垃圾。也有可能占用一些硬件资源,导致硬件资源来不及释放,其他软件没有办法再去使用。
线程中断不用stop方法,因为此方法是直接把线程掐死。我们应该使用interrupt方法,通过该方法给线程打上一个标记,在特殊情况下线程会检查iterrupt的状态,如果检查到就是报异常,就是告诉线程该自杀了,线程在捕获异常语句块里调用return方法就会中断线程。
public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
t1.start();
for(int i=1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
//给线程t1添加中断标记
t1.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("发现了中断标记,我们这个线程自杀");
return;
}
}
}
}
线程:分为守护线程和用户线程;
用户线程:当一个进程不包含任何存活的用户线程时,进程结束;
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
t1.setDaemon(true);//设置t1为守护线程
t1.start();
for(int i=1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
//线程不安全
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
@Override
public void run(){
wile(count>0){
System.out.println("正在准备卖票");
try{
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
}
}
}//三个线程会有可能会同时进入循环,同时调用count,从而造成count<0的情况发生
线程同步有三种方式,三种加锁的方式。
格式:synchronized(锁对象){ }
Java中任何对象都可以作为锁对象存在。多个线程应该抢一把锁,而不是一个线程一把锁。这样就不会出现同时操作造成数据混乱的情况了,因为都是排队执行代码块里的代码。
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
Object o = new Object();
@Override
public void run(){
//Object o = new Object();//注意:如果锁声明在这里,那三个线程调用run方法都会重新定义一把锁,那就还是同时执行,不是排队执行了。
while(true){
synchronized(o){
//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;
}
}
}
}
}
如果不是静态的(static)的同步方法,它的锁对象为this;如果是静态的同步方法,它的锁对象为类名.class,比如下方的例子中,如果是静态的,则锁对象为Ticket.class
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
//new Thread(new Ticket()).start()//这里不是同一把锁,所以是同时执行(异步),不是排队执行(同步)了。
}
static class Ticket implements Runnable{
private int count = 10;
@Override
public void run(){
//synchronized(){ }//如果这里同步代码块和同步方法同时用了一把锁,那么一个线程执行同步方法活同步代码块时,其他线程对同步方法和同步代码块都不能执行。就好比商店里的试衣间,大门和试衣间的门用的是同一把锁,那么只要有一个人(线程)在使用其中的一个试衣间,那么其他人连大门都进不去,只能等待里面的人用完试衣间把锁打开。
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;
}
}
}
同步代码块和同步方法都属于隐式锁。
显示锁Lock子类ReentrantLock
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).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();
}
}
}
公平锁就是排队时谁先来,谁就先用这个锁。
非公平锁就是排队时抢着来,谁先抢到谁就用,上述Java线程同步的三种方式就是非公平锁。
在显示锁中定义时,构造函数的第一个参数设为true就是公平锁,false就是不公平锁,例如:
private Lock l = new ReentrantLock(true)//fair参数设为true,就表示公平锁
两个线程相互等待对方释放锁就造成了死锁。比如某商城有A、B两个试衣间,客户甲进入了A试衣间,客户乙进入了B试衣间,但是客户甲觉得A试衣间不好用,想换到B试衣间,所以客户甲在A试衣间中等客户乙用完B试衣间出来。巧的是客户已也觉得B试衣间不好用,也想换到A试衣间,所以客户乙也再等客户甲用完A试衣间出来。双方都不知道对方都在等自己用完试衣间出来,而造成的一种相互等待的状态,就叫做死锁。
public static void main(String[] args){
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
//在这里主线程和MyThread线程同时执行,主线程拿到了罪犯类里的同步方法锁,只有主线程能调用罪犯类里的同步方法。而MyThread线程拿到了警察类里的同步方法的锁,只有MyThread线程能调用警察类里的同步方法。这样,主线程就需要等待MyThread线程释放所占用的锁,而MyThread线程也同样需要等待主线程释放所占用的锁,这样就造成了两线程相互等待的状态,从而形成了死锁。
//如果想要两个线程不造成死锁,那么就需要在其中一个线程还没有抢占到锁时,程序就执行完毕。这样才不会造成死锁。
//避免死锁的方法就是,你已经调用了一个同步方法,就不要再继续调用其他同步方法了,避免造成死锁。
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c,Police p){
this.c = c;
this.p = p;
}
@Override
public void run(){
p.say(c);
}
}
//罪犯
static class Culprit{
public synchronized void say(Police p){
//说
System.out.println("罪犯:你放了我,我放了人质");
p.response();
}
public synchronized void response(){
//回应
System.out.println("罪犯被放了,罪犯也放了人质");
}
}
//警察
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我放了你");//警察说了话,需要罪犯的回应
c.response();
}
public synchronized void response(){
System.out.println("警察救到了人质,但是罪犯跑了")
}
}
出现死锁的情况:
没有出现死锁的情况:
Object类里的notify()、notifyAll()、wait()方法。
生产者与消费者
就像厨师和服务员,厨师在做饭的时候,服务员是在休息的,等厨师做好了,服务员被唤醒,而厨师进入休息状态。
确保生产者在生产时消费者没有进行消费。
public static void main(String[] args){
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f){
this.f = f;
}
@Override
public void run(){
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
} else {
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务员
static class Waiter extends Thread{
private Food f;
public Waiter(Food f){
this.f = f;
}
@Override
public void run(){
for(int i=0;i<100;i++){
try{
Thread.sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
public void setNameAndTaste(String name,String taste){
this.name = name;
try{
Thread.sleep(100);
} catch(InterruptedException e){
e.printStackTrace();
}
this.taste = taste;
}
public void get(){
System.out.println("服务员端走的菜的名称是:"+name+",味道:"+taste);
}
}
造成上述错乱的原因是厨师做食物Food的时候刚给食物赋名字name,就被服务员端走了,而味道taste有可能是上一个对象遗留的。这就是两个线程合作时出现的不协调现象。
而如果把食物的两个方法定义成同步方法,会造成更加错乱的现象,有可能,厨师做完了第一顿饭,有可能服务员还没来得及端,厨师就做完了第二顿饭,那么第一顿饭就没了。即厨师做饭做快了,或者服务员端菜端快了。
解决方法:厨师干活的时候,服务员歇着;厨师歇着的时候,服务员干活
static class Food{
private String name;
private String taste;
private boolean flag = true;//饭菜是否做好
public synchronized void setNameAndTaste(String name,String taste){
if(flag){
this.name = name;
try{
Thread.sleep(100);
} catch(InterruptedException e){
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();//唤醒在当前this下睡着的所有线程
try{
this.wait();//厨师睡着
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag){
System.out.println("服务员端走的菜的名称是:"+name+",味道:"+taste);
flag = true;
this.notifyAll();
try{
this.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程状态。线程可以处于以下状态之一:
NEW
RUNNABL
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
Interface Callable
有两种用法:1、和主线程一起运行,和线程一样;2、主线程等待其完成
接口定义:
public interface Callable<V> {
V call() throws Exception;
}
public interface Runnable {
public abstract void run();
}
Callable使用步骤:
//1、编写类实现Callable接口,实现call方法
class xxx implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
//2、创建FutureTask对象,并进入第一步编写的Callable对象
FutureTask<Integer> future = new FutureTask<>(Callable);
//3、通过thread启动线程
new Thread(future).start();
public static void main(String[] args){
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
Integer j = task.get();//调用get方法会导致主线程停在这里,等待MyCallable线程执行完毕
//task.cancel(true);//取消线程
System.out.println("返回值:"+j);
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//Thread.sleep(3000);
for(int i=0;i<10;i++){
try{
Thread.sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
return 100;
}
}
线程操作流程:创建线程 -> 创建任务 -> 执行任务 -> 关闭线程
其中最耗费时间的是创建线程和关闭线程。
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
特点:长度无限制。
执行流程:
public static void main(String[] args){
ExecutorService service = Executor.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());
}
});
}
特点:长度是指定的数值
执行流程:
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());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
}
特点:效果与定长线程池创建时传入数值1效果一样。
执行流程:
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());
}
});
}
执行流程:
周期性任务执行时:定时执行,当某个时机触发时,自动执行某个任务
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
定时执行
参数1. runnable类型的任务
参数2. 时长数字
参数3. 时长数字的单位
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
周期执行
参数1. runnable类型的任务
参数2. 时长数字(延迟执行的时长)
参数3. 周期时长(每次执行的间隔时间)
参数4. 时长数字的单位
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相识一笑~嘿嘿嘿");
}
},5,1,TimeUnit.SECONDS);
函数式编程思想
面向对象:创建对象调用方法解决问题。
Lambda表达式关注的是对象的方法。
//冗余的Runnable代码
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("锄禾日当午");
}
}
//改善之后的Runnable代码
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("锄禾日当午");
}
});
t.start();
}
//使用Lambda表达式改善Runnable代码
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("锄禾日当午"));//效果和上述代码一致
//Thread t = new Thread(() -> {System.out.println("锄禾日当午");});
t.start();
}
//自定义接口测试
public static void main(String[] args) {
/*print(new MyMath() {
@Override
public int sum(int x,int y) {
return x+y;
}
},100,200);*/
print((int x,int y) -> {
return x+y;
},100,200);
}
public static void print(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}
static interface MyMath {
int sum(int x,int y);
}