java多线程基础

串行与并发

串行: 多个任务依次执行,第一个没完成,无法执行第二个·,
并发:多个任务同时执行

进程和线程

进程是对一个程序运行所需要占用的资源的描述。进程可以实现多个任务并发执行,但是我们一般不会去开发多个进程,因为多个进程执行的资源是不共享的。

线程:

线程是一个任务执行的多个单元,多个工厂可以理解为多个进程,而工程里面的多个流水线就是线程。线程之间的数据是可以共享的。进程包含线程,一个进程中是可以有多个线程的·。

生命周期

在程序开发中,将一个对象从实例化完成,到这个对象使用结束,并销毁,将这个过程称为生命周期。
线程的生命周期:
一个线程被实例化完成,到这个线程被消耗,中间的过程。

线程的状态:

新生态
一个线程对象被实例化完成,但是没有做任何操作
就绪态
一个线程被开启,已经开始争抢cpu时间片
运行态
一个线程抢到了cpu时间片,已经开始执行这个线程中的逻辑
阻塞态
现在在运行过程中,受到了某些操作的影响,放弃了已经获取到的时间片。并且不再参与时间片的争抢,此时线程处于挂起态
死亡态
线程被销毁。

线程的开辟方式

1、继承Thread类,然后重写run方法
public class Test extends Thread {
    @Override
    public void run() {
        System.out.println("继承thread类,然后重写run方法的方式");
    }

    public static void main(String[] args) {
        Test threa =new Test();
        //启动
        // start方法会开启一个新的线程,来执行run中的逻辑
         //如果· 直接调用run方法。则线程不会进入就绪态
        threa.start();
    }
}
2、实现Runnable接口,重写run()方法

ava只支持类的单继承,如果某个类已经继承了其他父类,就无法再继承Thread类来实现多线程。在这种情况下,就可以考虑通过实现Runnable接口的方式来实现多线程。
public class ThreadTest implements Runnable {

 @Override
    public void run() {
        for (int i=0;i<10; i++){
            System.out.println("小陈");
        }
    }

    public static void main(String[] args) {
      ThreadTest  t1=new ThreadTest();
        Thread t2=new Thread(t1);
        t2.start();
    }
}

在以后的大多数开发中,我们都会采用实现接口的方法
通过实现Runnable或Callable接口,相对于继承Thread类实现多线程来说:
适合多个线程去处理同一个共享资源的情况
可以避免Java单根继承带来的局限性
事实上,实际开发中大部分的多线程应用都会采用Runnable接口或者Callable接口的方式实现多线程。
比如下面这个售票的模拟:
虽然可能出现问题,但是我们可以通过控制临界资源,来解决

public class ThreadTest implements Runnable {
    private  int tk=0;
    @Override
    public void run() {
        while (tk<100){
            System.out.println(Thread.currentThread().getName()+"出售了第"+tk+"票");
            tk++;
        }
    }

    public static void main(String[] args) {
      ThreadTest  t1=new ThreadTest();
        Thread t2=new Thread(t1,"k1");
        Thread t3=new Thread(t1,"k2");
        t2.start();
        t3.start();
    }
}

如果采用继承的方法,就会出现两个窗口售卖同一张票

线程休眠

使用
1、Thread.sleep(200);
2、TimeUnit.MILLISECONDS.sleep(100);
如果在多线程任务中,使用这个休眠,可能会导致资源被重复调用的情况,因为线程在休眠时,是不会让出资源的

设置线程的优先级

设置线程优先级,只是修改这个线程抢到cpu时间片的概率,并不是优先级高得线程一定能抢到cpu得时间片,优先级得设置是一个整数,默认是五,

public static void main(String[] args) {
  ThreadTest  t1=new ThreadTest();
    Thread t2=new Thread(t1,"k1");
    Thread t3=new Thread(t1,"k2");
    
    //设置优先级别
    //设置优先级一定要放在这个线程开始执行之前
    t2.setPriority(10);
    t3.setPriority(1);

    t2.start();
    t3.start();

}

线程的礼让

线程礼让,是指,让当前自己正在运行的线程释放自己的cpu资源,由运行状态回到就绪状态。

临界资源

线程与进程最大的区别是:多个线程之间可以资源共享,而多个进程之间资源是不能共享的。
在线程中,共享的资源就叫临界资源。
上面我们说到,多线程售票,会出现两个窗口卖同一张票的问题,

产生原因:
是因为多线程太快了,当一个线程抢到时间片,正准备修改剩余票数时,另一个窗口又抢到了,这时,上一个线程还未修改,而这个线程已经拿到剩余的数据了,就会出现两个窗口售卖同一张票这个问题了。

根本原因就是:
有多个线程同时去操作这个临界资源

解决方法:
不让多个线程同时去操作临界资源
这里引入锁的概念,当一个线程在操作资源时,就放一把锁,而其他线程要操作资源是,看到资 源上面有锁,便开始等待,不能操作,当线程1使用完毕后,锁被打开,另一个线程再开始操作。

平时我们开发时,还会用到其他方法:
1、同步代码段
2、同步方法
3、同步锁

1、同步代码段:
public class SourceConfilct implements Runnable {
    //票数
    public static  int restCount=100;
    @Override
    public void run() {
        
        while(restCount>0){
            //对象锁:
//类锁
//这里需要保证的时,多个线程看到的锁,是同一个锁
            synchronized (""){
                if(restCount<=0){
                    return;
                }
                System.out.println(Thread.currentThread().getName()+"卖出1张票"+"剩余"+--restCount+"张");
            }
        }
    }
    public static void main(String[] args) {
        //用四个线程,模拟四个售票员
        SourceConfilct s1=new SourceConfilct();
        Thread t1=new Thread(s1,"1号");
        Thread t2=new Thread(s1,"2号");
        t1.start();
        t2.start();
        
    }
}
2、同步方法

同步的方法:
静态方法:同步锁就是类锁 当前类 .class
非静态方法:同步锁就是this

public class Test2 implements Runnable {
    //票数
    public static  int restCount=100;
    
//1、同步方法  使用关键字,synchronized修饰的方法 就是同步方法
    public synchronized static  void mp() {
            if (restCount <= 0) {
                return;
            }
            System.out.println(Thread.currentThread().getName() + "卖出1张票" + "剩余" + --restCount + "张");
    }

    @Override
    public void run() {

        while(restCount>0){
               mp();
        }
    }
    public static void main(String[] args) {

        
        //用四个线程,模拟四个售票员
        SourceConfilct s1=new SourceConfilct();
        Thread t1=new Thread(s1,"1号");
        Thread t2=new Thread(s1,"2号");
        t1.start();
        t2.start();

    }
}
3、同步锁
public class Test3 implements Runnable {
    //票数
    public static  int restCount=100;

    @Override
    public void run() {

        /**
         * 实例化一个锁
         */
        ReentrantLock lock=new ReentrantLock();

        while(restCount>0){
            lock.lock(); //上锁
            try {
                System.out.println(Thread.currentThread().getName() + "卖出1张票" + "剩余" + --restCount + "张");
            }catch (Exception e){
                lock.unlock(); //解锁
            }finally {
                lock.unlock();
            }

            lock.unlock();
        }
    }
    public static void main(String[] args) {

        //1、同步方法  使用关键字,synchronized修饰的方法 就是同步方法
        //用四个线程,模拟四个售票员
        SourceConfilct s1=new SourceConfilct();
        Thread t1=new Thread(s1,"1号");
        Thread t2=new Thread(s1,"2号");
        t1.start();
        t2.start();

    }

使用锁的时候,要注意避免死锁
死锁:多个线程彼此持有对方需要的锁对象,而不释放自己的锁。
死锁代码如下:

public class Dome1 {

    public static void main(String[] args) {
        //死锁 :多个线程彼此持有对方锁需要的锁
        Runnable runnable=() ->{
            synchronized ("A"){
                System.out.println("A线程持有a锁,等待B锁");
                synchronized ("B"){
                    System.out.println("A线程同时持有了A锁和b锁");
                }
            }
        };
        Runnable runnabletow=() ->{
            synchronized ("B"){
                System.out.println("b线程持有B锁,等待a锁");
                synchronized ("A"){
                    System.out.println("b线程同时持有了A锁和b锁");
                }
            }
        };
        Thread t1=new Thread(runnable);
        Thread t2=new Thread(runnabletow);
        t1.start();
        t2.start();
    }
}

那么在实际业务中,我们遇到这样的需求,需要持有一个锁标记,又需要另一个线程的锁标记时,我们应该怎么避免死锁呢

可以使用以下方法
// wait :是object类中的一个方法,让当前的线程释放自己的锁标记,并释放cpu资源 ,使当前的线程进入等待队列中
//notify :通知 是object类中的一个方法,作用是唤醒等待队列中的一个线程,使这个线程进入锁池
// notfyAll :通知 是object类中的一个方法,作用是唤醒等待队列中所有的线程,使这些线程进入锁池

public class Dome1 {

    public static void main(String[] args) {
        Runnable runnable=() ->{
            synchronized ("A"){
                System.out.println("A线程持有a锁,等待B锁");

                try {
                    "A".wait();  //让A 等待,并释放A锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized ("B"){
                    System.out.println("A线程同时持有了A锁和b锁");

                }

            }
        };

        Runnable runnabletow=() ->{
            synchronized ("B"){
                System.out.println("b线程持有B锁,等待a锁");
                synchronized ("A"){
                    System.out.println("b线程同时持有了A锁和b锁");
                    "A".notify(); //B执行完了唤醒A锁
                }
            }
        };

        Thread t1=new Thread(runnable);
        Thread t2=new Thread(runnabletow);
        t1.start();
        t2.start();
    }
}
posted @ 2021-03-14 16:45  万能工具人  阅读(40)  评论(0)    收藏  举报