1进程和线程

进程:正在运行的程序。也就是代表了程序锁占用的内存区域。
线程:线程(thread)是操作系统能够进行运算调度的最小单位;一个进程可以开启多个线程。
进程和线程的区别:
一个软件运行至少依赖一个进程。
一个进程的运行至少依赖一个线程。

2多线程

多线程:多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。

2.1线程状态

image.png
线程生命周期,总共有五种状态:
1) 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2) 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3) 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4) 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
5) 根据阻塞产生的原因不同,阻塞状态又可以分为三种:

a)   等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b)   同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c)   其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

6) 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2.2线程创建

image.png

2.2.1继承Thread

创建对象:

Thread() 
    分配新的 Thread 对象。 
Thread(Runnable target) 
    分配新的 Thread 对象。 
Thread(Runnable target, String name) 
    分配新的 Thread 对象。 
Thread(String name) 
    分配新的 Thread 对象。

常用方法:

long getId() 
    返回该线程的标识符。 
String getName() 
    返回该线程的名称。 
void run() 
    如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 
void setName(String name) 
    改变线程名称,使之与参数 name 相同。 
static void sleep(long millis) 
    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行), 
void start() 
    使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 
void stop() 
    终止线程。

测试:

package cn.tedu.thread;
        //测试  Thread类
        public class Test2_Thread {
            public static void main(String[] args) {
                //1,创建对象
                MyThread t = new MyThread();//新建状态
                //2,开启线程
        //      t.run();
                 t.setName("线程1");//修改线程名
                t.start();//可运行状态
                //3,run()和start()有什么区别?
                //run()和start()都能执行任务,但是run()执行时就只是一个普通的方法调用没有多线程并发抢占的效果
                //--模拟多线程--
                MyThread t2 = new MyThread();
        //        t2.run();
                 t.setName("线程2");//修改线程名
                t2.start();
                /*
                4,多线程的执行结果具有随机性
                Thread-0---0
                Thread-1---0
                Thread-0---1
                Thread-1---1
                Thread-0---2
                Thread-1---2
                Thread-1---3*/
            }
        }
        //1,继承Thread类,实现多线程编程
        class MyThread extends Thread {
            //2,在多线程编程里,把所有的业务放入--重写的run()里--generate--override methods..
            //--需求:重复的打印10次线程名称
            @Override
            public void run() {//运行状态
                for (int i = 0; i < 10; i++) {//fori
                    System.out.println(super.getName()+"---"+i);//getName()--获取线程名称
                }
            }//结束状态
        }

2.2.2实现Runnable接口

概述:如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口。

常用方法:

void run()
      使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。

测试:

package cn.tedu.thread;
//测试 Runnable接口
public class Test3_Runnable {
            public static void main(String[] args) {
                //1,创建目标对象
                MyRunnable target = new MyRunnable();
                //2,把要执行的目标 和Thread绑定关系
                Thread t = new Thread(target);
                t.setName("猪猪侠");
                //3,启动线程
                t.start();

                //4,模拟多线程
                Thread t2 = new Thread(target);
                t2.setName("蜘蛛侠");
                t2.start();
        /*      5,多线程的随机性
                Thread-0===0
                Thread-0===1
                Thread-1===0
                Thread-1===1
                Thread-1===2
                Thread-1===3
                Thread-1===4
                Thread-0===2
                Thread-0===3
        */
            }
        }
        //1,实现Runnable接口,实现多线程编程
        class MyRunnable implements Runnable{
            //2,如果有自己的业务,需要把业务 -- 放在重写的run()里
            //--需求:打印10次线程名称
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    //Thread.currentThread()--获取正在执行任务的线程对象
                    //getName()--获取线程的名字
                    //3,Thread.currentThread().getName() -- 获取当前执行任务的线程对象的 名字
                    System.out.println( Thread.currentThread().getName() +"==="+i );
                }
            }
        }

2.2.3实现Callable接口

创建步骤:
1.创建Callable接口的实现类,并重写call()方法,该方法作为线程的执行体,并有返回值。
2.创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3.创建Thread,调用start()方法。
4.可是有FutureTask.get()获取call()的返回值。

public class TestCallable {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        CallableDemo callableDemo = new CallableDemo();
        FutureTask futureTask = new FutureTask(callableDemo); 
        new Thread(futureTask).start();//启动线程对象,并启动线程
        //Integer a = (Integer) futureTask.get(); //通过get()获取返回值
        for (int j = 0; j < 10; j++) {
            System.out.println("main:"+j);
        }
    }
}
 
class CallableDemo implements Callable{
 
    @Override
    public Integer call() throws Exception {
        int i =0;
        for (; i < 10; i++) {
            System.out.println("线程:"+i);
        }
        return i;
    }
}

结果:

main:0
线程:0
main:1
线程:1
main:2
线程:2
main:3
线程:3
线程:4
线程:5
线程:6
线程:7
线程:8
线程:9
main:4
main:5
main:6
main:7
main:8
main:9

2.2.4创建线程池


线程池是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。

ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor之间的关系:

  • Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
  • ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
  • 抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
  • ThreadPoolExecutor继承了类AbstractExecutorService。

推荐使用ThreadPoolExecutor创建线程

// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        ... // 线程执行的任务
    }
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

4种线程池的创建方法:
image.png

  • FixedThreadPoolSingleThreadExecutor:主要问题是堆积的请求处理队列均采用LinkedBlockingQueue,可能会耗费非常大的内存,甚至OOM。
  • CachedThreadPoolScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

2.3线程通信

线程通信的两种简单模型,共享内存和消息传递。

2.3.1使用volatile关键字

使用volatile关键字定义全局变量,当变量发生变化时,线程能够感应并执行相应的业务。采用了共享内存的思想。

2.3.2使用Object类提供的wait(),notify()和notifyAll()

在synchronized修饰的同步方法或代码块中使用Object类提供的wait(),notify()和notifyAll()3个方法进行线程通信。

1.wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。wait()释放锁。
2.notify():唤醒在此同步监视器上等待的单个线程。notify()不释放锁。
3.notifyAll():唤醒在此同步监视器上等待的所有线程。

3线程锁

3.1悲观锁和乐观锁


悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

3.2两种常见的锁


synchronized 互斥锁(悲观锁,有罪假设)

采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)

ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。

读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

3.3synchronized同步锁

把共享资源,用锁锁起来,谁有钥匙,谁去用共享资源,没钥匙的就排队等待。

用法:
通过synchronized关键字实现同步锁

用在方法上:synchronized public StringBuffer append(String str)
用在代码块上:synchronized( 锁对象 ){ 需要同步的代码;}

用在方法上,使用的锁对象是默认的this.;
用在代码块上,锁的对象是任意的。

4单例设计模式

4.1饿汉式

public class Test6_Singleton {
    public static void main(String[] args) {
       MySingle m = MySingle.getSingle();
       MySingle m2 = MySingle.getSingle();
       //==如果比较的是两个引用类型的变量?比较的是地址值
       System.out.println(m==m2);
    }
}
//创建自己的单例程序
class MySingle {
    //1,私有化构造方法,不让外界随意new
    private MySingle() {}
    //2,在类的内部,提供一个已经创建好的对象
    //static是因为  静态资源getSingle()只能调用静态
    static private MySingle single = new MySingle();
    //3,对外提供一个全局访问点
    //static 是因为,想要访问get()可以创建对象访问,但是目前已经不允许创建对象了。只能通过类名调用,就得修饰。
    static public MySingle getSingle(){
       //把内部创建好的对象返回调用位置
       return single;
    }
}

4.2懒汉式

//面试重点:
//1、延迟加载思想:是指不会第一时间就把对象创建好来占用内存,而是什么时候用什么时候创建
//2、线程安全问题:是指共享资源有线程并发的数据隐患,加同步锁,锁方法,也可以锁代码块
public class Test7_Single2 {
    public static void main(String[] args) {
       MySingleton my = MySingleton.getMy();
       MySingleton my2 = MySingleton.getMy();
       System.out.println(my==my2);
    }
}
// 创建类
class MySingleton {
    // 1,私有化构造方法 -- 控制外界new的权利
    private MySingleton() {
    }
    // 2,在类的内部创建好对象 -- 延迟加载!!
    static private MySingleton my;
//  static Object obj = new Object();
    // 3,提供全局访问点
    // 问题:程序中有共享资源my,并且有多条语句(3条)操作了共享资源,此时,my共享资源一定会存在多线程编程的数据安全隐患
    // 解决方案就是加 同步锁 。
    //如果用同步代码块需要确定锁的位置? 锁的对象?由于方法中的所有代码都被同步了,可以直接变成同步方法。
    synchronized static public MySingleton getMy() {
//     synchronized (obj) {//同步代码块:静态区域内,不能是用this关键字?因为加载的先后顺序问题
           if (my == null) {// 说明没new过,保存的是默认值null
              my = new MySingleton();// 需要时才会创建对象,不需要也不会提前就开始占用内存。
           }
           return my;
//     }
    }
}

木安
13 声望6 粉丝

引用和评论

0 条评论