什么是进程

  1. 进程是指运行中的程序

  2. 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程

解释:电脑打开一个程序,程序一运行就是进程,进程会占用内存空间,关闭程序。内存释放

什么是线程

  1. 线程时有进程创建的,是进程的一个实体

  2. 一个进程可以拥有多个线程

解释:打开迅雷是一个进程,迅雷同时下载多个资源,为多个线程

什么是单线程和多线程

  1. 单线程:同一时刻,只允许执行一个线程

  2. 多线程:同一时刻,可以同时执行多个线程

解释:迅雷同时下载多个资源,为多个线程—多线程

什么是并发和并行

  1. 并发:同一时刻,多个任务交替执行。也就是单核CPU实现的多任务就是并发

  2. 并行:同一时刻,多个任务同时执行,多核CPU可以实现并行

解释

  1. 并发:一个CPU来回执行多个程序

  2. 并行:两个CPU同时各自实现自己的一个程序

  3. 并发和并行同时存在:两个CPU同时各自实现自己的多个程序

扩展:

获取自己电脑上有几个CPU

Runtime runtime = Runtime.getRuntime();
int cpunums = runtime.availableProcessors();

创建线程

  1. 继承Thread类,重写run方法

  2. 实现Runnable接口,重写run()方法

入门案例1—继承Thread类

要求:1. 编写程序,开启一个线程,该线程每个1秒。在控制台输出“喵喵,我是小猫咪”,当输出80次后,结束该进程

package threaduse;

//通过继承Thread类,创建线程
public class Thread01 {
   public static void main(String[] args) {
       cat cat = new cat();
       cat.start();
  }
}

class cat extends Thread {
   @Override
   public void run() {
       int c = 0;
       while (true) {
           System.out.println("喵喵,我是一只小猫咪"+"第"+(++c)+"次"+Thread.currentThread().getName());
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(c==80){
               break;
          }
      }
  }
}

多线程机制

  1. 执行代码:开启进程

  2. 执行main方法:开启main线程

  3. main方法执行线程类的start方法:开启线程类的线程

  4. main线程启动了线程类,不会发生阻塞,会和线程类的线程交替执行

需知点

  1. main线程会和线程类线程同时运行

  2. main线程可以继续开其他线程,线程类也可以开其他线程

  3. main线程结束、线程类线程不会结束

  4. 进程中的全部线程结束,进程才会消失

为什么调用是start方法,而不是run方法

  1. 调用start方法是启动线程—》最终会调用线程类的run方法

  2. 直接调用run方法的话,run方法其实就是一个普通的方法,没有真正的启动线程,会把run方法执行完,才会先下执行

  3. 真正实现多线程的效果是start0方法,而不run方法

步骤是:

  1. start方法

  2. start0方法 start0()是本地方法,由JVM调用

  3. 实际上是:在start0方法里面用多线程的机制来调用run方法

入门案例—实现Runnable接口

说明:java是单继承的,在某些情况下,一个继承了某个父类,就不能再继承Thread类,这就用到了接口

需求:请编写程序,该程序可以每隔1秒,在控制台输出hi,输出10次后,自动退出,用Runnable接口方式实现

package threaduse;

public class Runnable01 {
   public static void main(String[] args) {
       Dog dog = new Dog();
       Thread thread = new Thread(dog);
       thread.start();
  }
}
class Dog implements Runnable{
   @Override
   public void run() {
       int times = 0;
       while (true) {
           System.out.println("hi" + (++times));
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(times==10){
               break;
          }
      }
  }
}

需知点:

  1. 因为Dog类没办法调用start方法

  2. 所以创建了Thread类,把dog对象放入Thread对象中,就可以调用run方法了,因为这里底层使用设计模式【代理模式】

代码模拟 了解代理模式

解释为啥可以把dog对象放入Thread对象中,就可以调用run方法了

package threaduse;

public class Runnable01 {
   public static void main(String[] args)
       Tiger tiger = new Tiger();
       ThreadProxy threadProxy = new ThreadProxy(tiger);
       threadProxy.start();//1.
  }
}
class Animal{}
class Tiger extends Animal implements Runnable{
   @Override
   public void run() {//6
       System.out.println("嗷嗷叫");
  }
}
//模拟了一个极简的Thread类
class ThreadProxy implements  Runnable{
   private Runnable target = null;
   @Override
   public void run() {//5.
       if(target != null){
           target.run();//target 是Runnable类型的
      }
  }

   public ThreadProxy(Runnable target) {//2
       this.target = target;
  }
   public void  start(){//3
       start0();
  }
   public void start0(){//4
       run();
  }
}

入门案例—多线程执行

需求:编写一个程序,创建两个线程,一个线程每隔1秒输出“HelloWorld”,输出10次,退出。一个线程每隔1秒输出“Hi”,输出5次退出

package threaduse;

public class Thread02 {
   public static void main(String[] args) {
       T t = new T();
       TT tt = new TT();
       Thread thread1 = new Thread(t);
       Thread thread2 = new Thread(tt);
       thread1.start();
       thread2.start();
  }

}
class T implements  Runnable{
   @Override
   public void run() {
       int a = 0;
       while (true){
           System.out.println("Hello,World"+(++a));
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(a==10){
               break;
          }
      }
  }
}
class TT implements Runnable{
   @Override
   public void run() {
       int a = 0;
       while (true) {
           System.out.println("Hi" + (++a));
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           if(a==5){
               break;
          }
      }
  }
}

线程终止

  1. 当线程完成任务后,会自动退出

  2. 通过使用变量来控制run方法退出的方式停止线程,即通知方式

package threaduse;


public class ThreadExit {
   public static void main(String[] args) throws InterruptedException {
       T1 t1 = new T1();
       Thread thread = new Thread(t1);
       thread.start();

       Thread.sleep(10*1000);
       //如果希望main线程去控制t1 线程的终止,必须修改a的值
       t1.setA(false);

  }
}
class T1 implements  Runnable{
   int b = 0;
   private boolean a = true;
   @Override
   public void run() {
       while (a){
           System.out.println("你好"+(++b));
           try {
               Thread.sleep(50);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
   public void setA(boolean a) {
       this.a = a;
  }
}

线程常用方法

  • 常用方法1

    1. setName 设置线程名称,使之与参数的name相同

    2. getName 返回线程的名称

    3. start 使线程开始执行;java虚拟机底层调用该线程的start0方法

    4. run 调用线程对象run方法

    5. setPriority 更改线程的优先级

    6. getPriority 获取线程的优先级

    7. sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

    8. interrupt 中断线程

  • 常用方法1注意点

    1. start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程

    2. 线程的优先级范围 MAX_PRIORITY 10 MIN_PRIORITY 1 NORM_PRIORITY 5

    3. interrupt,中断线程,但并没有真正的结束线程。一般用于中断正在休眠的线程

    4. sleep:线程的静态方法,使当前线程休眠

  • 常用方法1代码实现

     package threaduse;

    public class ThreadMethod01 {
       public static void main(String[] args) throws InterruptedException {
           T2 t2 = new T2();
           t2.setName("常世超");
           t2.setPriority(Thread.MIN_PRIORITY);
           t2.start();//启动子线程

           //让主线程打印5次,中断子线程的休眠
           for (int i = 0; i <5 ; i++) {
               Thread.sleep(1000);
               System.out.println("hi"+i);
          }
           t2.getPriority();
           t2.interrupt();//当执行到这里 就会中断t2线程的休眠
      }
    }
    class T2 extends  Thread{
       @Override
       public void run() {
           while (true) {
               for (int i = 1; i <= 100; i++) {
                   System.out.println(Thread.currentThread().getName() + "吃" + i);
              }
               try {
                   Thread.sleep(1000*20);
              } catch (InterruptedException e) {
                   //当线程执行到了一个interrupt方法,就会catch 一个异常
                   System.out.println("中断线程");
              }
          }
      }
    }
  • 常用方法2

    1. yield :线程的礼让,让其他线程执行,但礼让的时间不确定,所以不一定礼让成功

    2. join :线程插队,插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务

  • 常用方法2代码实现

    package threaduse;

    public class ThreadMethod02 {
       public static void main(String[] args) throws InterruptedException {
           T3 t3 = new T3();
           Thread thread = new Thread(t3);
           thread.start();
           for (int i = 1; i <= 20; i++) {
               Thread.sleep(1000);
               System.out.println("main正在进行第" + i + "次冲刺");
               if (i == 10) {
                   thread.yield();//线程的礼让
    //               thread.join();//线程插队
              }
          }

      }
    }

    class T3 implements Runnable {
       @Override
       public void run() {
           for (int i = 1; i <= 20; i++) {
               System.out.println("T3正在进行第" + i + "次冲刺");
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }

      }
    }
  • 常用方法3

    1. 用户线程:也叫工作线程,当线程的任务执行完或者通知方式结束

    2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

    3. 常见的守护线程:垃圾回收机制

  • 代码实现

    package threaduse;

    public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
    A2 a2 = new A2();
    a2.setDaemon(true);
    a2.start();
    for (int i = 1; i <=10 ; i++) {
    System.out.println("我是用户线程"+i);
    Thread.sleep(1000);
    }

    }
    }
    class A2 extends Thread{
    @Override
    public void run() {
    for ( ; ;) {
    System.out.println("我是守护线程");
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

     

常用方法练习题

package threaduse;
/*1. 主线程每个1秒,输出hi,一共10次
* 2.当输出到 hi 5时 ,启动一个子线程(要求实现Runnable接口),每个1s输出hello,等该线程输出10次后,退出
* 3.主线程继续输出hi,直到线程结束*/
public class ThreadMethodTest {
public static void main(String[] args) throws InterruptedException {
A a = new A();
Thread thread = new Thread(a);
for (int i = 1;i<=10;i++){
Thread.sleep(1000);
System.out.println("hi"+i);

if(i==5){
thread.start();
thread.join();
}
}
}
}
class A implements Runnable{
@Override
public void run() {
for (int i = 1; i <=10 ; i++) {
System.out.println("hello"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

线程的生命周期

  • NEW 至今尚未启动的线程处于这种状态。 —创建状态

  • RUNNABLE 正在 Java 虚拟机中执行的线程处于这种状态。 —-可运行状态

    • Ready—-就绪状态

    • Running—-运行状态

  • BLOCKED 受阻塞并等待某个监视器锁的线程处于这种状态。 —-阻塞状态

  • WAITING 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。 —等待状态

  • TIMED_WAITING 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。 —超时等待状态

  • TERMINATED 已退出的线程处于这种状态。

售票案例引出线程同步锁

出现的问题:

  • 问题1.重卖:一张票卖给了多个人

  • 问题2.超卖:出现了票数为0甚至是负数的情况

package threaduse;

public class Mai {
public static void main(String[] args) {
Csc csc = new Csc();
Thread thread1 = new Thread(csc);
Thread thread2 = new Thread(csc);
Thread thread3 = new Thread(csc);
thread1.start();
thread2.start();
thread3.start();
}
}
class Csc implements Runnable{
int a = 100;
@Override
public void run() {
while (true){
if(a<=0) {
System.out.println("售票结束");
break;
}
try {
//让程序休眠后出现的两个问题:
//问题1.重卖:一张票卖给了多个人
//问题2.超卖:出现了票数为0甚至是负数的情况
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"\t"+(--a));
}
}
}

同步锁

  • 判断程序有没有出现线程安全问题:

    在多线程程序中 + 有共享数据 + 多条语句操作共享数据

  • 同步与异步:

    • 同步:体现了排队的效果,同一时刻只能有一个线程占用资源,其他没有权限的线程排队

      坏处:效率低,

      好处:安全

    • 异步:体现了多线程抢占资源的效果,线程间互想不等待,互相抢占资源

      坏处:有安全隐患

      好处:效率高

  • 同步锁关键字:synchronized

    • 实现同步具体方法
      1. 同步代码块

        synchronized (锁对象){//使用的锁对象类型任意,但注意:必须唯一!
        //需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
        }
      2. 同步方法

        public synchronized void m(){
        //需要同步的代码
        }
    • 使用同步锁的前提
      1. 同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)

      2. 多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

    • 锁对象

      • (非静态的)同步方法的锁可以是this,也可以是其他对象(要求是同一对象)

            public synchronized void aVoid(){
        System.out.println("我爱你");
        }
        //解释(非静态的)同步方法的锁可以是this,也可以是其他对象(要求是同一对象)
        public void a(){
        Object o = new Object();
        synchronized (this){
        // synchronized (o){
        System.out.println("我爱你");
        }
        }

         

      • (静态的)同步方法的锁对象是当前类本身

         public static synchronized void a(){
        System.out.println("我爱你");
        }
        //解释静态的同步方法锁对象时类本身
        public static void a(){
        synchronized (Csc.class){
        System.out.println("我爱你");
        }
        }

         

      • 同步代码块的锁:

        @Override  
        public void run() {
        Object o = new Object();
        synchronized (o) {}
        }
        @Override
        public void run() {
        synchronized (this) {}
        }

线程的死锁

  • 介绍

    多个线程都占用了对方的锁资源、但不肯想让,导致了死锁,在编程是一定要避免死锁的发生

  • 应用案例

    妈妈:你先写作业,我让你玩手机

    儿子:你先让我玩手机,我就写作业

  • 代码实现

    package threaduse;

    public class DeadLock {
    public static void main(String[] args) {
    DeadLockDemo A = new DeadLockDemo(true);
    DeadLockDemo B = new DeadLockDemo(false);
    A.setName("线程A");
    B.setName("线程B");
    A.start();
    B.start();
    }
    }
    class DeadLockDemo extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag ;
    public DeadLockDemo(boolean flag) {
    this.flag = flag;
    }

    @Override
    public void run() {
    /*下面有业务逻辑
    * 1.如果flag 为T ,线程A 就会先得到/持有 o1 对象锁,然后尝试去获取o2 对象锁
    * 2.如果线程A 得不到 o2 对象锁,就会Blocked
    * 3.如果flag 为F , 线程B就会就会先得到/持有 o2 对象锁,然后尝试去获取o1 对象锁
    * 4.如果线程B 得不到 o1 对象锁,就会Blocked*/
    if(flag) {
    synchronized (o1) {
    System.out.println(Thread.currentThread().getName()+"进入状态1");
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName()+"进入状态2");
    }
    }
    }else {
    synchronized (o2) {
    System.out.println(Thread.currentThread().getName()+"进入状态3");
    synchronized (o1) {
    System.out.println(Thread.currentThread().getName()+"进入状态4");
    }
    }
    }
    }
    }

释放锁

  • 下面操作会释放锁

    1. 当前线程的同步方法、同步代码块执行结束

      案例:上厕所,完事出来

    2. 当前线程在同步方法、同步代码块中遇到break、return。

      案例:没有正常完事,经理叫他修BUG,不得不出来

    3. 当前线程在同步方法、同步代码块中出现了未处理的Error或Exception,导致异常结束

      案例:没有正常完事,发现为带纸,不得不出来

    4. 当前线程在同步方法、同步代码块中执行了wait()方法,当前线程暂停,并释放锁

      案例:没有正常完事,觉得需要酝酿一下,所以出来等会再进去

  • 下面操作不会释放锁

    1. 线程执行同步方法、代码块时,程序调用了Thread.sleep()、Thread.yield()方法暂停当前线程的执行、不会释放锁

      案例:上厕所,眯一会

    2. 线程执行同步方法、代码块时,其他线程调用了suspend()方法将线程挂起,该线程不会释放锁

      提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

    3.  

版权声明:本文为csc0712原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/csc0712/p/15256990.html