java线程基础学习(一)

线程学习

一 线程基础学习

什么是进程?

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础

​ 如下图所示,一个PID对应一个进程。

在这里插入图片描述

什么是线程?

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位

​ 如下图所示,一个进程可能由一个或多个线程来执行

在这里插入图片描述

ps: 由此我们可以将进程可以看作一次系统进行资源分配和调度的战役,而线程就是其中大大小小作战的士兵,实际操作的单元

进程和线程的区别

  1. 一个程序至少有一个进程,一个进程至少有一个线程.
  2. 线程的划分尺度小于进程,使得多线程程序的并发性高。
  3. 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  4. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  5. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

Java实现线程的三种方式

  • 继承Thread
  • 实现Runnable接口(初中阶常用)
  • 实现Callable(高阶必备)
1 继承Thread的方式

其中必须重写run方法,自定义线程执行逻辑。然后使用main方法new Thread(自定义线程类,线程名),调用run方法则只有主线程,调用start则启用主副线程模式。

ps:此方法适合初学,使用一个则必须继承Thread,不够多态,不推荐使用

public class ThreadTest1 extends Thread{
    @Override
    public void  run(){
        for (int i = 0; i < 10; i++) {
            System.out.println("正在学习thread---->"+Thread.currentThread().getName()+i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20000; i++) {
            System.out.println("父线程执行代码------>"+Thread.currentThread().getName()+i);
        }
        new Thread(new ThreadTest1(),"子线程").run();//调用run 则只有一个mian线程
        new Thread(new ThreadTest1(),"子线程").start();//调用start。则mian和子线程并行代码
    }
}
2 实现Runnable接口方式

同上,重写run方法,线程所有处理都在run()中进行定义,线程启动是调用start()方法,才是真正启动线程,而启动start()方法时会自动调用run()方法

public class ThreadTest2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("正在学习thread---->"+Thread.currentThread().getName()+i);
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 20000; i++) {
            System.out.println("父线程执行代码------>"+Thread.currentThread().getName()+i);
        }
        new Thread(new ThreadTest2(),"子线程").run();
        //new Thread(new ThreadTest2(),"子线程").start();
    }
}

ps:其中run()和start()方法区别:如下图所示

在这里插入图片描述

由图可知,使用run()方法类似于javaScript一样,只有一个执行路径,而start则主副交替执行

3 实现Callable接口方式

实现Callable<线程返回参数值类型>接口,编写call(),自定义线程执行逻辑,使用FutureTask作为对象使用

new Thread(FutureTask).run() / new Thread(FutureTask).start()

public class ThreadTest3 implements Callable<Integer> {
    public Integer call() throws Exception {
        int i=0;
        for (; i < 200; i++) {
            System.out.println(Thread.currentThread().getName()+"正在看第"+i+"本书");
        }
        return i;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadTest3 t3=new ThreadTest3();
        FutureTask<Integer> futureTask=new FutureTask<Integer>(t3);
        new Thread(futureTask).start();
        System.out.println("当前线程返回值"+futureTask.get());
    }
}

**高阶写法 lambda表达式 **

public class ThreadLambda{

    public static void main(String[] args) {
        new Thread(()-> System.out.println("学习thread")).start();
    }
}
线程锁的学习

什么是锁?锁顾名思义就是锁住东西

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。

​ 这样说可能有点空洞,那么我们提到锁,就必须提到多线程的线程泄露:当多个线程访问一个数据对象时,容易对数据造成的破坏,举个经典的抢票实例

public class ThreadSafe implements Runnable {
    private int count = 10;
   
    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(100);
                sale();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 售票方法,解耦合
     */
    public void sale() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + ",拿到了出售的第" + (10 - count + 1) + "张票");
            count--;
        }
    }

    public static void main(String[] args) {
        ThreadSafe t1 = new ThreadSafe();
        new Thread(t1, "小杨").start();
        new Thread(t1, "小张").start();
    }
}

​ 如图所示,这里使用Thread.sleep(100);是为了让扩大因素,让线程更容易出险问题。我们初始化6张票,然后让小张和小阳连个线程去抢票(start方式),按照正常逻辑,在线程优先级一致(线程优先级并不能完全左右线程执行顺序,下文会详细介绍),不会出现抢到同一张票

在这里插入图片描述

​ 如图所示,小张和小杨多次抢到同一张票,小张和小杨按照短信提示消息来找老板要票,老板都傻了,为了避免这种情况,我们必须线程同步,在店铺前加一个大门,大门上有一个锁,只有有钥匙的人,我们才允许他进来买票,没钥匙的人就在外面等着

锁的两种实现方式

使用锁的必备前提:

  • 存在共享数据
  • 多线程共同操作共享数据

按照抢票列子,共享数据(票数),而多线程共同操作共享数据(小张和小杨抢占票数),这种情况我们必须使用锁,所以以后我们使用锁,首先分析代码中是否存在共享数据,然后看是否有多个线程使用这个数据,

ps: 不要动不动就锁this对象,影响性能

  1. synchronized 隐式锁

    java任何一个对象都可以作为锁,synchronized可以作用于方法和变量上,同步方法和同步代码块都可以,这里我们只要用synchronized锁住票数更改这块代码块就行

    先在在这个ThreadSafe类下定义一个变量

    private static Object object = new Object();
    

    然后使用

       public  void sale() {
            synchronized(object){
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + ",拿到了出售的第" + (6 - count + 1) + "张票");
                    count--;
                }
            }
    
        }
    

    或者使用在sale()方法上修饰synchronized效果一致

    通过synchronized锁住关键的对象object,此时代码跑起来,完全没问题

在这里插入图片描述

  1. lock 显式锁

lock 也是锁的一种写法,使用jdk 1.5的ReentrantLock对象,使用锁lock.lock(),释放锁lock.unlock();

public ReentrantLock lock=new ReentrantLock();

为什么称synchronized为隐式锁,lock为显式锁?

因为使用synchronized,只用关注在哪里使用锁,不用关心锁的释放,由系统自动判断

使用lock,在哪里用锁,哪里释放锁都可以自己控制,十分只管,所以称为显示

public class TestLock {
    public static void main(String[] args) {
        ThreadLock t1=new ThreadLock();
        new Thread(t1,"小杨").start();
        new Thread(t1,"小张").start();

    }

}
class ThreadLock implements Runnable{
    public ReentrantLock lock=new ReentrantLock(); //使用jdk 5以后的显式锁 ReentrantLock
    public  int ticketNum=6;
    @Override
    public void run() {
        while(true){
            try{
                lock.lock();
                if(ticketNum>0){
                    System.out.println(Thread.currentThread().getName()+":买到了第"+(7-ticketNum--)+"票");
                }else{
                    break;
                }
                Thread.sleep(100);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

        }
    }
}

效果如下:

在这里插入图片描述

什么是死锁

​ dead lock:是开发者多线程编程常见问题,是根本原因在于不了解何为锁,如何锁,如何释放,是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

在这里插入图片描述

​ 如图所示:线程A使用锁占用着资源2,释放锁后就要获取资源1。而线程B使用占用资源1,释放锁就要获取资源2。但是谁也没有释放,最终就会形成死循环,俗称死锁

//线程1
Synchorized(objectA){
Synchorized(objectB){
//操作
}
}
//线程2 
Synchorized(objectB){ 
Synchorized(objectA){ 
//操作}

Thread代码示例

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值