多线程知识小结

一、 进程与线程

1、进程与线程的概念

1.1 进程

如下图所示,我们可以认为计算机中运行的每一个.exe文件都是一个进程,拥有属于自己独立的一块内存空间,是竞争计算机资源的基本单位。

图 1-1

1.2 线程

线程是进程中的一个执行单位(控制单元),一个线程只能属于一个进程,而一个进程可以拥有多个线程。		

2、用户线程与守护线程

2.1 用户线程

通过Thread.setDaemon(false)设置为用户线程。当一个进程不包含任何存活的用户线程时,进程就会结束。

2.2 守护线程

通过Thread.setDaemon(true)设置为守护线程。顾名思义,守护线程就是用来守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
注意,线程属性的设置要在线程启动之前,否则会报IllegalThreadStateException异常。如果不设置线程属性,那么默认为用户线程。
将用户线程设置为守护线程的格式为:Thread 类的对象名.setDamon(true);

二、同步与异步,并发与并行

1、线程的同步与异步

1.1 线程同步

线程同步就是当多个线程访问同一资源时,线程之间排队执行,效率低,但是可以保证数据的安全性。

1.2 线程异步

线程异步指当多个线程访问同一资源时同时执行,效率较高,但是数据的安全性不高。

2、线程的并发与并行

2.1 并发

指两个或多个线程在**同一时段内**运行

2.2 并行

指两个或多个线程在**同一时刻**同时运行

三、 创建线程的三种方式

1、Thread

继承Thread类,重写run()方法。

步骤简介:

  1. 编写一个Thread类的子类。
  2. 重写run()方法,在run()方法中编写线程任务。
  3. 通过子类对象调用.start()方法进行启动线程。

代码示例:
1:编写子类、重写run()方法。

package com.java.demo1;

public class MyThread extends Thread {
    /**
     * run方法就是线程要执行的任务方法
     */
    @Override
    public void run() {
        //这里的代码就是一条新的执行路径
        //这个执行路径的触发方式不是调用run方法,
        // 而是通过thread对象的start()方法来启动任务
        for(int x=0; x<10; x++){
            System.out.println("很爱很爱你"+x);
        }

    }
}

2: 通过子类对象调用.start()方法进行启动线程。

package com.java.demo1;

/**
 * 实现多线程的技术一:继承MyThread类
 * 
 */
public class ThreadTest {
    public static void main(String[] args){
        //
        MyThread my = new MyThread();
        my.start();
        for(int i=0; i<10; i++){
            System.out.println("是吗是吗"+i);
        }
    }
}

2、Runnable

实现Runnable接口,重写run()方法。

步骤简介:

  1. 创建一个任务对象(实现Runnable接口)
  2. 创建一个线程并为其分配任务(Thread类)
  3. 执行这个任务(start()方法)

代码示例:
1:创建任务

package com.java.demo1;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程的任务
        for(int i=0; i<10; i++){
            System.out.println("爱你"+i);
        }
    }
}

2:创建线程并实现

package com.java.demo1;

/**
 * 实现多线程的技术二:实现Runnable类
 */
public class RunnableTest {
    public static void main(String[] args) {
        //实现Runnable
        //1.    创建一个任务对象
        MyRunnable my = new MyRunnable();
        //2.    创建一个线程,并为其分配一个任务
        Thread r = new Thread(my);
        //3.    执行这个线程
        r.start();

        for(int i=0; i<10; i++){
            System.out.println("喜欢你"+i);
        }
    }
}

3、Callable

实现Callable接口,重写call方法

上面两种方法是常用的创建线程的方法,这种相对而言不常用。Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能:Callable可以提供返回值,而Runnable不可以;call()方法可以抛出异常而run()方法不可以。

四、Thread类的常用方法

1、获取和设置线程名称

1.1 获取线程名称

方法: Thread.currentThread().getName()

public class MyThreadTest1 {

    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println("这是子线程:"+Thread.currentThread().getName());
            }
        }.start();

        System.out.println("这是主线程:"+Thread.currentThread().getName());
    }
}

1.2 设置线程名称

1.2.1 setName()方法
public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
        t1.setName("线程1");
        System.out.println("线程的名字是:"+t1.getName());
    }
1.2.2 使用构造函数设置名称
public class MyThread1 extends Thread
{
    MyThread1(String name){
        super(name);
    }

    public void run(){
        System.out.println("线程启动中");
    }
    public static void main(String[] args) {
        MyThread1 t1=new MyThread1("一个线程");
        t1.start();
        System.out.println("Thread Name: "+t1.getName());
    }
}

2、线程休眠

.sleep()方法

try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

3、线程中断

interrupt()方法

public class Demo {
        public static void main(String[] args) throws InterruptedException {
 
            //线程的中断
            //一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
            Thread t1 = new Thread(new MyRunnable());
            //把t1设置为守护线程(一定要在启动之前设置)
            t1.setDaemon(true);
            t1.start();
            for(int i=0; i<5; i++){
                            System.out.println(Thread.currentThread().getName()+":"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //给线程t1添加中断标记,当main线程运行结束时,让t1线程运行catch块中的代码
            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("发现了中断标记, 让t1线程通过return结束run方法进行自杀");
                        return;
                    }
                }
            }
        }
}

五、线程安全

当存在两个或以上线程对象共享同一资源时容易出现线程安全问题,下面将以三个不同的窗口(线程)同时卖同一种票(资源)为例子,分别使用三种不同方法解决线程安全问题。

1、同步代码块

即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
格式:
synchronized(锁对象){需要同步的代码 }
在这个案例中,我们应该把if语句上锁,即当某线程在运行if语句时,其它线程需要等待锁对象的标记消失
注意:

  1. 任何对象都可以作为锁对象存在,但是多个线程执行某同步代码时应该共用同一把锁
  2. 如果一个线程在同步代码块中休眠了,那它就不会释放锁对象
  3. 锁对象不能放在run方法中创建,因为这样每个线程都会创建新的锁对象,不能实现排队机制
public class SaveTest1 {
    public static void main(String[] args) {
        Runnable run = new MyRunnable1();

        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

        t1.start();
        t2.start();
        t3.start();
    }


    static class MyRunnable1 implements Runnable{
        //票数
        int count = 10;
        //锁对象
        Object o = new Object();

        @Override
        public void run() {

           // Object o = new Object();  注意:锁对象不能放在run方法中创建,因为这样每个线程都会创建新的锁对象,不能实现排队机制
            while(true){
                //给if语句上锁
                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;
                    }
                }

            }
        }
    }
}

2、同步方法

即被synchronized关键字修饰的方法。 被该关键字修饰的方法会自动被加上内置锁,从而实现同步。
格式: synchronized 返回值类型 方法名(){ }
在这个案例中,我们应该把if语句抽成方法,并用synchronized修饰该方法
注意:

  1. 与这个方法相比,同步代码块的方法最小可以精确到一行代码,但是这个方法最小为方法
  2. 锁对象:谁调用这个方法,谁就是锁对象,一般情况为this,如果方法被静态修饰,则锁对象为类名.class
  3. 如果同步代码块和同步方法同时存在,使用的是同一个锁对象,因此同步代码块和同步方法不能被同时运行
public class SaveTest2 {
    public static void main(String[] args) {
        Runnable run = new MyRunnable2();

        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

        t1.start();
        t2.start();
        t3.start();
    }


    static class MyRunnable2 implements Runnable{
        //票数
        int count = 10;

        @Override
        public void run() {
            while(true){
                boolean flag = sale();
                if(!flag){
                    break;
                }
             }


        }

        //把需要上锁的if语句抽成一个方法,并用synchronized修饰方法
        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;
            }
            return false;
        }
    }
}

3、显示锁

显示锁:Lock 子类:ReetrantLock
在要排队进行的代码之前通过ReetrantLock对象调用lock()方法上锁,在要排队进行的代码之后调用unlock()方法开锁

public class SaveTest3 {
    public static void main(String[] args) {
        Runnable run = new MyRunnable();

        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

        t1.start();
        t2.start();
        t3.start();
    }


    static class MyRunnable implements Runnable{
        //票数
        int count = 10;
        //显示锁
        //创建锁的时候让fair参数为true,就可以使其成为一个公平锁
        private Lock l = new ReentrantLock(true);

        @Override
        public void run() {
            while(true){
                //给if语句上锁
                //执行到这里的时候把锁锁上
                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();
            }


        }
    }
}

4、公平锁与非公平锁

公平锁:多线程按照申请锁的顺序来获取锁,不会出现争抢的情况。
非公平锁:多线程不会按照顺序获取锁,会先尝试去争抢锁,有可能后申请的线程比先申请的线程先抢到锁,在高并发情况下,可能会造成优先级反转和饥饿的现象。(例如ReentrantLock默认为非公平锁)

六、多线程通信

1、线程死锁

死锁是指多个进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象(互相挂起等待),若无外力作用,它们都将无法推进下去。

解决方法:在任何有可能导致锁产生的方法里,不要再调用另一个可能产生锁的方法
举例:绑匪和警察对话

public class Demo1 {
    public static void main(String[] args) {
        Culprit c = new Culprit();
        Police p = new Police();
        //子线程,警察说话,让罪犯回应
        new MyThread(c,p).start();
        //主线程,罪犯说话,让警察回应
        c.say(p);

    }
    //建立线程
    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.fun();
        }

        public synchronized void fun() {
            System.out.println("罪犯把人质放了,警察把罪犯捉了");
        }
    }

    //警察
    static class Police{
        public  synchronized void say(Culprit c){
            System.out.println("警察:你先把人质放了,我再放你");
            c.fun();
        }

        public synchronized void fun() {
            System.out.println("警察把罪犯放了,罪犯把人质放了");
        }
    }

}

2、生产者与消费者

解决方法:添加标记
举例:生产者与消费者问题(厨师与服务员)

public class Demo2 {
    /**
     * 多线程通信问题
     *  举例:生产者与消费者问题(厨师与服务员)
     *
     *  解决方法:添加标记
     */

    /*
    //问题代码
    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 synchronized  void run() {
            for(int i = 0; i<100; i++){
                 if(i%2==0){
                     try {
                         f.setNameAndTaste("牛肉饭","美味");
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }else{
                     try {
                         f.setNameAndTaste("炒河粉","一般般");
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }

        @Override
        public synchronized void run() {
            for(int i=0; i<100; i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }

    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //设置名称和味道,并在两者之间进行一个延时(休眠0.1s)以便更容易出现问题(抢到的时间片不足以进行设置味道)
        public void setNameAndTaste(String name,String taste) throws InterruptedException {
            this.name = name;
            Thread.sleep(100);
            this.taste = taste;
        }
        public void get(){
            System.out.println("服务员端走的菜名称是:"+name+"味道是:"+taste);
        }
    }
     */
    //解决方法:
    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 synchronized  void run() {
            for(int i = 0; i<100; i++){
                if(i%2==0){
                    try {
                        f.setNameAndTaste("牛肉饭","美味");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    try {
                        f.setNameAndTaste("炒河粉","一般般");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }

        @Override
        public synchronized void run() {
            for(int i=0; i<100; i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    f.get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //true表示可以进行生产(做菜)
        boolean flag = true;

        //设置名称和味道,并在两者之间进行一个延时(休眠0.1s)以便更容易出现问题(抢到的时间片不足以进行设置味道)
        public void setNameAndTaste(String name,String taste) throws InterruptedException {
            if(flag){
                this.name = name;
                Thread.sleep(100);
                this.taste = taste;
                //做完一道菜,把标记改为false,这样就不能连续做菜了
                flag = false;
                //做完一道菜,唤醒所有睡眠的线程
                this.notifyAll();
                //厨师线程进入睡眠
                this.wait();
            }
        }
        public void get() throws InterruptedException {
            //flag标记为false时表示厨师线程已休眠,此时服务员线程被唤醒,可以进行端菜
            if(!flag){
                System.out.println("服务员端走的菜名称是:"+name+",味道是:"+taste);
                //端完菜之后flag变为true,厨师线程可以启动
                flag = true;
                //唤醒厨师
                this.notifyAll();
                //自己进入睡眠
                this.wait();
            }
        }
    }
}

七、线程的六种状态

java.lang.Thread.State枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()方法获取当前线程的状态。

1、NEW

新建状态,即用new关键字新建一个线程,还未调用start方法进行启动的状态。

2、RUNNABLE

运行状态,操作系统中的就绪(ready,调用start方法后等待运行)和运行(running)两种状态,在Java中统称为RUNNABLE。

3、BLOCKED

阻塞状态,表示线程正等待监视器锁而陷入的状态。

4、WAITING

等待状态的线程正在等待另一线程执行特定的操作(如notify)

5、TIMED_WAITING

具有指定等待时间的等待状态

6、TERMINATED

线程完成执行,终止状态

7、线程状态转化

下图源自《Java并发编程艺术》图4-1
在这里插入图片描述

八、线程池

线程池Executors是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。 可以理解为用于存放线程的容器。
线程池的优点:

  1. 降低资源消耗。
  2. 提高响应速度。
  3. 提高线程的可管理性。

1、缓存线程池

public class Demo5 {
    public static void main(String[] args) {
/**
     缓存线程池
     (长度无限制)
       任务加入后的执行流程
        1判断线程池是否存在空闲线程 
        2存在则使用  
        3不存在则创建线程并使用
*/
        //向线程池中加入新的任务
        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) {
            e.printStackTrace();
        }


    }
}

2、定长线程池

public class Demo6 {
    /*定长线程池
    长度是指定的线程池
    加入任务后的执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程
    **/
    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(1000);
                } 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()+"哈哈哈");
            }
        });
    }
}

3、单线线程池

public class Demo7 {
    /*单线程线程池
    执行流程
        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()+"哈哈哈");
            }
        });
    }
}

4、周期定长线程池

public class Demo8 {
    /*周期任务  定长线程池
    执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程

        周期性任务执行时
                定时执行 当某个任务触发时  自动执行某任务
    **/
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //定时执行一次
        //参数1:定时执行的任务
        //参数2:时长数字
        //参数3:时长数字的时间单位    Timeunit的常量指定
       /* scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"啦啦啦");
            }
        },5, TimeUnit.SECONDS);      //5秒钟后执行*/

        /*
        周期性执行任务
            参数1:任务
            参数2:延迟时长数字(第一次在执行在什么时间以后)
            参数3:周期时长数字(每隔多久执行一次)
            参数4:时长数字的单位
        * **/
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5,1,TimeUnit.SECONDS);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值