java多线程编程核心技术(1)

文章目录

    • Thread类的核心方法
      • 进程
      • 线程
          • 使用案例(继承Thread类)
          • 使用案例(实现Runnable接口)
        • 线程要点补充:
    • 对进程和线程打个比方
    • 非线程安全
    • synchronized关键字的理解
    • 基本的api
    • 终止线程的三种方法
    • stop()方法的缺点
    • suspend()方法+resume()方法的缺点
    • 个人理解

Thread类的核心方法

进程

操作系统中正在运行的QQ.exe程序就是一个进程(它是受操作系统管理的基本运行单元)

线程

独立运行在进程中的子任务(操作系统能够进行运算调度的最小单位)。QQ可以同时运行好友视频,下载文件,传输数据,发送表情等线程。多个 线程的执行是(异步的)随机的。它并不是线性执行的(是并行,而非串行。cpu轮询他的时间是随机的)线程启动的顺序与代码的位置无关,与start()执行顺序无关(线程调用具有随机性)

使用案例(继承Thread类)
public class MyThread extends Thread {
    private static int count=0;
    @Override
    public void run(){
        for(int i=0;i<50;i++){
            count++;
        }
        System.out.println(count);
    }
}
public class Main {

    public static void main(String[] args) {
        MyThread thead1=new MyThread();
        MyThread thead2=new MyThread();
            thead1.start();
            thead2.start();
    }
}

打印结果:
50
100

//因为只循环了50次,cpu光顾一下线程1,就完成了循环。更新了count的值。然后线程2接着做累加。(表面看是串行完成的)
但我们把50改成1000,会发现每次运行的结果都不同[1260,1644],[1316,1316],[1000,2000].这证明了线程的运行是随机的,是异步的。所以往往会出现竞态现象
注意:

启动线程的时候,是thead1.start();调用start()方法,而非调run方法

Thread. java类中的start方法通知“线程规划器”此线程已经准备
就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排
一个时间来调用 Thread中的run()方法,也就是使线程得到运行,启动
线程,具有异步执行的效果。如果直接调用代码 thread.run()就不是异步执行
了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,
而是由main主线程来调用runO方法,也就是必须等 run()方法中的代
码执行完后才可以执行后面的代码。(使用start()方法通知cpu,我准备好了,数据备齐了,内存空间也申请得到了,你可以抽点时间了处理我这个线程所对应的内存中的数据了。至于你来不来,何时来,这都是随机的,它也有可能就不来了)
使用案例(实现Runnable接口)
public class MyThread implements Runnable {//别的一样}
public class Main {
    public static void main(String[] args) {
        MyThread runable1 = new MyThread();
        MyThread runable2 = new MyThread();
        Thread thread1 = new Thread(runable1);
        Thread thread2 = new Thread(runable2);
        thread1.start();
        thread2.start();
    }
}
//runable1.run()这样写的话线程就是同步(串行)的。要启动一个线程,就要和runnable接口打交道,要让线程异步,就要调用Thread.start()方法。

线程要点补充:

任何一个线程都有一个父线程

currentThread 代表的是创建它的那个线程,我们创建的线程都是main函数创建的,意味着我们所有的创建的线程的父线程都是main线程—(main线程由jvm创建),Thread类有8个构造方法,每个都会调用init方法

结论:

      一个线程的创建肯定是由另一个线程完成的

      被创建线程的父线程就是创建他的线程

对进程和线程打个比方

一个进程是一个生产汽车的车间,车间里有很多工人(线程),这些工人共用着车间里的资源(橡胶,钢管,螺母,升降机…)来共同完成生产车轮子,车窗,发动机,方向盘等子任务…最后再进行统一的组装。完成车辆的生产。
一个java进程的内存约等于堆内存+线程数量*栈内存
线程所占的内存空间该被分配到哪,就分配到哪。开启一个线程,程序的内存空间的分配规则没有改变(资源的分配没有改变),只是工人的工作方式变了,车间的生产政策变了。原来是串行生产的,现在改为并行生产了。原来是厂长依次巡视生产线,走到生产车窗的地方,停下来,下令让车窗工人开始动工生产车窗。直到车窗生产好了,才离开,走到下一处,停下来,下令让方向盘工人动工生产方向盘…改进后—厂长开始轮流巡视,全员动工,一会走到车窗工人那,下令动工,一会走到方向盘那,下令动工…
谁慢了,就到它那盯着。就好像银行只有一个窗口,但要处理abcd4个人的业务,a:需要10分钟,b:需要20分钟,c:需要30分钟,d:需要40分钟。串行的策略是排队,依次处理。并行的策略是轮询处理,且将主要精力放在处理a的业务上。(这样平均每个人的等待时间就减少了)

非线程安全

多个线程对同一实例变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程。

synchronized关键字的理解

synchronized是锁定对象或对象的方法,对象或对象的方法都可以理解为资源。就是给资源加锁。
==初态:==资源是完全开发的,对外共享的。锁的个数为0
==现态:==现在很多线程都想对资源进行写操作(专业术语叫获得写锁。即拿到对资源进行写的权限)线程1给资源加了锁,锁的状态变为了1。此刻线程1独占整个资源,别的线程想打这个资源的主意(修改资源),只能是阻塞排队(他们拿不到写锁。因为写锁被线程1占了。只有线程1释放了他的写锁,此时资源又是共享的了,锁的个数又变为0。别的线程才能再拿到属于自己的写锁。)。也只能不断尝试拿到这把锁,直到拿到为止。而且是多线程去竞争这把锁。运气好的,也许线程1结束后,好运气的线程立马就能拿到锁。(获得cpu时间片,cpu再将写的权限赋给好运气的线程)。运气糟的,也许等程序运行结束了,他也没能拿到锁。

基本的api

  • Thread.currendThread().getName()-----获取系统命名的线程的名称
  • Thread.getName()–获取自定义命名的线程的名称
  • isAlive() --线程是否处于活动状态(线程以启动且尚未终止)
  • sleep() 线程休眠—让当前正在执行的线程–(指的是this.currentThread())暂停一段时间再执行
  • getId()获取线程唯一标识
  • Thread.yield() --放弃当前的cpu资源,将他让给其他的任务去占用cou执行时间,但放弃的时间不确定,有可能刚刚放弃,马上又获得cpu时间片
public class Mythread {
   public static void main(String arg[]) {
       new Thread() {
           public void run() {
               long bebginTime = System.currentTimeMillis();
               int count = 0;
               for (int i = 0; i < 50000000; i++) {
                   //Thread.yield();    ---去掉注释:代码运行时间60毫秒
                   //                   ---加上注释:代码运行时间11557毫秒
                   //有没有这行代码,程序运行的时间是不同的
                   count += i + 1;
               }
               long endTime = System.currentTimeMillis();
               System.out.println("用时:" + (endTime - bebginTime) + "毫秒");
           }
       }.start();
   }
}

终止线程的三种方法

停止一个线程意味着在线程处理完当前任务之前,放弃正在进行的操作。(在停掉之前要做好相应的防范措施,否则很容易造成线程不安全问题。所以stop方法最好别用)

  • 使用退出标志
  • 使用stop方法强行停止
  • 使用interrupt方法中断线程(打暂停标志,并非真的停止线程)
    - isInterrupted()和interrupted()方法都是判断的当前线程是否处于中断(停止)状态–指的是主线程。即使你用的myThead对象去调用它,它显示的结果还是主线程的中断状态

stop()方法的缺点

强制让线程停止,则有可能使一些清理性的工作得不到完成。对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题

suspend()方法+resume()方法的缺点

suspend()方法暂停线程,resume()方法恢复线程----二者使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步的对象------常见于让a线程start()后(a线程得到了SynchronizedObjectSole对象的pstring()方法的锁)又suspend暂停a线程,但程序却忘了resume恢复a线程,就相当于a线程得到的那把锁的钥匙丢了。则锁就永远不会被释放了

public class SynchronizedObjectSole {
    synchronized public void pstring(){
        System.out.println("start");
        if(Thread.currentThread().getName().equals("a")){
            Thread.currentThread().suspend();
            System.out.println("a永远suspend");
        }
        System.out.println("end");
    }
}

公共的同步对象的独占代码测试

public class Run {
    public static void main(String arg[]) throws InterruptedException {
            final SynchronizedObjectSole sos=new SynchronizedObjectSole();
            Thread thread1=new Thread(){
                @Override
                public void run(){
                    sos.pstring();
                }
            };
            thread1.start();
            Thread.sleep(1000);
            Thread thread2=new Thread(){
                @Override
                public void run(){
                    System.out.println("线程2永远无法使用sos对象的pstring()方法,因为它被a线程独占了");
                    sos.pstring();
                }
            };
            thread2.start();
    }
}

个人理解

1.Thread可理解为大的线程容器,它提供统一的线程操作(几乎包含了线程所有的api),你可以把thread的子类或runnable接口的实现类丢进Thread的构造函数里面。
2.造成线程不安全的情况

  • 线程发生休眠操作
  • 多线程对同一变量进行修改(尤其是对类变量进行修改)

你可能感兴趣的:(读书笔记,java并发)