Java线程

线程

线程的创建方式一

1、 创建Thread的子类,并重写run()方法,此时run()就是线程要执行的任务

2、 创建Thread的子类对象,并调用其start()方法,启动线程

注: start()方法之后,线程不一定马上启动,只是进入了Runnable状态(就绪或可运行)

案例

public class Demo{

    public static void main(String[] args) {
        //创建线程
        PersonThread personThread1=new PersonThread("personThread1");
        PersonThread personThread2=new PersonThread("personThread2");

        //启动线程
        //personThread1.run();//此方法不能启动线程,只是线程体(任务代码)
        personThread1.start();
        personThread2.start();

    }

}


class PersonThread extends Thread {

    private String name;

    public PersonThread(String name) {
        this.name = name;
    }
    //注:InterruptedException异常不能在run()方法声明时通过throws抛出
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 20; i++) {
            //获取当前任务所在线程的名字 ,Thread-编号,编号从0开始
            System.out.println(Thread.currentThread().getName() + "-" + name + "-" + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果

从运行结果我们可以看到,程序运行并不是顺序的,而是迸发的,还有就是你看不出线程的执行规律。

线程的创建方式二

1、 创建Runnable接口的实现类,并实现run()方法

2、 创建Runnable实现类的对象

3、创建Thread类的对象,并将Runnable实现类的对象,作为Thread的构造方法参数使用

4、 执行Thread类对象的start()方法,来启动线程

这种方式的好处: 可以在多个线程之间共享数据。

案例

public class Demo {
    public static void main(String[] args) {
        //第二步:创建Runnable接口实现类的对象
        Bank bank= new Bank();

        //第三步:创建线程对象,并将第一步创建的对象作为线程的构造方法的参数使用
        Thread t1=new Thread(bank);
        Thread t2=new Thread(bank);

        //此时,四个线程的任务都是同一个对象(Runnable),即任务代码相同

        t1.setName("小鱼");
        t1.start();//第四步:启动线程
        t1.setPriority(Thread.MAX_PRIORITY);//设置线程的优先级,使得线程调度器优先考虑分配CPU资源

        t2.setName("SF");
        t2.start();


    }

}
//1、 创建Runnable接口的实现类,并实现run()方法
class Bank implements Runnable {
    //钱
    int moneyCount = 20;//共享数据
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (moneyCount > 0) {
                System.out.println(Thread.currentThread().getName() + "查看剩余金额:" + moneyCount--);
            } else {
                break;
            }
        }
    }
}

运行结果

线程的运行状态

线程分五大状态,新建、就绪、运行、阻塞(睡眠,等待、IO操作等导致)、死亡;
  • 新建状态:
    当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

  • 就绪状态:
    一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,
    并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

  • 运行状态
    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

  • 阻塞状态
    线程运行过程中,可能由于各种原因进入阻塞状态: 1:线程通过调用sleep方法进入睡眠状态;
    2:调用wait方法,使线程处于等待状态;
    3:线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;线程试图得到一个锁,而该锁正被其他线程持有;
    ....
    所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

  • 死亡状态
    1:run方法正常退出而自然死亡,
    2:一个未捕获的异常终止了run方法而使线程猝死。
    为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true;
    如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

图解线程运行状态

案例

      /**
       * 线程的状态:
       * 1、 创建状态-NEW
       * 2、 就绪状态--Runnable
       * 3、 运行状态--Running
       * 4、 阻塞状态--BLOCK(WAITING,TIMED_WAITING,BLOCK)
       * 5、 结束状态--TERMINATED
       */
      public class Demo {
          public static void main(String[] args) {
              //创建子线程并启动
              ChildThread childThread= new ChildThread();
              childThread.setName("自定义线程名");

              //显示当前方法所在的线程名
              System.out.println(Thread.currentThread().getName()
                      +"-"+Thread.currentThread().getId());

              System.out.println("ChildThread-->"+childThread.isAlive());

              childThread.start();//注:start()方法只能执行一次

              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //显示某一线程是否还“存活”:boolean isAlive()
              System.out.println("ChildThread-->"+childThread.isAlive());
          }
      }

      class ChildThread extends Thread {
          @Override
          public void run() {
              //获取 当前的线程
              Thread t = Thread.currentThread();

              for (int i = 0; i < 10; i++) {
                  System.out.println(t.getId() + "-" + t.getName() + "-" + t.isAlive() + ":" + i);

                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }

线程安全问题

多个线程共享数据时,可以出现数据不一致性问题,由于操作共享数据的语句有多条,当线程执行中,由于Cpu被其它线程占用,使得操作停止,当再次获取Cpu时,
数据可能已发生的变化,因此造成了数据不一致

解决方式,加锁

锁(对象锁):Java对象中存一个标志("互斥锁"),保证对象在同一时刻,只能有一个线程去使用(访问)它 。

  • 一个线程访问加锁的对象,其它线程只能等这个线程释放锁后 ,才能访问。
  • 注: 加锁操作后,由于其它线程不停地判断锁是否释放(解锁),所以会影响执行效率

加锁的方式

  • 同步非静态方法:
    在方法声明时,增加synchronized修饰符,针对this对象加锁,如果一个线程访问了这个方法,其它线程在访问this对象的同步方法时,会进入等待状态,
    直到这方法执行完成后,才能访问。
  • 同步静态方法:在静态方法声明时,增加synchronized修饰,针对.Class对象加锁,如果一个线程访问了这个方法,其它线程在访问这个类的同步静态方法时,
    会进入等待状态,直到这方法执行完成后,才能访问
  • 同步代码块

案例一同步方法

      public class Demo {

          //线程中访问的共享数据对象
          static class Test {
              static int nums = 10;

              //增加同步静态方法: 对Class类进行加锁,在同一时刻只有一个线程使用此方法
              static synchronized void sop() {
                  while (true) {
                      if (nums > 0) {
                          try {
                              Thread.sleep(200);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(Thread.currentThread().getName() + ":" + (nums--));
                      } else {
                          nums = new Random().nextInt(5);
                          break;
                      }
                  }
              }

              public void show() {
                  System.out.println(Thread.currentThread().getName() + ":" + (nums--));
              }
          }

          //定义线程的任务类
          static class MyRunnable implements Runnable {

              @Override
              public void run() {
                  Test.sop();
              }
          }

          public static void main(String[] args) {
              MyRunnable myRun = new MyRunnable();
              Thread t1 = new Thread(myRun);
              Thread t2 = new Thread(myRun);
              t1.start();
              t2.start();

              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              Test t = new Test();
              System.out.print("访问非同步的方法(main方法):");
              t.show();
          }

      }

运行结果

同步代码块

存在同步代码区域,这个区域主要是共享数据的操作(多条语句)

案例二同步代码块

    public class Demo{
        static class MyTask implements Runnable {
            int nums = 10;

            @Override
            public void run() {
                while (true) {
                    //增加同步代码块
                    synchronized (this) {
                        if (nums > 0) {
                            try {
                                Thread.sleep(500);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + ":" + (nums--));
                        } else {
                            break;
                        }
                    }

                }
            }
        }

        /**
         * 不出意外 应该是线程1首先获取到CPU资源,所以run执行完后,nums=0;t2刚开始运行就跳出了。
         * @param args
         */
        public static void main(String[] args) {
            MyTask task = new MyTask();
            Thread t1 = new Thread(task);
            t1.start();

            Thread t2 = new Thread(task);
            t2.start();
        }

    }

运行结果

案例三

    public class Demo {
        // 数据类:资源
        static class Bank {
            private int money;

            public Bank(int money) {
                this.money = money;
            }

            // 为方法加同步锁,如果一个线程进入这个方法,则其它线程只有等到这个线程将方法执行完成之后,才能执行
            public synchronized void add(int money) {
                this.money += money;
                show();
            }

            // 取钱
            public synchronized void sub(int money) {
                if (this.money >= money) {
                    this.money -= money;
                    show();
                } else {
                    System.out.println(Thread.currentThread().getName()
                            + "->当前的存款不足" + money);
                }
            }

            public void show() {
                System.out.println(Thread.currentThread().getName() + "->当前的存款:"
                        + money);
            }
        }

        // 线程的任务类
        static class BankAddTask implements Runnable {
            private Bank bank;

            public BankAddTask(Bank bank) {
                this.bank = bank;
            }

            @Override
            public void run() {
                // 存入2000
                bank.add(2000);
            }
        }

        static class BankSubTask implements Runnable {
            private Bank bank;

            public BankSubTask(Bank bank) {
                this.bank = bank;
            }

            @Override
            public void run() {
                // 取出2000
                bank.sub(2000);
            }
        }

        public static void main(String[] args) {
            Bank bank = new Bank(1000);
            BankAddTask bankAddTask = new BankAddTask(bank); //存钱
            BankSubTask bankSubTask = new BankSubTask(bank); //取钱
            Thread t1 = new Thread(bankAddTask);
            t1.setName("存钱");
            t1.start();
            Thread t2 = new Thread(bankSubTask);
            t2.setName("取钱");
            t2.start();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            bank.show();

        }

    }

运行结果

死锁

一个线程拿到A资源对象锁后,还想要等着拿到B资源的对象锁,另一线程拿到B资源对象锁后,还想要等着拿到A资源的对象锁(吃着碗里的,看着锅里的)

案例

    public class Demo {

        public static void main(String[] args) {
            MyRunnable myRunnable=new MyRunnable();
            Thread thread1=new Thread(myRunnable,"线程A");
            Thread thread2=new Thread(myRunnable,"线程B");
            thread1.start();;
            thread2.start();
        }

    }

    class MyRunnable implements Runnable {
        boolean flag = true;
        Object object1 = new Object();
        Object object2 = new Object();

        @Override
        public void run() {
            if (flag) {
                flag = false;
                synchronized (object1) {
                    System.out.println(Thread.currentThread().getName() + "得到资源A");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //又想得到资源B
                    synchronized (object2) {
                        System.out.println(Thread.currentThread().getName() + "想要得到B");
                    }
                }
            } else {
                flag = true;
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName() + "得到资源B");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //又想得到资源A
                    synchronized (object1) {
                        System.out.println(Thread.currentThread().getName() + "想要得到A");
                    }
                }
            }
        }
    }

运行结果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,292评论 18 399
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,376评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,921评论 1 18
  • 本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获。由于个人水平有限,不对之处还望...
    Steven_cao阅读 880评论 0 2
  • 什么是数组 数组是特殊的变量,它可以同时保存一个以上的值。数组能够在单一变量名中存储许多值,并且能够通过引用下标号...
    NoFacePeace阅读 119评论 0 0