什么是并行和并发?

并发和并行是即相似又有区别:(微观)

并行:指两个或多个事件在同一时刻发生; 强调的是时间点.

并发:指两个或多个事件在同一时间段内发生; 强调的是时间段.

  

  进程和线程的区别?

进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。 因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器,程序员是控制不了的。我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性。

 

  怎么创建进程?

创建进程的方式有两种,我以windows上的记事本为例:



package com.StadyJava.day14;

import java.io.IOException;

public class ProcessDemo {
    public static void main(String[] args) throws Exception {
        //方法1:使用Runtime
        Runtime runtime=Runtime.getRuntime();
        runtime.exec("notepad");

        //方法2:ProcessBuild
        ProcessBuilder processBuilder=new ProcessBuilder("notepad");
        processBuilder.start();
    }
}



运行代码,此时会生成两个记事本。

我也尝试过启动其他的程序,但是计算器不认识,只有notepad这种计算机自带的才认识。

如果想要启动其他的程序,只能写上绝对路径



ProcessBuilder processBuilder=new ProcessBuilder("E:\\shuyunquan\\TIM\\Bin\\TIM.exe");
processBuilder.start();



这样我测试了,是可以的,但是没意思,这样你写了程序发布了也搞怪不了,别人电脑上的路径和你不一样。。。

接下来讲解线程。

 

 

下图是线程的一些常用的方法 

删除java文件提示另一程中打开 删除java进程_创建线程

 

创建进程的两个方法已经知道了,接下来看看创建线程的两个方法

创建线程方法1:使用继承Thread类



package com.StadyJava.day14Thread;

import  java.lang.Thread;

class Music extends Thread{
    
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("听音乐"+i);
        }
    }
}

public class MusicThread {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("打游戏"+i);
            if (i == 10) {
                Music music=new Music();
                music.start();
            }
        }
    }
}



注意,继承了Thread类之后要重写run方法,而且在调用的时候,只能使用start方法,不能调用run方法,切记!

输出的结果是在打游戏10之后,下面的打游戏和听音乐都是随机出现,因为主线程main和线程music在抢占资源,谁抢到谁执行,所以输出的结果是随机的

(注意!我使用Idea输出的结果是顺序的,不是随机的。我同学和我一样的代码使用Eclipse结果是随机的,我自己复制到Eclipse之后偶尔随机,Idea是死都不随机,这个我解决不了,希望以后知道原因或者有人告诉我)

 

创建线程的方法2:实现Runnable接口

其实Thread类也是实现了Runnable接口的,所以这个方法我感觉是本源??



package com.StadyJava.day14Thread;

import  java.lang.Runnable;

class MusicRun implements Runnable{

    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("听音乐"+i);
        }
    }
}

public class MusicRunnable {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("打游戏"+i);
            if (i == 10) {
                Runnable music=new MusicRun();
                Thread thread=new Thread(music);
                thread.start();
            }
        }
    }
}



没什么特别的,就是生成一个runnable的对象,然后Thread对象加载进这个Runnable对象,最后start方法调用一下就完事了。这个结果我的Idea依然是显示的不符合我的期望的。

 

 

上面两种创建线程的方式是最常用的方式,一般也就足够了,下面介绍一下不怎么常用的

创建线程的方法3:匿名内部类创建线程

匿名内部类的格式:new 接口(){}  应该是这样的,待补充

其实匿名内部类创建线程还是使用上面的两种方式,只不过那个Music类我不需要去定义了,这就是匿名内部类,看代码吧



package com.StadyJava.day14Thread;

import  java.lang.Runnable;

class MusicRun implements Runnable{

    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("听音乐"+i);
        }
    }
}

public class MusicRunnable {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("打游戏"+i);
            if (i == 10) {

                //匿名内部类的形式1,使用接口
              new Thread(new Runnable() {

                  public void run() {
                      for (int i = 0; i < 50; i++) {
                          System.out.println("听音乐"+i);
                      }
                  }
              }).start();

              //匿名内部类的形式2,使用继承类
                new Thread(){
                public void run() {
                    for (int i = 0; i < 50; i++) {
                        System.out.println("听音乐"+i);
                    }
                }
            }.start();
            }
        }
    }
}



这回,输出的结果总算是符合我的预期了,可能是线程变成了3个,抢占资源激烈了些。。。

 

学了两种常见的创建线程的方法之后,他们之间有什么区别呢?

Thread创建线程和Runnable创建线程的区别

 我写个例子,吃苹果大赛,3个人参加比赛,先使用继承Thread类创建线程的方式,代码如下:



package com.StadyJava.day14;


class Person extends java.lang.Thread{

    private int num=50;
    public Person(String name){
        super(name);
    }

    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num >0) {
                System.out.println(super.getName()+"吃了编号为"+num--+"的苹果");
            }

        }
    }
}

public class EatAppleThread {
    public static void main(String[] args) {
        //创建3个人,去参加吃苹果大赛
        new Person("许嵩").start();
        new Person("林俊杰").start();
        new Person("蜀云泉").start();

    }
}



 这个输出的结果,就是许嵩,林俊杰,蜀云泉每个人都吃了50 个?。原因是因为我new了三个对象,没个对象都有num变量,他们之间互不干扰,如下图所示:

 

删除java文件提示另一程中打开 删除java进程_System_02

 

这样很不好,我的吃苹果大赛是总共50个?,你们3个人来吃就完事了。我们看看实现Runnable接口创建线程的方式是怎么样的,代码如下:



package com.StadyJava.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num >0) {
                //返回当前线程的引用Thread.currentThread(),再获取名字
                System.out.println(Thread.currentThread().getName()+"吃了编号为"+num--+"的苹果");
            }

        }
    }
}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //创建3个人,去参加吃苹果大赛
        Apple apple=new Apple();
        new Thread(apple,"许嵩").start();
        new Thread(apple,"林俊杰").start();
        new Thread(apple,"蜀云泉").start();

    }
}



 这次因为我传入的都是apple这个对象,这一个对象有50个?,创建了3个线程,这次输出的结果是OK的,原因如下图所示,3个线程共用了一个苹果对象。

 

删除java文件提示另一程中打开 删除java进程_创建线程_03

 

 从这个吃苹果比赛的例子中可以总结一下继承Thread类创建线程的方式和实现Runnable接口创建线程的方式的区别:

继承Thread类创建线程方式:

  1. Java中类是单继承的,如果使用了继承Thread类创建线程,那么就 不能再有其他父类了,这是一个限制
  2. 从操作上来说,继承Thread类的方式更简单,获取线程名称也简单,直接getName就好了。操作简单,这是优点
  3. 从多线程共享资源的方面分析,继承方式不行,直接3个人,每个人50个苹果,没有实现共享,这是缺点

 

实现Runnable接口创建线程方式:

  1. 设计优雅,这是优点
  2. 从操作上分析,实现接口方式有点复杂,获取线程名称的时候,必须使用Thread.currentThread()来获取当前线程的引用
  3. 从多线程共享资源的方面上,实现接口方式可以做到共享资源,3个人去吃50个苹果,共享资源。这是优点

 

 

综合上面的区别对比,我们的这个比赛。看来只能使用实现Runnable接口创建线程的方式来实现了。推荐以后创建线程,都使用实现Runnable接口的方式。

 

 

 线程安全问题

 拿上面写的实现接口创建线程的吃苹果比赛为例,这个是存在线程安全的,我们可以写一个线程休眠来看看,这样更容易观察。

需要说明,Thread.sleep线程休眠,需要使用try catch来扑捉异常,不能使用throw抛出,因为Runnable接口里面的run方法本身就没有throw的写法



package com.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            if (num >0) {
                //线程休眠,必须使用try catch扑捉异常
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //返回当前线程的引用Thread.currentThread(),再获取名字
                System.out.println(Thread.currentThread().getName()+"吃了编号为"+num--+"的苹果");
            }

        }
    }
}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //创建3个人,去参加吃苹果大赛
        Apple apple=new Apple();
        new Thread(apple,"许嵩").start();
        new Thread(apple,"林俊杰").start();
        new Thread(apple,"蜀云泉").start();

    }
}



我就加了一个线程休眠,我们现在来看看输出的结果是什么样的

删除java文件提示另一程中打开 删除java进程_创建线程_04

居然有0,还有-1 不是已经写了if(num>0)吗?出现这种情况的原因是,许嵩,林俊杰,蜀云泉这三个线程都在num>0的时候,例如num=1的时候,他们仨都拿到了资源,都可以去执行run方法

这个时候就会出现这种情况。通俗一点讲许嵩拿到了最后一个苹果,但是没吃,林俊杰和蜀云泉来抢。由于这个苹果不具备独占性,所以最后一个苹果被许嵩,林俊杰,蜀云泉都咬了一口。他们都宣称自己吃了苹果,所以就会出现0和-1的情况。这样显然是不允许的。这就是

多线程并发的访问一个资源产生的安全问题

 要想解决这个问题,就必须要保证苹果的数量减少必须保证同步,许嵩拿了最后一个苹果,这个时候苹果数量同步为0,剩下的人不能再抢了。

许嵩这个线程在操作的时候,林俊杰和蜀云泉只能等着。等许嵩操作完了。许嵩,林俊杰和蜀云泉才有机会去重新抢资源。

 意思是这样,方法有3种

 

方法1.同步代码块

方法2.同步方法

方法3.锁机制(Lock)

 

 方法1:同步代码块

语法:

 synchronized(同步锁){

  需要同步操作的代码

}

同步锁:为了保证每个线程都能单独的执行操作,java线程同步的机制。同步锁也叫

同步监听对象/同步锁/同步监听器/互斥锁

 这些都是别名,就像茴香豆的“茴”字有几种写法一样,都是别名。

Java程序中的任何对象都可以作为同步监听对象,但是我们一般把多个线程同时访问的共享资源作为同步监听对象。监听其它的单独的对象有啥意义。注意,在任何时候,最多运行一个线程拥有同步锁。

代码如下:



package com.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            //方法1:同步代码块
            //由于我多个线程共享的是Apple对象,所以同步锁就是this,当前类的对象。不能使用num变量,因为num变量一直在变化
        synchronized (this) {
            if (num > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //返回当前线程的引用Thread.currentThread(),再获取名字
                System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
            }

        }

        }
    }
}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //创建3个人,去参加吃苹果大赛
        Apple apple = new Apple();
        new Thread(apple, "许嵩").start();
        new Thread(apple, "林俊杰").start();
        new Thread(apple, "蜀云泉").start();


    }
}



运行结果如下:

删除java文件提示另一程中打开 删除java进程_创建线程_05

这下不会出现抢苹果事件了,也不会出现数量为0和-1的情况了。

 

 方法2:同步方法

synchronize修饰的方法就是同步方法,保证当前线程执行的时候,其它线程只能等待

语法:

synchronize public void Dowork(){

  执行操作

}

同步锁:对于非static方法,同步锁就是this,对于静态方法,同步锁就是当前方法所在类的字节码对象(类.class)

注意!不能使用synchronize修饰run方法。



package com.day14;


class Apple implements Runnable{

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            eat();
        }
    }

    //方法2:同步方法,直接用synchronized修饰方法
    synchronized private void eat(){
        if (num > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //返回当前线程的引用Thread.currentThread(),再获取名字
            System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
        }
    }

}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //创建3个人,去参加吃苹果大赛
        Apple apple = new Apple();
        new Thread(apple, "许嵩").start();
        new Thread(apple, "林俊杰").start();
        new Thread(apple, "蜀云泉").start();


    }
}



运行结果也是OK的

删除java文件提示另一程中打开 删除java进程_创建线程_06

 

 或许有人会想,既然方法加了个synchronize就线程安全了,那我把所有的方法都加上synchronize不就得了。答案是不行滴

synchronize的优缺点:

优点:保证了多线程并发访问时的同步操作,避免了多线程操作的安全问题。

缺点:使用synchronize同步方法/同步代码块会导致性能降低。

 

方法3:锁机制(Lock)

锁机制用到的是Lock这个接口,当然我们在写代码的时候使用的是Lock接口的一个实现子类,叫ReentrantLock

语法:



private final Lock lock=new ReentrantLock();

try{

    线程操作代码

}

catch (InterruptedException e) {

    e.printStackTrace();
}

finally {
    lock.unlock();
}



还是上面的吃苹果比赛,使用锁机制的代码如下:



package com.day14;


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Apple implements Runnable{

    //创建一个Lock接口的实现子类对象。ReentrantLock是Lock接口的一个子类的实现。
    private final Lock lock=new ReentrantLock();

    private int num=50;

    public void run() {
        for (int i = 0; i < 50; i++) {
            eat();
        }
    }

    //方法3:同步锁(Lock)的方式,这个方式和方法2的同步方式很类似。
     private void eat(){
        //进入方法首先上锁
        lock.lock();
        if (num > 0) {
            try {
                //返回当前线程的引用Thread.currentThread(),再获取名字
                System.out.println(Thread.currentThread().getName() + "吃了编号为" + num-- + "的苹果");
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                //结束后,记得释放锁
                lock.unlock();
            }

        }
    }

}

public class EatAppleRunnable {
    public static void main(String[] args) {
        //创建3个人,去参加吃苹果大赛
        Apple apple = new Apple();
        new Thread(apple, "许嵩").start();
        new Thread(apple, "林俊杰").start();
        new Thread(apple, "蜀云泉").start();


    }
}



这个锁机制是不是和synchronize同步方法很相像,只不过同步方法是synchronize修饰的方法,而锁机制是在方法里面上锁,释放锁。

锁机制和同步代码块/同步方法比,范围更广泛。也就是说锁机制包括了同步代码块和方法,而且范围更大,更加面向对象。上锁,释放锁都自己来写,还有创建实现Lock接口的对象。

 

 

用线程实现生产者消费者问题

 

现在来讲一个生产者和消费者的问题,讲定生产者生产一些东西,放到分享池中,然后消费者去分享池中消费东西,大概就是下图那样的展示:

 

删除java文件提示另一程中打开 删除java进程_System_07

这就是我们的生产者和消费者的模型,我们要根据这个写代码。记得加上上面学习的同步锁知识。

 

分享池代码:



package com.day15;

public class ShareResource {

    private String name;
    private String sex;
    private int num;
    private boolean isEmpty=true;


    synchronized public void push(String name,String sex,int num) {
        try {
            while (!isEmpty) {//如果不为空的时候,生产者线程就等待
                    this.wait();
            }
            //开始生产,生产过后,要isEmpty变成为空,然后唤醒其它的线程
            this.name=name;
            this.sex=sex;
            this.num=num;
            isEmpty=false;
            this.notifyAll();
        } catch (InterruptedException e) {
                e.printStackTrace();
        }


    }

    synchronized public void popup(){
        try {
            while (isEmpty) {
                this.wait();
            }
            System.out.println(this.name + this.sex + this.num);
            isEmpty=true;
            this.notifyAll();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}



 

生产者代码:



package com.day15;

//生产者线程
public class Producer implements Runnable{

    //分享池对象
    public ShareResource shareResource=null;

    public Producer(ShareResource shareResource) {
        this.shareResource=shareResource;
    }

    @Override
    synchronized public void run() {
        for (int i = 0; i <50 ; i++) {

            if (i % 2==0) {
                shareResource.push("许嵩","男",i);
            }
            else{
                shareResource.push("梦中人","女",i);
            }

        }
    }
}



 

消费者代码:



package com.day15;

public class Consumer implements Runnable {

    //分享池对象
    public ShareResource shareResource=null;

    public Consumer(ShareResource shareResource) {
        this.shareResource=shareResource;
    }

    @Override
    synchronized public void run() {
        for (int i = 0; i <50 ; i++) {
           shareResource.popup();
        }
    }
}



 

 

执行测试代码:



package com.day15;

//测试类
public class RunTest {
    public static void main(String[] args) {
        //创建生产者和消费者共同的资源对象
        ShareResource resource=new ShareResource();

        //启动生产者线程
        new Thread(new Producer(resource)).start();
        new Thread(new Producer(resource)).start();
        //启动消费者线程
        new Thread(new Consumer(resource)).start();
        new Thread(new Consumer(resource)).start();

    }
}



 

代码就是这些,我实行的是生产者生产一个东西,消费者就去消费,我这里是直接打印出来了。生产者生产之后就去wait休息,等到东西没了才开始干活。这里我们学到了两个新的方法

1.wait()方法:线程休眠,除了被唤醒,否则就会一直睡觉休息,和睡美人是差不多了,没有人唤醒是不会苏醒的。wait方法里面可以加参数,毫秒,就是没人唤醒的话就自己醒(好惨啊...)

2.notifyAll()方法:唤醒除自己以外所有的线程,还有一个方法是notify(),唤醒随机的一个线程

最后我们看看输出的结果:

删除java文件提示另一程中打开 删除java进程_Apple_08

 

通过上面同步方法synchronized和线程的wait方法,notify方法很好的完成了生产者和消费者的问题。这里需要说明的是,wait方法和notify方法都必须需要同步锁,那么,我现在想用Lock锁机制去完成生产者消费者问题,那该怎么办呢?

 

锁机制Lock完成生产者和消费者问题

锁机制Lock是无法使用wait和notify方法的,那使用锁机制的线程之间怎么进行通信呢?

从Java5开始就为锁机制的线程提供了Condition接口,用于线程直接的通信,主要使用的方法有:

1.await()方法,相当于wait()方法,线程睡眠

2.signal()方法,相当于notify()方法,随机的唤醒任意一个线程

3.signalAll()方法,相当于notifyAll()方法,唤醒除了自己以外的所有的线程

 

然后代码其实就修改了分享池的代码,放出来看一下:



package com.StadyJava.day15;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ShareResource {

    private String name;
    private String sex;
    private int num;
    private boolean isEmpty=true;
    private final Lock lock=new ReentrantLock();
    //创建lock锁机制的Condition对象
    private Condition condition=lock.newCondition();

    public void push(String name,String sex,int num) {
        lock.lock();

        try {
            while (!isEmpty) {
                condition.await();
            }
            //开始生产,生产过后,要isEmpty变成为空,然后唤醒其它的线程
            this.name=name;
            this.sex=sex;
            this.num=num;
            isEmpty=false;
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }


    }

    synchronized public void popup(){
        lock.lock();

        try {
            while (isEmpty) {
                condition.await();
            }
            System.out.println(this.name + this.sex + this.num);
            isEmpty=true;
            condition.signalAll();
        }
        catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}



其实没什么大变化,就是方法换了名字而已。

 

 

线程的死锁

死锁就是两个线程互相在等待对方释放锁,死锁的现象一旦出现,是解决不了的,所以死锁现象只能避免,不能解决。

 关于死锁,有一个超级经典的例子,就是哲学家就餐问题

删除java文件提示另一程中打开 删除java进程_Apple_09

 

线程的6种状态

 如下图所示,Java线程有6种状态,现在来介绍一下各种状态。

删除java文件提示另一程中打开 删除java进程_System_10

 

删除java文件提示另一程中打开 删除java进程_创建线程_11

删除java文件提示另一程中打开 删除java进程_创建线程_12

 

 

线程的核心内库(几个重要的方法)

线程有几个很重要的方法需要讲一下

1.线程睡眠,sleep方法

这个方法是不是和上面讲的wait方法很像?其实不一样,wait睡觉之后,同步锁就释放了。sleep睡觉的时候,同步锁是紧紧的抓住不松手的

这个方法大部分用来模拟网络延迟,因为你刷新网页的时候不是会转圈圈吗,可以模拟这个。代码中也有很多地方用这个来写东西,例如我想模拟一个定时炸弹,我可以这样写代码:



package com.StadyJava.day15;

public class ThreadDemo {
    public static void main(String[] args)  {
        for (int i = 10; i > 0; i--) {
            System.out.println("离爆炸还有"+i+"秒");
        }
        System.out.println("嘣,爆炸啦");
    }
}



这样写,没问题吧,有问题的,我想定时,但是这个一运行,结果全出来了,不是想要的结果,所以我们可以加一个睡眠1秒来实现,代码如下:



package com.StadyJava.day15;

public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        for (int i = 10; i > 0; i--) {
            System.out.println("离爆炸还有"+i+"秒");
            Thread.sleep(1000);
        }
        System.out.println("嘣,爆炸啦");
    }
}



记得,sleep方法是要抛出异常的

这样就可以了,实现了倒计时的效果。还有几个例子,例如坦克发射子弹,那这个子弹肯定是不断位移的,我们设置好之后,子弹可能瞬间就打出去了,你根本看不到子弹的运行轨迹。为了仔细的观察,或者实现子弹慢速射击的一个要求,我们也可以去sleep一下。还有NBA投篮游戏也是,都可以去试试。

 

2.联合线程,join方法

线程的join方法表示一个线程等待另一个线程完成后才执行。就是说把当前线程和当前线程所在的线程联合成一个线程。join方法被调用之后,线程对象处于阻塞状态。

适用于A线程需要等到B线程执行完毕,再拿B线程的结果再继续运行A线程。写个代码



package com.StadyJava.day15;

class Join extends Thread{
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("join"+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        Join joinThread=new Join();
        for (int i = 0; i < 50; i++) {
            System.out.println("main"+i);

            if (i == 1) {
                joinThread.start();
            }
            else if (i == 20) {
                joinThread.join();
            }
        }
    }
}



运行结果如下:

删除java文件提示另一程中打开 删除java进程_Apple_13

删除java文件提示另一程中打开 删除java进程_创建线程_14

Idea副线程想和主线程抢资源真难。。。。运行了好几次才抢到。。。我们可以看到在主线程等于1的时候,两个线程开始抢占资源打印输出。在主线程为20的时候,joinThread线程就调用了join方法,这个时候他们俩就变成了联合线程,主线程main开始进入阻塞状态,必须等到joinThread线程执行完毕才可以执行。

 

3.后台线程

在后台运行,其目的是为其他线程提供服务,也称为“守护线程。
JVM的垃圾回收器就是典型的后台线程。
特点:若所有的前台线程都死亡,后台线程自动死亡。
测试线程对象是否为后台线程:使用thread.isDaemon()。
前台线程创建的线程默认是前台线程,并且当且仅当创建线程是后台线程时,新线程才是后台线程。
设置后台线程:thread.setDaemon(true),该方法必须在start方法调用前,否则出现IllegalThreadStateException异常。

 


package com.StadyJava.day15;

class Daemon extends Thread{
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(super.getName()+"-"+super.isDaemon()+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 50; i++) {
            System.out.println("main"+i);
            if (i == 1) {
                Daemon daemon=new Daemon();
                daemon.setDaemon(true);//设置线程为后台线程,必须先设置后台线程才能start开启,先start开启再设置会报错
                daemon.start();
                Thread.sleep(10);
            }
        }
    }
}


就一个判断是否是后台线程的方法 isDaemon() 和一个设置线程为后台线程的方法 setDaemon(true)

如果没有了前台线程,后台线程会死亡。

 

4.线程的优先级

优先级有两个方法

1.setPriority() 获取当前线程的优先级,main线程的优先级是5,默认的都是5

2.setPriority() 设置线程的优先级,不同的操作系统是不一样的,Linux和Windows都不一样。但是有3个数字是统一的,分别是1,5,10  1是最低,10是最高。所以用这3个数字就可以了。

 

注意:并不是优先级高的线程一定先执行,而是说这个线程有更多的机会去执行。执行的几率大了一些。

下面看一个代码


package com.StadyJava.day15;

class PriorityThread extends Thread{

    public PriorityThread (String name){
        super(name);
    }

    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("我是"+getName()+i);
        }
    }
}

public class Priority {
    public static void main(String[] args) {
        PriorityThread priorityThread1=new PriorityThread("优先级高的线程");
        PriorityThread priorityThread2=new PriorityThread("优先级低的线程");
        //priorityThread1.setPriority(Thread.MIN_PRIORITY);也可以使用这个,但是数字更简单
        priorityThread1.setPriority(10);
        priorityThread2.setPriority(5);
        priorityThread2.start();
        priorityThread1.start();
    }
}


删除java文件提示另一程中打开 删除java进程_Apple_15

看结果也知道,优先级高并不是一定先执行。

 

5.线程的礼让

古有孔融让梨,那么线程也有一个礼让的方法叫 yield方法。这个方法比较特别,他把自己执行的机会让给那些优先级高的线程,提出这个请求给调度器CPU,但是CPU可以同意这个请求也可以无视这个请求

这个yield方法和sleep方法的区别如下:

1.都可以使得当前处于运行状态的线程放弃执行的机会,让给其它线程

2.sleep方法会让给其它线程,随机的让。yield方法会让给那些优先级高的线程。

3.调用sleep方法后,线程会进入计时等待状态。调用yield方法后,线程会进入就绪状态。

 

这个yield方法一般是不用的。不使用。。。。。在调试和测试线程的时候,可能会重现多线程的错误,可能。。。。所以还是了解一下就好吧

 

6.线程的定时器和线程组

定时器,就是定时去执行啦,直接看代码


package com.StadyJava.day15;
import java.util.Timer;
import java.util.TimerTask;

class Vae extends TimerTask {
    public void run() {
        System.out.println("大家好,我是Vae");
    }
}

public class TimerDemo {
    public static void main(String[] args) {
          new Timer().schedule(new Vae(),3000,1000);
    }
}


注意,我的Vae类是继承的TimeTask类,不是Thread类。定时器的方法就是schedule方法,第一个参数就是TimeTask对象,第二个参数就是第几秒出现执行,第三个参数就是间隔多少秒执行一次。

 

线程组,就是多个相同的线程在一个组里面,就像老师讲课,给A同学讲一遍,再给B同学讲一遍,再给C同学讲一遍。。。。这样太麻烦。直接让ABC三个同学都过来,一起听就完事了。这就是线程组的意义

线程组的特点:

1.如果线程A创建了线程B,那么B和A一定是一组的。

2.一个线程一旦加入了线程组,一辈子就是这个组的,一天是不良人,一辈子都是不良人。

当Java程序运行时,JVM会创建一个main线程组,默认所有的线程都是main线程组的

 

线程的知识差不多就这些了,下面来问几个问题,看看都能不能回答

1.synchronized和Lock的区别是什么?

  synchronized是一个修饰符,Lock是一个类。这是最本质的区别。

2.为什么wait方法和notify方法/notifyAll方法不在Thread类中,而在Object类中?

简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象。