2020-11-05

多线程

进程:进入到内存的程序
线程:在进程里,开启一条到cpu的执行路径,cpu可以通过这个路径执行功能,这个路径就叫线程
单个的cpu在多个线程之间做高速切换,8核的就是可以同时执行8个线程,所以就是单线程的8倍,多线程的好处就是效率高,多个线程之间不影响

  • 线程调度的方式
    1.抢占式调度(优先级高的就先占领)
    2.分时调度

  • 主线程:执行main方法的线程

  • 如何创建多线程?
    1.将类声明为Thread的子类,重写run方法
    子类

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run"+i);
        }
    }
}

执行代码

public class Lianxi {

//创建多线程
    public static void main (String[] args) /*throws ParseException8*/ {
        //创建Thread的子类
        //重写thread的run方法.设置线程任务
        //创建子类的对象
        //调用thread类的start方法.来执行run方法
        //start就是调用一个线程来执行run方法
        //当前的线程是main线程
        //run方法的线程与main线程并发
        //java属于抢占式,谁的优先级高先执行谁


        MyThread mt=new MyThread();
        mt.start();

        for (int i = 0; i <20 ; i++) {
            System.out.println("main"+i);
        }
        

    }

}

我们执行以上的程序,发现run和main里面的打印是随机性执行的,这是为什么呢?
结果长这个样子

main0
run0
main1
run1
run2
main2
run3
main3
main4

new mythread会开辟一条到cpu的新路径,用来执行run方法,那么cpu就在main和run这两个路径里面选择一个线程执行,我们控制不了cpu,导致这两个线程是随机执行的,谁抢到了cpu谁执行

  • 多线程的内存情况是什么样的呢?
    我们还是以上面这个例子来讲,有一个main方法和一个run方法,我们压栈执行
    main里面new了一个mythred,就到了堆里面去
    然后调用run方法,那么就把run压栈了,这是单线程的写法(main线程)
    而多线程开启的时候,调用start()方法,我们另外开启了一个新的栈空间来执行run方法
    每多开一个线程,就会开一个新的栈空间来执行run
    cpu就在这几个开辟的空间里面选择执行,而且因为在不同的栈空间中,所以多个线程之间不会互相影响
    在这里插入图片描述
  • 打印线程名字常见的两种方式
public class MyThread extends Thread{
    @Override
    public void run() {
        //获取线程名称
//        String name=getName();
//        System.out.println(name);

        //第二种获取线程名称
        Thread thread=Thread.currentThread();
        System.out.println(thread);

    }
}
    public static void main (String[] args) /*throws ParseException8*/ {


        MyThread mt=new MyThread();
        mt.start();
        new MyThread().start();
        new MyThread().start();
    }
  • 给线程起名字的方法
    1.setName(String name)
    2.直接 new Thread(String name),继承的时候需要重写构造方法

  • sleep
    是当前正在执行的线程以指定的毫秒数暂停,之后线程继续执行

  • 创建多线程程序的第二种方式Runnable,这个接口就一个run方法
    首先实现一个接口

public class RunnableImpl implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

然后把接口的实现类传进去,执行

//创建多线程
    public static void main (String[] args) /*throws ParseException8*/ {
        //创建一个runnable接口的实现类
        //在实现类中重写runnable接口的run方法,设置线程任务
        //创建一个runnable接口的实现类对象
        //创建thread类对象,构造方法中传递runnable接口的实现类对象
        //调用thread类的start

        RunnableImpl run=new RunnableImpl();
        Thread t=new Thread(run);
        t.start();


        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }


    }
  • 实现runnable来创建多线程的好处
    1.一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其他的类,实现其他的接口
    2.增强了程序的扩展性,降低了程序的耦合性,实现runnable接口的方式,把设置线程任务和开启新线程进行了分离,实现类中,重写了run方法,用来设置线程任务
    所以以后尽量使用第二种方式来实现多线程

  • 匿名内部类实现线程的创建(究极简化代码之术)
    匿名:没有名字
    内部类:写在其他类内部的类
    匿名内部类作用:简化代码,把子类继承父类,重写父类的方法,创建子类对象合一步完成,把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
    匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

public class Lianxi {

//创建多线程
    public static void main (String[] args) /*throws ParseException8*/ {

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"Thread");
                }
            }
        }.start();

        //多态的写法?
        Runnable r=new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"Runnable");
                }
            }
        };
        new Thread(r).start();

        //简化接口的方式,匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+"简化");
                }
            }
        }).start();


    }

}
  • 线程安全问题
    单线程是不会出现线程安全问题的,多线程程序没有访问共享数据的时候也不会出现线程安全问题,但是多个线程使用共享数据的时候会出现安全问题
    设置三个线程卖100张票
public class RunnableImpl implements Runnable{

    private int ticket =100;

    //卖票
    @Override
    public void run() {
        //使用
        while(true){
            if(ticket>0){
                //票存在,卖
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
                ticket--;
            }
        }


    }


}
public class Lianxi {

//模拟卖票
    public static void main (String[] args) {
        RunnableImpl run=new RunnableImpl();
        Thread t0=new Thread(run);
        Thread t1=new Thread(run);
        Thread t2=new Thread(run);

        t0.start();
        t1.start();
        t2.start();

    }

}

执行程序,发现出现了重复的票和不存在的票

Thread-1-->正在卖1张票
Thread-0-->正在卖1张票
Thread-2-->正在卖-1张票

为什么会出现这样的问题呢?
首先t012一起抢夺cpu的执行权,谁抢到执行谁
1.t0先抢到,进入到run方法中执行,执行到if语句它就睡眠了,因此失去了cpu的执行权
2.这个时候t2抢到了执行权,进入run,再到if,sleep,又失去执行权
3.t1同上
4.t2睡醒了,抢到了cpu的执行权,继续执行,进行卖票,输出正在卖第一张票,ticket–之后,ticket=0了,不满足>0的条件了,那么if里面的语句就不执行了
5.t1睡醒了,进行卖票,但是这个时候ticket已经变成0了,输出正在卖第0张票,ticket–到变成-1,判断一下不执行了
6.t0行了,输出正在卖第-1张票,ticket–到变成-2

  • 怎么解决线程安全问题呢?
    1.同步代码块,把刚才的代码都放到synchronized的大括号里面就行了
public class RunnableImpl implements Runnable{

    private int ticket =100;

    //创建一个锁对象
    Object obj=new Object();

    /**1.使用同步代码块解决
    (1)通过代码中的所对象,可以使用任意的对象
    (2)但是必须保证多个线程使用的锁对象是同一个
    (3)把同步代码块锁住,只让线程在同步代码块中执行
    synchronized (同步锁){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
    */


    //卖票
    @Override
    public void run() {
        //使用
        while(true){
            synchronized (obj){
                if(ticket>0){
                    //票存在,卖
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
                    ticket--;
                }
            }
        }


    }


}

同步技术的原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票
t0抢到了,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行
t1抢到了,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象,一直到t0执行完同步中的代码,会把锁对象归还给同步代码块t1才能获取到锁对象进入到同步中执行
也就是说,同步中的线程,没有执行完毕,不会释放锁,同步外的线程没有锁进不去同步
(感觉同步代码块就像是搞了一个带锁的房间啊,保证了一次只能只能有一个线程使用这个房间)
但是需要频繁的判断锁,归还锁,释放锁,效率比较低

2.第二种方法,使用同步方法来解决
(1)把访问了共享数据的代码抽出来,放到一个方法中
(2)给方法添加synchronized修饰符

public class RunnableImpl implements Runnable{

    private int ticket =100;

    //创建一个锁对象
    Object obj=new Object();




    //卖票
    @Override
    public void run() {
        //使用
        while(true){
            payticket();
        }


    }
    //同步方法
    public synchronized void payticket(){
        if(ticket>0){
            //票存在,卖
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
            ticket--;
        }
    }


}

同步方法也会把方法内部的代码锁住,它的锁对象就是当前线程的实现类对象new RunnableYmpl(),也就是this

2.第二种方法的变体,静态同步方法

public class RunnableImpl implements Runnable{

    private static int ticket =100;

    //创建一个锁对象
    Object obj=new Object();




    //卖票
    @Override
    public void run() {
        //使用
        while(true){
            payticket();
        }


    }
    //同步方法
    public static synchronized void payticket(){
        if(ticket>0){
            //票存在,卖
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
            ticket--;
        }
    }


}

就是加了一个静态的修饰符,但他的锁对象就不是this了,因为static我们知道,是属于类的而不是属于对象的,static的方法是优先于对象的,而this是创建对象之后产生的,静态方法的锁对象是本类的class属性–>class文件对象(反射),也就是Runnable.class

3.第三种方案,Lock锁
Lock接口实现了synchronized语句,它提供两个方法,一个是获取锁,一个是释放锁

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

public class RunnableImpl implements Runnable{

    private static int ticket =100;

    //Lock
    Lock l=new ReentrantLock();


    //卖票
    @Override
    public void run() {
        
        while(true){
            //在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
            l.lock();
            if(ticket>0){
                //票存在,卖
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
                ticket--;
            }
            //在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
            l.unlock();
        }
    }

}

就是前后加个锁再把锁卸了,还有一个更好的写法,就是吧unlock写到finally语句块里面,这样无论是否程序出现异常,都会把锁释放掉,提高效率

  • 线程状态
    new/runnable/blocked/time_waiting/waiting/terminated
    休眠和waiting有什么区别呢,time_waiting是可以自动唤醒的,而waiting状态只有notify才能强制唤醒
    在这里插入图片描述
    time_waiting:由sleep()可以进去等待一段时间
    blocked:锁阻塞状态,这个是因为同步锁在别的线程,自己没有锁对象,需要等到别人把锁释放
    waiting:调用wait()就可以使线程进入等待状态,notify()可以唤醒线程,由此实现了线程之间的通信
  • 等待与唤醒

下面的内容就是使用等待和唤醒实现两个线程之间的通信:

import java.util.*;

public class DemoWaitAndNotify {
    //进入waiting状态

    //花了5s做包子,然后调用notify唤醒顾客吃包子

    //顾客和老板必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
    //同步使用的锁对象必须保证唯一
    //只有锁对象才能调用wait和notify

    public static void main(String[] args) {
        //创建锁对象

        Object obj=new Object();

        //创建一个顾客线程
        new Thread(){
            @Override
            public void run() {
                //保证等待和唤醒的线程只有一个执行
                synchronized (obj){
                    System.out.println("告知老板包子数量");
                    try {
                        //调用wait方法,放弃cpu的执行,进入到waitting状态
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //唤醒之后,就会继续执行wait之后的代码
                    System.out.println("包子做好了开始吃");
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                //花了5s做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj){
                    System.out.println("告诉顾客包子可以吃了");
                    //实质唤醒
                    obj.notify();
                }
            }
        }.start();

    }


}
  • 线程池
    因为频繁的创建和销毁线程很麻烦,所以就创建了一个线程池的概念,使得一个线程可以复用,也就是执行完一个任务以后不被销毁
    线程池可以理解为一个容器–>集合(ArrayList,HashSet,LinkedList< Thread >,HashMap)
    当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们想要使用线程的时候,就可以从集合中取出来线程使用
    使用的时候就linked.removeFirst()
    使用完毕之后,需要把线程归还给线程池,list.add(t)
    JDK1.5之后直接内置了线程池的概念,可以直接使用Executors,ExecutorService线程池接口
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DemoThread01 {
    public static void main(String[] args) {
        //创建一个指定线程数量的线程池
        ExecutorService es= Executors.newFixedThreadPool(2);

        ///调用submit方法,传递线程任务的实现类,开启线程,执行run方法
        es.submit(new RunnableTest());//pool-1-thread-1创建了一个新的线程
        //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
        es.submit(new RunnableTest());//pool-1-thread-2创建了一个新的线程
        es.submit(new RunnableTest());//pool-1-thread-1创建了一个新的线程
        //shutdown销毁线程池
        es.shutdown();


    }
}

垃圾回收机制

NIO

数组中的最长连续子序列

import java.util.*;


public class Solution {
    /**
     * max increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型
     */
    public int MLS (int[] arr) {
        // write code here
        if (arr==null || arr.length==0){
            return 0;
        }
        int res=1,sum=1;
        Arrays.sort(arr);
        for(int i=0;i<arr.length-1;i++){
            if(arr[i+1]==arr[i]){
                continue;
            }else if(arr[i+1]==arr[i]+1){
                sum++;
                res=Math.max(res,sum);
            }else{
                sum=1;
            }
        }
        return res;
    }
}

二叉树的序列化和反序列化

前序遍历的序列化
与前序遍历的反序列化

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public int index=-1;
    //二叉树序列化
    String Serialize(TreeNode root) {
        StringBuffer sb=new StringBuffer();
        if(root==null){
            sb.append("#,");
            return sb.toString();
        }
        //看顺序中左右,是前序遍历
        sb.append(root.val+",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
  }
    //序列反序列化
    TreeNode Deserialize(String str) {
        index++;
        int len=str.length();
        if(len<=index){
            return null;
        }
        String[] strr=str.split(",");
        TreeNode node=null;
        //为什么前序遍历的反序列化是这个样子的???
        if(!strr[index].equals("#")){
            node=new TreeNode(Integer.valueOf(strr[index]));
            node.left=Deserialize(str);//因为把index放外面了所以传原本的str就行了啦,改变的一直都是全局变量index
            node.right=Deserialize(str);
        }
        
        return node;
        
        
        
  }
}

==

6号开始要转变重点了,需要开始javaweb的项目,spring等框架的学习,争取15号之前完成一个基本的项目框架
然后改变一下刷题的方式,不要自己写了,一边看教程一边写,还是过一遍基本的想法吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值