线程与进程

watson第一篇笔记(线程与进程)

线程与进程原理理解

什么是进程/线程:

  1. 进程:是指占用一个独立的内存空间,在这块独立空间运行的应用程序。(可以理解为一个软件or一个app,虽然当前市面上许多程序都是多进程的)
  2. 线程:是进程中的一个执行路径,在进程中,线程可以随意切换,并行,并发运行,一个进程最少有一个线程。线程相当于对进程的多次划分。(可以理解成一个软件中的多项功能,类似于打游戏的同时可以语音,缓存音乐的时候可以播放音乐,)

线程的六大状态:

1.创建(NEW):一般指线程刚刚被创建,还没有调用.start()方法时的状态。
2.运行(RUNNABLE):调用start后,线程被启动运行,这里包括1.ready(准备运行),2.running(正在运行)两种状态。
3.终止(TERMINATED):运行结束,内存回收,线程死亡。
4.阻塞(BLOCKED):线程被阻塞(等待获取锁并运行)。
5.等待(WAITING):无限等待某一线程完成某一动作后再运行。
6.定时等待(TIMED_WAITING):在指定时间等待某一线程完成动作。

同异步和调度

  1. 同步和异步:
    1.1 同步是指程序中各个线程/任务几乎同时加载到内存中的时候,采用排队的方式(先来先运行,或者 高优先级先运行等等)的方式处理。
    1.2 异步是指程序中各个线程/任务几乎同时加载到内存中的时候,直接并发执行。
    1.3 特点:同步运行可以保证程序安全,但是比较费时。异步运行比较节省时间,但是很有可能导致程序死锁,不安全。
  2. 调度:
    2.1 同步调度:
    内存和CPU平均分配时间给各个线程,使得每个线程都能平均使用系统资源
    2.2 抢占式调度:
    给每个线程建立优先级,优先级高的优先抢占系统资源,遇到同优先级的,系统将随机选取其一运行。

并发与并行

1.并发是指一个时间段内发生的一系列事件
2.并行是指多个事件在同一时间发生。
(并发是你今天吃了饭还睡了觉。并行是你边睡觉边吃饭,最主要区别是对“时间段”的理解)

java实现多线程的方式

1.Thread:
通过继承父类Thread 并重写其run()方法,即可new出一个新线程进行并发执行,是一个线程类。
2.Runnable:
通过接口实现Runnable,成为一个任务类型,通过写其run()方法,可以修改成自己需要的任务。再把任务作为参数传入

代码示例:

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {

        //Thread
        MyThread myThread = new MyThread(); //直接继承一个线程对象,重写其运行任务

        //Runnable
        MyRunnable myRunnable = new MyRunnable();//创建一个任务对象
        Thread t = new Thread(myRunnable); //把任务传给一个新建的线程
        /**
         * java是抢占式调度的,所以执行顺序是不确定的
         */
        myThread.start(); //副1线程启动
        t.start();  //副2线程启动
        for (int i =2;i<4097;i=i*2){
            //主线程启动
            System.out.println("主线程:"+i);
        }

        //匿名内部类实现多线程
        new Thread(){
            @Override
            public void run() {
                for (int i =2;i<4097;i=i*2){
                    System.out.println("副3线程:"+i);//副线3程启动
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        Thread ts = new Thread(new MyRunnables());
        ts.start();
        ts.sleep(1000);
        new Thread(new MyRunnables()).start();
        new Thread(new MyRunnables()).start();

    }
    //任务类Runnable
    public static class MyRunnables implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

3.二者对比:

     * 多线程技术 Thread 和 Runnable的区别
     * Thread 是继承的 ,Runnable是接口实现的
     * 1.  Runnable通过创建任务,导入线程的方式运行
     * 2.  Thread通过继承线程类,对父类的任务方法重写的方式运行
    
     * Runnable相比Thread有如下好处:
     * 1.  多线程执行相同任务时,Runnable效果好
     * 2.  Thread只能单继承,不能再继承其他类。
     * 3.  任务和线程分离,提高健壮性
     * 4.  线程池技术接受Runnable任务

4.线程创建的第三种方法(callable)
4.1callable类创建带返回值的线程:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 线程创建的第三种方法
 * callable 带返回值的线程
 */
public class Demo5 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> fc = new FutureTask<>(c);
        new Thread(fc).start();
        //副线程获取返回值的时候,主线程不启动。
        int r = fc.get();
        if (fc.isDone()){
            System.out.println("副线程运行完成");
        }
        for (int i=1;i<r;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(100);
            for (int i=1;i<10;i++){
                Thread.sleep(100);
                System.out.println(i);
            }
            return 30;
        }
    }
}

注意:可以通过获取callable返回值的方式(new callable.get()),让主线程和副线程分时调度,使其不能并发操作。

总结:各个创建方式都有其特点,在需要线程有返回值的时候使用callable。其他的尽量通过Runnable方式创建线程任务对象的方式实现多线程。

线程不安全和守护线程:

  1. 线程不安全
    1.1线程不安全:简单的理解就是,当多个线程对一个全局变量同时进行读写操作的时候,会造成结果的不一致(A线程在读X变量s时刻的值的时候,B线程在同时(s时刻)进行对X变量修改的操作,这样就产生了一个概率问题,有可能A读取的是X修改前的,也可能是X修改后的数据)这样就产生了线程不安全。
    1.2守护线程:守护线程,是保证副线程和主线程同时死亡,当设置setDaemon()为true的时候,会和主线程一起消灭 虽然未执行完。
/**
 * 守护线程
 */
public class Demo2 {
    public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      //设置副线程为守护线程,当主线程先死亡的时候,(虽然没执行完)自动死亡
      t.setDaemon(true);
      t.start();
        for (int i =2;i<257;i=i*2){
            System.out.println("主线程:"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for (int i =2;i<4097;i=i*2){
                System.out.println("副线程:"+i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

解决线程不安全——线程锁

  1. 解决线程不安全的三种方式:

     * 1. 使用同步代码块:synchonized(){}
     * 2. 同步方法:方法前缀加 synchonized
     * 3. 显式锁 :Lock()
    
  2. 代码示例(以模拟抢票为基础):

public class Demo3 {
    public static void main(String[] args) {
        /**
         * 解决线程不安全的方法:
         * 1. 使用同步代码块 synchonized(){}
         * 2. 同步方法 方法前缀加 synchonized
         * 1,2方法都是隐式锁
         * 3. 显式锁 Lock()
         */

		Runnable t1 = new SellTickets1(); 
 		Runnable t2= new SellTickets2();
        Runnable t3= new SellTickets3();
        new Thread(t,"线程1").start();
        new Thread(t,"线程2").start();
        new Thread(t,"线程3").start();
        new Thread(t,"线程4").start();
    }

    //同步代码块
    static class SellTickets1 implements Runnable{
        private int ticket = 10;
        //synchronized的锁
        private final Object o = new Object();
        @Override
        public void run() {

            while (ticket>0){
                synchronized (o){
                    if (ticket>0){
                        System.out.println("成功卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticket --;
                        System.out.println(Thread.currentThread().getName()+"剩余票数:"+ticket);
                    }else {
                        break;
                    }

                }
            }
        }
    }

    //同步方法
    static class SellTickets2 implements Runnable{
        //使用同步方法使得线程安全
        private int ticket = 10;

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

        public synchronized int sale() {
            while (ticket > 0) {
                if (ticket > 0) {
                    System.out.println("成功卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "剩余票数:" + ticket);
                    return 1;
                }
                return 0;

            }
            return 0;
        }

    }

    //显式锁
    static class SellTickets3 implements Runnable{
        private int ticket = 10;

        Lock lock = new ReentrantLock();
        @Override
        public void run() {
            /**
             * 通过不断的开锁解锁达到控制一个线程运行的目的
             */
            while (ticket>0){
                //锁住 只有一个线程能进入下面的代码
                lock.lock();
                if (ticket>0){
                    System.out.println("成功卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket --;
                    System.out.println(Thread.currentThread().getName()+"剩余票数:"+ticket);
                }else {
                    lock.unlock();
                    break;
                }
                //解锁,上面的代码继续运行
                lock.unlock();
            }
        }
    }
}

同步代码块:
是将会产生数据修改的代码片段添加一个Synchonized(object){}代码块,并赋予一个object类型的锁,这个锁要和所有线程所操作的共同变量放在一起,当进行中括号内代码运行的时候,会对数据进行上锁,期间任何线程都不可以对该共同变量进行读写操作。
同步方法:
在方法体外加上synchronized标记,使得一次只能被一个线程调用方法,原理大致和同步代码块一致。
显式锁:
在即将发生数据修改的代码前添加Lock.lock()方法,使得只能单线程运行以下代码,在代码结束阶段进行Lock.unlock()方法,使得线程重新竞争运行。

显式锁(Lock)和隐式锁(Sync)的区别(面试大概率会问)

1. 出身问题:
Sync是JVM层面的,调用系统底层的monitorenter对象加载。
Lock是API层面的,自己本身就是一个类,调用时需要实例化。

2. 同步锁问题:
Sync会自动释放锁,不会产生死锁。
Lock是手动释放锁,操作不当会导致线程死锁。

3. 中断处理:
Sync运行时不能被中断。
Lock可以调用方法(interrupt())中断或挂起线程。

4. 公平机制:
Sync是非公平锁,抢占式调度。
Lock默认是非公平锁,当实例化时可以传入”true“ 变为公平锁。

5. 唤醒机制:
Sync要么随机唤醒一个进程,要么全部唤醒进程。
Lock可以做到精确唤醒和分组唤醒。

6. 性能对比:
JDK1.5之前,Sync性能比Lock差。
JDK1.6之后,Sync性能大幅提升,性能和Lock相差无几,且还有提升空间。
且官方较支持使用Sync进行多线程。

线程池

顾名思义,线程池就是一个容纳多个线程的容器,通过建立线程池,就不会使得一个线程被反复创建销毁,减轻内存负担的同时,在多任务的时候也能提高运行效率。

执行流程是:线程创建->任务创建->执行任务->关闭线程

线程池类型:

1.缓存线程池:
缓存线程池是建立一个线程池,通过判断任务数量是否多于当前线程数,如果任务多则添加线程。
在代码中,创建了一个缓存线程池,初始线程容量为0,当同时传入3个任务后,自动创建3个线程添加到线程池中,所以结果是三个任务同时运行

    public static void main(String[] args) {
    	//创建缓存线程池
        ExecutorService service = Executors.newCachedThreadPool();
        Runnable r1 = new SellTickets1();
        Runnable r2 = new SellTickets2();
        Runnable r3 = new SellTickets3();
        service.execute(r1);
        service.execute(r2);
        service.execute(r3);
    }

2.定长线程池:
定长线程池的原理是,创建一个指定长度的线程池,当任务少于当前线程数量的时候,部分线程处于休眠状态,当任务数大于当前线程数的时候,部分任务需进行等待线程完成任务。

    public static void main(String[] args) {
    	//创建了一个有两个线程的定长线程池,传入3个任务
    	//必然有一个任务需要排队
        ExecutorService service = Executors.newFixedThreadPool(2);
        Runnable r1 = new SellTickets1();
        Runnable r2 = new SellTickets2();
        Runnable r3 = new SellTickets3();
        service.execute(r1);
        service.execute(r2);
        service.execute(r3);
    }

3.单线线程池:其实就是定长线程池的参数是1(只有一个线程的线程池)
4.周期性任务定长线程池:
在定长线程池的基础上,给予一个时间周期,当到达该周期的时候才执行该任务:

    public static void main(String[] args) {
    	//创建了一个有1个线程的周期性定长线程池,传入3个任务
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        Runnable r1 = new SellTickets1();
        Runnable r2 = new SellTickets2();
        Runnable r3 = new SellTickets3();
        //r1任务延迟20秒执行
        service.schedule(r1,20,TimeUnit.SECONDS);
        service.execute(r2);
        service.execute(r3);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值