线程基础:概念、创建方式、同步方式

12 篇文章 0 订阅
2 篇文章 0 订阅

前言

线程的创建方式4种
线程的安全问题解决的方法3种:同步代码块,同步方法,Lock

基本概念

  • 程序(program)
    是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
  • 进程(process)
    正在运行的一个程序
  • 线程(thread)
    线程是进程的一部分,是指进程中的一个执行流程

下面的概念也了解一下

  • 并行:多个cpu执行多个任务。例:多个人做不同的事情
  • 并发:一个cpu执行多个任务。例:秒杀。多个人做同一件事

一个java程序至少有三个线程:

  • 1、main()主程序线程
  • 2、gc()垃圾回收线程
  • 3、异常处理线程

创建线程方法一:继承Thread

下面是工程例子:
包含说明

package com.edu.create;

import static java.lang.Thread.*; 
/***
 * 创建线程的方式一:
 *1、 继承Thread类
 *2、 重写run()方法
 *3、 start()启动线程并调用run()方法
 */
 
/**
 * 常用方法:
 * 1、run()运行过程
 * 2、start()启动
 * 3、currentThread()静态方法,返回当前线程
 * 4、getName() 获得线程名称
 * 5、setName() 设置线程名称
 * 6、yield()   释放当前cpu的执行权,注:当前线程依然有抢占cpu的权利
 * 7、join()    在线程a中使用b.join() 则a阻塞,直到b线程执行结束
 * 8、stop()    已过时,强制结束线程
 * 9、sleep()   睡眠,使线程睡眠
 */

/***
 * 优先级:
 * MAX_PRIORITY  :10 最高级
 * MIN_PRIORITY  :1  最低级
 * NORM_PRIORITY :5  默认优先级
 * setPriority()   设置线程优先级
 * getPriority()   获取线程优先级
 *
 * 注:设置线程优先级,例如:设置最高级。
 *     并不意味着一定是这个线程最优先执行
 *     这里还是有一定的概率问题,只能说提高线程抢占cpu的优先概率
 *
 *
 */
class MyThread extends Thread{
    @Override
    public void run() {
        //Thread.currentThread().setName("线程1");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class CreateThread {
    public static void main(String[] args) {
        MyThread thread0 = new MyThread();//线程0
        //线程启动
        thread0.start();
        //设置名称
        thread0.setName("线程0");
        //设置优先级
        thread0.setPriority(MAX_PRIORITY);

        //如果直接使用run,会不会报错?不会
        //new MyThread().run();
        //但是直接使用run(),并不是创建一个新线程,而只是运行它的方法而已
        MyThread thread1 = new MyThread();//线程0
        thread1.start();
        thread1.setName("线程1");
        //设置thread1线程优先级
        thread1.setPriority(MIN_PRIORITY);

        for (int i = 0; i < 10; i++) {
//            if(i == 5){
//                yield();
//                //释放先前cpu
//                //但是当前线程依然可以抢到cpu的执行权
//                try {
//                    sleep(100);
//                    //使线程陷入睡眠100
//                    //配合上面党的yield使用,基本上可以切换当前cpu的执行权
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }

            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

创建线程的方式二:实现Runnable

下面例子工程:

package com.edu.create;

/***
 * 创建线程方法2:Runnable
 * 注:这个创建线程的步骤与方法一有所不同
 *
 * 不同1:实现Runnable接口--Runnable
 * 不同2:
 *       MyThread2 myThread = new MyThread2();
 *       Thread    t = new Thread(myThread);
 *       需要将创建的对象,传入新建Thread对象的构造方法中
 */

class MyThread2 implements Runnable{
    private int num = 10;

    @Override
    public void run() {
       while (num>0){
           System.out.println(Thread.currentThread().getName()+":"+num);
           num--;
       }
    }
}
public class CreateThread2 {
    public static void main(String[] args) {
        MyThread2 myThread = new MyThread2();

        Thread    t = new Thread(myThread);
        t.start();

        //这里t1与t2线程共享同一个Runnable
        Thread    t2 = new Thread(myThread);
        t2.start();


    }

}

实现Callable接口

Class TestCallable implement Callable<Integer>{
     @Override
    public Integer call() {
       return 0;
    }
  }

Callable 实现的线程需要通过线程池来实现

public class CreateThread3 {
    public static void main(String[] args) {
          //创建线程池
          ExecutorService pool = Executors.newFixedThreadPool(taskSize);
          TestCallable  thread = new TestCallable ();
          //执行线程
          Future future = pool.submit(thread );
          pool.showdown()
          //获取返回值
          try{
             future .get();
          }catch(Expection e){}
          


    }

}

上面三种种创建线程的方式比较

  • 在实际开发之中优先使用Runnable类
    原因一:实现Runnable接口没有单一继承的局限性
    原因二:实现的方式更适合用来处理共享数据

  • 联系: Thread implements Runnable 都会实现 Runnable类
    (Run() 方法是Runnable类的子方法,Thread 也是实现Runnable接口覆盖重写)

  • Callable最大的区别在于他能接收返回值,也能抛出线程内异常
    主要用来做线程的通信

线程生命周期

这个Thread源码中有自己的状态定义
查看源码状态定义步骤:

  • crtl+鼠标点击 进入Thread类
  • crtl+o 找到state方法

就会看到有六种状态:

  • NEW :新建
  • RUNNABL:运行
  • BLOCKED:阻塞
  • WAITING:等待
  • TIMED_WAITING:有时间性的等待
  • TERMINATED:终结

而实际上我们一般也只会说五种状态
下面借用一下尚硅谷老师的图,比较好理解
(就绪就是等待)
在这里插入图片描述

线程的同步

解决线程的安全问题(一):synchronized

当多个线程在操作一个共享数据是,容易发生线程的同步
而线程同步时,多个线程一起操作共享数据,就容易发生数据的错误

下面工程例子;

package com.edu.safe;

/***
 * 线程安全问题
 *
 * 我们加入sleep看一下线程的输入
 * 发现:num并没有按理想状态递减,出现一定的重复,错位
 * 原因:在某个线程在操作共享数据(num)时,另一个线程也参与进来
 * 解决途径:加锁
 *
 * 在java中我们通过同步机制来解决线程的安全问题
 * 解决方式一:
 *   同步代码块
 *     synchronized(同步监视器){
 *         //共享数据操作区
 *     }
 *     说明 同步监视器:俗称,锁。任何一个类的对象都可以当作锁。
 *            隐藏要求:需要同步的线程要公用一把锁。
 *
 *   同步方法
 *   private synchronized void 方法名(){}  注:private、void不是固定的
 *   同步方法的锁是this
 *
 *
 *
 */

class MyThread implements Runnable{
    private int num = 10;
    Object lock = new Object();
    @Override
    public void run() {
        //同步代码块
//        synchronized (lock) {
//            while (num > 0) {
//                //加入休眠
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println(Thread.currentThread().getName() + ":" + num);
//                num--;
//            }
//        }
        num();
    }

    //同步方法
    private synchronized void num(){
        while (num > 0) {
            //加入休眠
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + num);
            num--;
        }

    }
}
public class ThreadSafe {
    public static void main(String[] args) {
     MyThread myThread = new MyThread();

        Thread    t = new Thread(myThread);
        t.start();

        //这里t1与t2线程共享同一个Runnable
        Thread    t2 = new Thread(myThread);
        t2.start();


    }

}

在没有解解决问题时:
出现num重复,错位,甚至=0
在这里插入图片描述

死锁

死锁定义:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁在操系统课程中有更加详细的定义,同时死锁也是一节大课,要想真正的了解死锁,我这里是写不完了,如果想要跟家了解死锁可以直接度娘

死锁需要学习的知识:

  • 死锁产生的必要条件
  • 处理死锁
  • 预防死锁的方法
  • 避免死锁
  • 死锁的解除
  • 还涉及到计算问题:多少个线程需要至少多少个资源才不会产生死锁

线程的安全问题二:Lock

lock() 加锁
unlock() 解锁

package com.edu.safe;
import java.util.concurrent.locks.ReentrantLock;

/***
 * 解决线程的安全问题方法二:Lock
 */
public class ThreadSafe2 {
    public static void main(String[] args) {
        myThread thread = new myThread();
        Thread t = new Thread(thread);
        Thread t2 = new Thread(thread);

        t.start();
        t2.start();
    }
}

class myThread implements  Runnable{
    private  int  num=100;

    //声明锁
    private ReentrantLock lock =new ReentrantLock();

    @Override
    public void run() {
         //加锁
        lock.lock();
        while (num >=0){
            System.out.println(Thread.currentThread().getName() + ":" + num);
            num--;
        }
        //释放锁
        lock.unlock();
    }
}

synchronized 和lock 的区别

synchronized

  • 执行完相应的同步代码后,会自动的释放监视器
  • 可以有同步方法和同步代码块两种
  • 隐式锁

lock

  • 需要手动的加锁和解锁
  • 只有同步代码块,没有同步方法
  • 性能更好,更容易扩展
  • 显式锁

线程阻塞

  • wait(); 阻塞线程
  • notify(); 唤醒一个线程
  • notifyAll(); 唤醒多个线程

wait和sleep的区别

  • sleep 不会释放锁
  • wait只能用在同步块或者同步方法(synchronized)里面,他可以释放锁
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值