关于线程学习总结

线程与进程的区别

  • 进程可以理解成一个正在运行的程序,进程占有独立的内存空间。如:打开QQ。
  • 线程是进程里的一个个分支,共享一个内存空间,线程是并发执行的,如果一个进程内不存在任何线程,那么这个进程会消失。
  • 如果一个进程同时执行多个线程,那么就是多线程。如打开QQ的两个功能

线程有几个状态

  • 新建:当一个 Thread 类或其子类被创建声明后,新生的线程对象就处于新建状态。
  • 就绪:当处于新建状态的线程对象调用 start 方法以后,它就进入线程队列等待 cpu 时间片,此时线程对象已经具备了运行条件,只是没有得到 cpu 资源。
  •  运行:处于就绪状态的线程对象获得 cpu 资源以后就可以进入运行状态,run 方法 定义了线程的任务。
  •  阻塞:在某些特殊情况下,被人为挂起或执行输入输出操作的时候,线程对象让出 自己的 cpu 并终止任务进入阻塞状态。
  •  死亡:线程对象完成了它的线程任务或者被强制退出、发生异常等,线程对象就进 入死亡状态。

线程调度

分时调度:线程轮流使用CPU,每一个线程使用到的时间是平均的。

抢占式调度:顾名思义,哪个线程抢到就是谁的,可以设置优先级,增加或减少某个线程获取到的概率,java用的就是抢占式调度。

调度不会增加程序的运行速率,因为在某个时刻CPU一个核心只能处理一个线程,来回切换让用户感觉好像是在同时执行,但是这样会提高CPU的运行效率,增加CPU的使用率。

并发与并行

并发:线程在同一个时间段发生。即一个cpu执行多个任务。
并行:线程在同一时刻发生。即多个cpu执行多个任务。

 创建多线程

可以通过Thread 类和 Runable Callable 接口去实现。继承 Thread 类或者去实现 Runable Callable 接口。(extend Thread / implements Runable/implements Callable)

 Thread继承类创建


public class ThreadCreat1  {
    public static void main(String[] args) {
        Myhread1 myhread1=new Myhread1();
        //myhread1.start();//用于开启一个线程,并且start方法只能调用依次
        //start有两个功能:1.让线程进入等待队列  2.调用run方法
        myhread1.run();
        myhread1.start();
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"线程打印--->"+i);
        }
    }
}
class  Myhread1 extends Thread{
    //run方法里面存放的是线程任务
    public void run(){
        for (int i=0;i<10;i++){
            //Thread类提供了一个静态方法名字叫做getName()用于获取线程名称
            System.out.println(getName()+"线程打印--->"+i);
        }
    }
}

 我们通过继承Tread创建线程,利用start()方法开始线程,这里注意:run()方法代表mythread1对象调用类方法开始运行。而start()方法才是表示开始线程。运行结果也大不相同。

run():  start():

这里可以看出线程调度是不可控的,所以线程执行也是随机的,不可控制的。

那么什么是线程调度:

 线程调度有两种:
1.分时调度
分时调度就是所有线程轮流拥有(使用)cpu的使用权,平均分配每个线程占用cpu的时间
2.抢占式调度
抢占式调度就是优先让优先级高的线程使用cpu,如果线程的优先级相同,则会随机选择一个,所以谁的优先级高,谁抢夺cpu的几率就越大,从而优先级高的占用cpu的时间会更长,Java为抢占式调度.
抢占式调度使cpu会在多个进程中、多个线程中来回跳转,其速度非常快。

 Thread类的常见方法:

  • start():1.启动当前线程 2.调用线程中的 run 方法(一个线程只能调用依次start方法)
  •  run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的操作声明在此方
  • 法中(不会开启新的线程)
  •  currentThread():静态方法,返回执行当前代码的线程
  •  getName():获取当前线程的名字
  •  setName():设置当前线程的名字
  •  yield():(从运行到就绪)可以让当前线程释放时间碎片,进入就绪状态,进入就绪状态,重新等待时间碎片
  •  join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执 行完毕以后,该线程才继续执行下去
  • stop():过时方法。当执行此方法时,强制结束当前线程。
  •  sleeplong millitime):线程休眠一段时间

线程优先级

给线程制定一个优先级,高优先级的线程抢占 cpu 的概率更高,
它们被分为 10 个等级,其中最为常用的是最高优先级 10 、最低优先集 1 、默认优先级 5
如下所示:
  • 最低优先级 1Thread.MIN_PRIORITY
  • 最高优先级 10Thread.MAX_PRIORITY
  • 普通优先级 5Thread.NORM_PRIORITY
如果我们要去查看和设置一个线程的优先级,我们可以通过 Thread 类提供的 getPriority()
setPriority() 方法,其中 getPriority() 用于查看一个线程的优先级,而 setPriority() 则是用于
去设置一个线程的优先级。

这里要注意,这和HTML5中的index级别不同,它不代表级别越高就一定会总是优先执行,它仅代表比它优先级低的线程有更大的几率获得时间碎片,启动线程。

Runable接口 

Runable和Thread没什么区别,本质上只是实现关系

public class Model {
    public static void main(String[] args) throws InterruptedException {
        RunableTest runableTest = new RunableTest();
        Thread thread = new Thread(runableTest, "线程一");
        Thread thread1 = new Thread(runableTest, "线程二");
        thread.start();
        thread.join();
        thread1.start();
    }
}
class RunableTest implements Runnable {

    @Override
    public void run() {
    /*
    currentThread()Thread提供的方法,返回当前线程类对象
    */
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

这里可以看出,Runable接口实现线程是通过借用了Thread来实现的,Runable必须使用run方法。我们通过RunableTest类实现接口Runable,重写run方法,再在main方法中创建RunableTest对象,这里注意因为是通过接口实现,所以不能直接调用start()方法,要将RunableTest对象放进Thread类创建的空间中,间接通过Thread实现线程。

Runnable实现的线程可以放入线程池,Thread不行。

Callable接口

Callable Thread 类、 Runable 接口一样都是创建线程的方式,其中 Thread 类和 Runable
接口通过 run 方法实现线程的任务, Callable 接口通过 callable 方法实现线程的任务,并且
callable 方法是有返回值的。

 线程安全

线程安全问题也就是当多个线程去操作 共享数据的时候出现了共享数据的冲突,此时线程时不安全的。

比如:售票问题。三个窗口共享100张票(即三个线程),100张票就是三个线程所共享的数据,因为线程进行是随机不可控的,谁先抢到时间碎片就先进行,但此时会出现某一线程在处理数据时,另外一个线程又加入进来处理共享数据,此时会造成多个线程同时操作同一张票即重票,这个时候线程就属于不安全。

那么怎样解决线程安全问题呢?

需要采取一定措施,也就是说一个线程在操作数据的时候其它线程不能参与进来,只能当前线程某个操作完成以后才可以 让其它线程参与进来即线程同步。

Synchronized与Lock

这两个都可以用来解决线程安全问题,利用他们实现锁住功能,即将其内的内容锁住,只允许一个线程完成后另外一个才能进来。

Synchronized可同步代码块或方法,它是自动锁的

同步代码块:

public class Test000 {
    public static void main(String[] args) {
        Object o=new Object();
        Station1 s1=new Station1("售票口1",o);
        Station1 s2=new Station1("售票口2",o);
        Station1 s3=new Station1("售票口3",o);
        s1.start();
        s2.start();
        s3.start();
    }
}
class Station1 extends Thread{
    private Object o;

    public Station1() {
    }

    public Station1(String name, Object o) {
        super(name);
        this.o = o;
    }

    static int j=1;
    @Override
    public void run(){
        while (true){
            synchronized (o) {
                if(j>100){
                    System.out.println("售罄");
                    break;
                }
                System.out.println(getName() + "正在售卖第" + j + "张票" + " " + "还剩下" + (100 - j) + "张票");
                j++;
            }
        }
        
    }
}

将售票手续逻辑实现的代码块同步锁,此时只有当线程完成里面的操作,另一个线程才能进来。这里注意:

synchronized()里面的参数列表在Thread类进行线程时可以:

1.在Statino类中定义私有private Object o;创建Station含参构造,在main方法中创建Object对象o和Station类对象si,将Object对象放入Station类对象空间,此时synchronized()参数列表为对象o。

2.直接在Station类中定义private static Object o=new Object();,将o放入synchronized()中。

3.直接在synchronized()中放入Station.class//(0类.class)

synchronized()里面的参数列表在利用Runable接口实现线程时可以:

也有三种方法,前两种和Thread中的一样。

第三种:在synchronized()里面直接放this。

但synchronized会造成死锁。


public class DeathSynchronized {
    public static void main(String[] args) {
        Thread007 t1 = new Thread007("线程一",1);
        Thread007 t2 = new Thread007("线程二",2);
        t1.start();
        t2.start();
    }
}

    class Thread007 extends Thread{

        private static Object object1 = new Object();
        private static Object object2 = new Object();
        private int i;

        public Thread007(String name,int i){
            super(name);
            this.i=i;
        }

        @Override
        public void run() {
           if (i==1){
               synchronized (object1){
                   System.out.println(getName()+"拿到了锁1");
                   try {
                       sleep(300);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(getName()+"尝试拿取锁2");
                   synchronized (object2){
                       System.out.println(getName()+"拿到了锁2");
                   }
               }
           }else {
               synchronized (object2){
                   System.out.println(getName()+"拿到了锁2");
                   try {
                       sleep(300);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(getName()+"尝试拿取锁1");
                   synchronized (object1){
                       System.out.println(getName()+"拿到了锁1");
                   }
               }
           }

        }
    }

因为线程一拿到锁一,线程一拿到锁二,但此时两个线程又要互相访问双方的锁,锁得不到释放,两线程就一直处于等待状态,造成死锁

 Lock

它有两种方式:

一:


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

public class LockTest1 {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("线程1");
        MyThread myThread2 = new MyThread("线程2");
        myThread1.start();
        myThread2.start();
    }
}
class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    private static Lock lock=new ReentrantLock();
    @Override
    public void run(){
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println(getName()+"----->"+i);
            }
        }finally{
            lock.unlock();//lock和unlock之间是被保护的代码
        }
    }
}

lock是手动形式的。

我们在操作 Lock 锁需要注意的是 Lock 锁需要手动释放,如果发生了异常锁也不会被
释放,需要我们手动释放,对此我们在使用 Lock 锁时就需要将代码放在放在 try-finally
常处理机制中。

lock.lock();与lock.unlock();中的内容是被保护的代码。 

 trylock

tryLock(long time, TimeUnit unit) 方法和 tryLock() 方法是类似的,只不过区别在于这个方法在
拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回 false 。如果一开始
拿到锁或者在等待期间内拿到了锁,则返回 true

public class LockTest2 {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2("线程1");
        MyThread2 myThread20 = new MyThread2("线程2");
        MyThread2 myThread21 = new MyThread2("线程3");
        myThread2.start();
        myThread20.start();
        myThread21.start();
    }
}
class MyThread2 extends Thread{
    public MyThread2() {
    }

    public MyThread2(String name) {
        super(name);
    }

    private static Lock lock=new ReentrantLock();
    @Override
    public void run(){
       
        boolean flag= false;
        try {
            flag = lock.tryLock(20000, TimeUnit.MICROSECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(flag){
                try {
                    System.out.println(getName()+"获得锁");
                }finally {
                    System.out.println(getName()+"释放锁");
                    lock.unlock();
                }
            }else{
                System.out.println(getName()+"加锁失败!");
            }

    }

}


        tryLock( ):和Lock ()方法一样也是用于加锁,只是它有返回值:
        若加锁成功则返回true,否则返回false
        需要注意的是若加锁失败就不能去unLock解锁
        tryLock(Long time,TimeUnit unit) :
        和tryLock类似,只是该方法加入了时间即规定时间内加锁成功就返回true,
        否则返回false该方法会抛出一个异常即InterruptedException
 

 线程通信

一个线程可以去完成属于自己的任务,如果我们想要多个线程按照规则去协同执行任务
就需要使用到线程通信。
利用wait()、join、sleep等方法去实现线程之间的一个交互wait和notify配合使用。
wait ()利Inotify必须放在Synchronized同步方法或者代码块里面 通过锁对象去调用wait利notify方法。

notifyalL ()利Inotify ():notify( )---用于唤醒单个线程,notifyaLL()---用于唤醒所有线程。

wait和sleep区别:
        sleep不会释放锁,wait会
        sleep到时间后自动唤醒,wait需要notify手动huanx

    public static void main(String[] args) {
        Object o=new Object();
        T t1=new T("线程1",o);
        T t2=new T("线程2",o);
        t1.start();
        t2.start();

    }
}
class T extends Thread {

    private static int num=1;
    private Object o;
    public T() {
    }

    public T(String name, Object o) {
        super(name);
        this.o = o;
    }

    //private static Object object=new Object();
    @Override
    public void run() {
        while (true){
             synchronized (o){
                 o.notify();//notify用于唤醒另外一个被wait的线程
                 if (num<=20){
                     System.out.println(getName()+"----->"+num);
                     num++;
                     try {
                         o.wait();//用于将当前线程处于阻塞状态,并且释放锁
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }else{
                     break;
                 }
             }
           }

    }
}

*线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象。

线程池的优点:降低资源消耗、提高响应速度、方便管理;线程可以复用、可以控制最大并发数、可以管理线程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值