创建两个分线程,干不同的事情

    1. package com.codeday22.demo01;
    2. /**
    3. * 创建两个线程,一个遍历100内偶数,另外一个遍历100内奇数
    4. *
    5. */
    6. public class ThreadDemo {
    7. public static void main(String[] args) {
    8. // MyThread1 t1 = new MyThread1();
    9. // t1.start();
    10. //
    11. // MyThread2 t2 = new MyThread2();
    12. // t2.start();
    13. // 也可以创建Thread的匿名子类
    14. new Thread(() -> {
    15. for (int i = 0; i < 100; i++) {
    16. if (i % 2 == 0){
    17. System.out.println(Thread.currentThread().getName() + ":" + i);
    18. }
    19. }
    20. }).start();
    21. new Thread(){
    22. @Override
    23. public void run() {
    24. for (int i = 0; i < 100; i++) {
    25. if (i % 2 == 1){
    26. System.out.println(Thread.currentThread().getName() + ":" + i);
    27. }
    28. }
    29. }
    30. }.start();
    31. }
    32. }
    33. //class MyThread1 extends Thread{
    34. // @Override
    35. // public void run() {
    36. // for (int i = 0; i < 100; i++) {
    37. // if (i % 2 == 0){
    38. // System.out.println(MyThread1.currentThread().getName() + ":" + i);
    39. // }
    40. // }
    41. // }
    42. // }
    43. //
    44. //class MyThread2 extends Thread{
    45. // @Override
    46. // public void run() {
    47. // for (int i = 0; i < 100; i++) {
    48. // if (i % 2 == 1){
    49. // System.out.println(MyThread1.currentThread().getName() + ":" + i);
    50. // }
    51. // }
    52. // }
    53. //}

    包含了匿名内部类和写两个子类的方法

    Thread类的常用方法:

    • 测试Thread中的常用方法:
    • 1、start():启动当前线程,调用当前线程的run();
    • 2、run():通常需要重写此方法,将要实现的代码写在这个方法中;
    • 3、currentThread():静态方法,返回当前代码的线程;
    • 4、getName():获取当前线程的名字;
    • 5、setName():设置当前线程的名字;
    • 6、yeld():释放CPU,然后所有线程去抢;
    • 7、join():让线程a阻塞,线程b加入进来,线程b执行完毕线程a才结束阻塞状态;
    • 8、stop():结束线程的运行,已过时;
    • 9、sleep():让线程休眠,单位毫秒;
    • 10、isAlive():判断当前线程是否存活。

    线程的调度:
    2、如何测试线程的优先级:

    • getPriority():获取当前线程的优先级;
    • setPriority():设置分线程的优先级,CPU只是参考优先级设置,不是把优先级高的跑完再跑优先级低的。

    卖票:

    1. package com.codeday23.demo01;
    2. /**
    3. * 创建三个窗口买票,总票数100张
    4. */
    5. public class Window extends Thread{
    6. private static int ticket = 100;// 这里有线程的安全问题
    7. @Override
    8. public void run() {
    9. while(true){
    10. if(ticket > 0) {
    11. System.out.println(getName() + ":卖票,票号为:" + ticket);
    12. ticket--;
    13. }else{
    14. break;
    15. }
    16. }
    17. }
    18. }
    19. package com.codeday23.demo01;
    20. public class WindowTest {
    21. public static void main(String[] args) {
    22. Window t1 = new Window();
    23. Window t2 = new Window();
    24. Window t3 = new Window();
    25. t1.setName("窗口1");
    26. t2.setName("窗口2");
    27. t3.setName("窗口3");
    28. t1.start();
    29. t2.start();
    30. t3.start();
    31. }
    32. }

    多线程的方式二:

    1. package com.codeday23.demo01;
    2. /**
    3. * 创建多线程的方式二:实现Runnable接口
    4. * 1、创建一个实现了Runnable接口的类
    5. * 2、实现类去实现Runnable中的抽象方法:run()
    6. * 3、创建实现类的对象
    7. * 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    8. * 5、通过Thread类的对象调用start()
    9. *
    10. */
    11. //创建一个实现了Runnable接口的类
    12. class MThread implements Runnable{
    13. //实现类去实现Runbale中的抽象方法:run()
    14. @Override
    15. public void run() {
    16. for (int i = 0; i < 100; i++) {
    17. if(i % 2 == 0){
    18. System.out.println(i);
    19. }
    20. }
    21. }
    22. }
    23. public class ThreadTest {
    24. public static void main(String[] args) {
    25. //创建实现类的对象
    26. MThread mThread = new MThread();
    27. // 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    28. Thread t1 = new Thread(mThread);
    29. // 通过Thread类调用start():①启动线程;②调用当前线程的run()-->调用了
    30. // Runnable类型的target
    31. t1.start();
    32. // 再启动一个线程,遍历100以内的偶数
    33. Thread t2 = new Thread(mThread);
    34. t2.start();
    35. }
    36. }

    开发中有限选择实现runnable接口的方式。
    原因:
    1、实现的方式没有类的单继承性的局限性;
    2、实现的方式更适合来处理多个线程有共享数据的情况。

    联系:
    1、两种方式都需要重写run(),将线程要执行的逻辑声明再run()中。

    线程的生命周期:

    java:多线程 - 图1

    多线程的同步:
    卖票过程中出现重票、错票,原因是一个线程没有执行完毕,其他线程就参与了进来。
    如何解决:用🔒
    方式一:同步代码块

    1. synchronized(同步监视器){
    2. // 需要被同步的代码
    3. }

    说明:
    1、操作共享数据的代码,即为需要被同步的代码; 不能包含代码多了,也不能包含少了。
    2、共享数据:多个线程共同操作的变量。比如:ticket;
    3、同步监视器,就是🔒。任何一个类的对象,都可以充当锁,多个线程必须公用同一把🔒。

    补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
    在继承Thread类创建多线程的方式中,慎用this充当同步监视器。

    1. package com.codeday23.demo02;
    2. class Window1 implements Runnable{
    3. private int ticket = 100;
    4. Object obj = new Object();
    5. @Override
    6. public void run() {
    7. while(true){ // 这里的while语句是为了让程序执行完一次后退出循环重新判断条件
    8. // 不加while,程序会一直在里面执行
    9. synchronized (obj) {
    10. if (ticket > 0) {
    11. try {
    12. Thread.sleep(200);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
    17. ticket--;
    18. } else {
    19. break;
    20. }
    21. }
    22. }
    23. }
    24. }
    25. public class WindowTest1 {
    26. public static void main(String[] args) {
    27. Window1 w1 = new Window1();
    28. Thread t1 = new Thread(w1);
    29. Thread t2 = new Thread(w1);
    30. Thread t3 = new Thread(w1);
    31. t1.start();
    32. t2.start();
    33. t3.start();
    34. }
    35. }

    方式二:同步方法
    如果操作共享数据的代码完整地生命在一个方法中,可以将此方法声明为同步。
    总结:
    1、同步方法依然涉及同步监视器,只是不需要我们显式的声明;
    2、非静态的同步方法,同步监视器是this,静态的同步方法是this.class

    1. package com.codeday23.demo02;
    2. public class WindowTest3 {
    3. public static void main(String[] args) {
    4. Window1 w1 = new Window1();
    5. Thread t1 = new Thread(w1);
    6. Thread t2 = new Thread(w1);
    7. Thread t3 = new Thread(w1);
    8. t1.start();
    9. t2.start();
    10. t3.start();
    11. }
    12. }
    13. class Window3 implements Runnable{
    14. private int ticket = 100;
    15. @Override
    16. public void run() {
    17. while(true){
    18. show();
    19. }
    20. }
    21. private synchronized void show(){// 同步监视器是:this
    22. if (ticket > 0) {
    23. try {
    24. Thread.sleep(100);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
    29. ticket--;
    30. }
    31. }
    32. }

    继承Thread类的多线程方法使用同步方法:

    1. package com.codeday23.demo02;
    2. import com.codeday23.demo01.Window;
    3. public class WindowTest4 {
    4. public static void main(String[] args) {
    5. Window4 t1 = new Window4();
    6. Window4 t2 = new Window4();
    7. Window4 t3 = new Window4();
    8. t1.setName("窗口1");
    9. t2.setName("窗口2");
    10. t3.setName("窗口3");
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }
    16. class Window4 extends Thread{
    17. private static int ticket = 100;// 这里有线程的安全问题
    18. @Override
    19. public void run() {
    20. while(true){
    21. show1();
    22. }
    23. }
    24. private static synchronized void show1(){// 此时的同步监视器是this.class
    25. if(ticket > 0) {
    26. try {
    27. Thread.sleep(100);
    28. } catch (InterruptedException e) {
    29. e.printStackTrace();
    30. }
    31. System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
    32. ticket--;
    33. }
    34. }
    35. }

    单例设计模式:
    1、所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例。
    ①饿汉式:私有化构造器,私有的静态属性是new一个对象,使用静态方法去调用这个属性,由于是静态的,所有怎么都只有一个对象;
    ②懒汉式:私有化构造器,私有的静态属性是一个空对象,判断instance是否为null,如果是nullnew一个对象,否则直接返回instance。饿汉式是一个套娃结构,如果第一次调用get方法,会new一个该对象,第二次再调用,这个对象已经new了一个实例,就会跳过。

    懒汉式:

    1. class Bank1{
    2. private Bank1(){
    3. }
    4. private static Bank1 b1 = null;// 这里需要注意,这只是调用构造器之后的初始化,
    5. // 并不是说b1一直都是null
    6. public static Bank1 getInstance(){
    7. if(b1 == null){
    8. Bank1 b1 = new Bank1();
    9. }
    10. return b1;
    11. }
    12. }
    13. public class BankTest {
    14. public static void main(String[] args) {
    15. Bank b1 = Bank.getInstance();
    16. Bank b2 = Bank.getInstance();
    17. System.out.println(b1 == b2);
    18. }
    19. }

    image.png

    使用同步机制将单例模式中的懒汉式改写为线程安全的:

    1. class Bank {
    2. private Bank() {
    3. }
    4. private static Bank instance = null;
    5. public static Bank getInstance() {
    6. // 方法一:效率稍差,因为所有线程都要进入同步代码块
    7. // synchronized (Bank.class) {
    8. // if (instance == null) {
    9. // instance = new Bank();
    10. // }
    11. // return instance;
    12. // }
    13. // 方法二:
    14. if (instance == null) {
    15. synchronized (Bank.class) {
    16. if (instance == null) {
    17. instance = new Bank();
    18. }
    19. }
    20. }
    21. return instance;
    22. }
    23. }

    线程的死锁问题:不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己的同步资源,就形成了线程的死锁。
    代码:

    1. package com.codeday23.demo03;
    2. public class ThreadTest {
    3. public static void main(String[] args) {
    4. StringBuffer s1 = new StringBuffer();
    5. StringBuffer s2 = new StringBuffer();
    6. new Thread(){
    7. @Override
    8. public void run() {
    9. synchronized (s1){
    10. s1.append("a");
    11. s2.append("1");
    12. try {
    13. Thread.sleep(100);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }
    17. synchronized (s2){
    18. s1.append("b");
    19. s2.append("2");
    20. System.out.println(s1);
    21. System.out.println(s2);
    22. }
    23. }
    24. }
    25. }.start();
    26. new Thread(new Runnable(){
    27. @Override
    28. public void run() {
    29. synchronized (s2){
    30. s1.append("c");
    31. s2.append("3");
    32. try {
    33. Thread.sleep(100);
    34. } catch (InterruptedException e) {
    35. e.printStackTrace();
    36. }
    37. synchronized (s1){
    38. s1.append("d");
    39. s2.append("4");
    40. System.out.println(s1);
    41. System.out.println(s2);
    42. }
    43. }
    44. }
    45. }).start();
    46. }
    47. }

    我的理解:
    首先,统共两个线程,不分先后顺序。当线程1执行时,手中拿着S1的锁,代码块被执行,然后阻塞100ms。与此同时,在线程1阻塞的过程中,线程2极有可能开始执行,并且手中持有S2的锁。当线程1醒后,想去拿S2的锁执行下一个代码块的内容,发现线程2还在阻塞,无法拿到,就开始僵持,出现死锁。

    线程安全方法三,公平锁:

    1. package com.codeday23.demo03;
    2. import java.util.concurrent.locks.ReentrantLock;
    3. /**
    4. * 解决线程安全问题的方式三:Lock锁 -----JDK 5.0新增
    5. *
    6. */
    7. class Window implements Runnable{
    8. private int ticket = 100;
    9. private ReentrantLock lock = new ReentrantLock();
    10. @Override
    11. public void run() {
    12. while (true){
    13. try {
    14. //2、调用锁定的方法lock
    15. lock.lock();
    16. if(ticket > 0) {
    17. try {
    18. Thread.sleep(100);
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }
    22. System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
    23. ticket--;
    24. }else{
    25. break;
    26. }
    27. }finally {
    28. //3、调用解锁的方法
    29. lock.unlock();
    30. }
    31. }
    32. }
    33. }
    34. public class LockTest {
    35. public static void main(String[] args) {
    36. Window w = new Window();
    37. Thread t1 = new Thread(w);
    38. Thread t2 = new Thread(w);
    39. Thread t3 = new Thread(w);
    40. t1.setName("窗口1");
    41. t2.setName("窗口2");
    42. t3.setName("窗口3");
    43. t1.start();
    44. t2.start();
    45. t3.start();
    46. }
    47. }

    同步和公平锁的区别:
    相同:二者都是用来解决线程安全问题的。
    不同:同步在执行完相应的代码后自动解锁,lock需要手动实现解锁

    注意:lock对象同样需要做到统一,不能有多个lock对象。

    一个三个用户存款的实现代码:

    1. package com.codeday24.demo01;
    2. import com.codeday13.A;
    3. import java.util.AbstractCollection;
    4. /**
    5. * 银行有一个账户
    6. *
    7. * 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
    8. *
    9. * 分析:
    10. * 1、是否有多线程问题? 有
    11. * 2、是否有共享数据? 是
    12. * 3、是否有线程安全问题? 是
    13. * 4、需要考虑如何解决线程安全问题。同步机制,三种方式。
    14. *
    15. */
    16. class Account{
    17. private static double balance;
    18. public static void deposit(double amt){
    19. balance += amt;
    20. try {
    21. Thread.sleep(100);
    22. } catch (InterruptedException e) {
    23. e.printStackTrace();
    24. }
    25. if(balance >= 0) {
    26. System.out.println(Thread.currentThread().getName() + "存钱成功,余额为:" + balance);
    27. }else{
    28. System.out.println("余额不足!!");
    29. }
    30. }
    31. public double getBalance(){
    32. return balance;
    33. }
    34. }
    35. class Customer extends Thread{
    36. private Account acct;
    37. public Customer(Account acct) {
    38. this.acct = acct;
    39. }
    40. private static Object obj = new Object();
    41. @Override
    42. public void run() {
    43. synchronized (obj){
    44. for (int i = 0; i < 3; i++) {
    45. acct.deposit(1000);
    46. }
    47. }
    48. }
    49. }
    50. public class AccountTest {
    51. public static void main(String[] args) {
    52. Account acct = new Account();
    53. Customer c1 = new Customer(acct);
    54. Customer c2 = new Customer(acct);
    55. c1.setName("甲");
    56. c2.setName("乙");
    57. c1.start();
    58. c2.start();
    59. }
    60. }

    新增线程创建方式,实现Callable接口:

    1. package com.codeday25.demo01;
    2. import java.util.concurrent.Callable;
    3. import java.util.concurrent.ExecutionException;
    4. import java.util.concurrent.Future;
    5. import java.util.concurrent.FutureTask;
    6. /**
    7. * 创建线程得方式三:实现Callable接口。
    8. *
    9. * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建都线程方式强大?
    10. * 1、call()有返回值;
    11. * 2、call()方法可以抛出异常,被外面的操作捕获;
    12. * 3、Callable支持泛型
    13. *
    14. */
    15. // 1、创建一个实现Callable的实现类
    16. class NumThread implements Callable{
    17. // 实现call方法,将此线程需要执行的操作写在这里面。
    18. @Override
    19. public Object call() throws Exception {
    20. int sum = 0;
    21. for (int i = 1; i <= 100; i++) {
    22. if (i % 2 == 0){
    23. System.out.println(i);
    24. sum += i;
    25. }
    26. }
    27. return sum;
    28. }
    29. }
    30. public class ThreadNew {
    31. public static void main(String[] args) {
    32. // 3、创建Callable接口实现类的对象
    33. NumThread numThread = new NumThread();
    34. // 4、将次Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
    35. FutureTask futureTask = new FutureTask(numThread);
    36. // 5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
    37. new Thread(futureTask).start();
    38. try {
    39. // 6、获取Callable中call方法中的返回值
    40. // get()的返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
    41. Object sum = futureTask.get();
    42. System.out.println(sum);
    43. } catch (InterruptedException e) {
    44. e.printStackTrace();
    45. } catch (ExecutionException e) {
    46. e.printStackTrace();
    47. }
    48. }
    49. }

    线程池:

    1. package com.codeday25.demo02;
    2. import java.util.concurrent.ExecutorService;
    3. import java.util.concurrent.Executors;
    4. import java.util.concurrent.ThreadPoolExecutor;
    5. /**
    6. * 创建线程的方式四:创建线程池
    7. *
    8. * 优点:
    9. * 提高响应速度(减少了创建新线程的时间)
    10. * 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    11. * 便于线程管理
    12. *
    13. * 1)corePoolSize:核心池的大小
    14. * 2)maximumPoolSize:最大线程数
    15. * 3)keepAliveTime:线程没有任务时最多保持多长时间后会终止
    16. *
    17. * 创建多线程有四种方式
    18. */
    19. class NumberThread implements Runnable {
    20. @Override
    21. public void run() {
    22. for (int i = 0; i <= 100; i++) {
    23. if (i % 2 == 0) {
    24. System.out.println(Thread.currentThread().getName() + " " + i);
    25. }
    26. }
    27. }
    28. }
    29. class NumberThread1 implements Runnable{
    30. @Override
    31. public void run() {
    32. for (int i = 0; i <= 100; i++) {
    33. if (i % 2 != 0){
    34. System.out.println(Thread.currentThread().getName() + i);
    35. }
    36. }
    37. }
    38. }
    39. public class ThreadPool {
    40. public static void main(String[] args) {
    41. // 1、提供指定线程数量的线程池
    42. ExecutorService service = Executors.newFixedThreadPool(10);
    43. ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
    44. // 设置线程属性(这就是管理)
    45. // System.out.println(service.getClass());// 获取该对象的类,结果是ThreadPoolExecutor
    46. service1.setCorePoolSize(15);
    47. // service1.setKeepAliveTime();
    48. // 2、执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
    49. service.execute(new NumberThread());// 适用于Runnable
    50. service.execute(new NumberThread1());// 适用于Runnable
    51. // service.submit();// 适用于Callable
    52. // 3、关闭连接池
    53. service.shutdown();// 关闭线程池
    54. }
    55. }