02_1_Java多线程

1 基本概念:程序、进程、线程

一个线程都有一个独立的栈和程序计数器

一个进程中的多个线程,共享这一个进程中的堆和方法区。

进程和进程

②进程process

概念:程序的一次执行过程,或是正在运行的一个程序。

说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存空间。

③线程thread

概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。

说明:线程作为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器(pc),线程切换的开销小。

​ 多个线程,共享同一个进程中的结构:方法区、堆。

1597246626221

补充:内存结构

1597246649756

④并行与并发

  • 单核CPU与多核CPU:
    • 单核CPU:一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。

      单核CPU不能处理并行任务,如果执行多线程和多进程任务,这些任务其实是并发执行的,操作系统会不断切换多个任务,因此单核CPU是不能够实现并行的。

      • 下面的抢票程序如果在单核CPU上运行,不会出现重票和错票现象。!!!!❌
      • 下面的抢票程序如果在单核CPU上运行,也会出现重票和错票现象。
    • 多核CPU:现在的服务器都是多核的。

      真正的并行任务,只可能出现在多核CPU中。

    • Java应用程序java.exe,至少三个线程:main()主线程,gc垃圾回收线程,异常处理线程。如果发生异常,会影响主线程。

  • 并行与并发的理解
    • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
    • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

2 线程的创建和使用

①继承Thread类的方式

线程的创建方法之一:继承Thread类的方式

  1. 创建一个继承于Thread类的子类
  2. 重写run()方法
  3. 实例化Thread类的子类对象
  4. 调用对象的start方法
  • 但是存在线程不安全的,如果一个线程占用了资源,但是还没有释放的时候,其他线程也会获得。所以就会出现重票,或者错票的情况。
  • 可以在某些位置加上,Thread.sleep(Time time);可以让线程进入阻塞状态。增大其他线程获得线程的机会。
package www.codejiwei.test;
/**
* 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
* 存在线程的安全问题,待解决。存在重票,错票问题。
 **/
public class WindowDemo {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class Window extends Thread{
    //static应该共享这一百张票
    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                System.out.println(getName()+":卖票,票号为:"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

②实现Runnable接口的方式

创建实现Runnable接口的方法:

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:重写run()!
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创造Thread类的对象
  5. 通过Thread类的对象调用start()
public class WindowDemo02 {
    public static void main(String[] args) {

        Window02 window02 = new Window02();

        Thread t1 = new Thread(window02);
        Thread t2 = new Thread(window02);
        Thread t3 = new Thread(window02);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class Window02 implements Runnable{
    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0){          System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

③前两种方式的对比

继承Thread类 PK 实现Runnable接口

  • 优先使用:实现Runnable接口的方法

  • 原因:

    • 实现的方式没类的继承性的局限性
    • 实现的方式更适合来处理多个线程共享数据的情况。
  • 联系:public class Thread implements Runnable

    ​ Thread类就实现了Runnable,那么继承Thread方式的子类也就继承了父类的方法。

  • 相同点:

    • 两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
    • 目前两种方法的启动现成的方式都是调用Thread类的start()

④Thread类中的常用方法

  1. start():启动当前线程;调用当前线程的run()

  2. run():通常要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。

  3. currentThread():静态方法,返回执行当前代码的线程,通常配合线程常用方法,如:

Thread.currentThread().getName()//获取当前线程的名字
Thread.currentThread().setName("主线程")//设置线程的名字
Thread.currentThread().stop()//已过时。执行此方法时,强制结束当前线程
    结束线程一般采取逻辑去结束,比如return结束方法等
Thread.currentThread().sleep(long millitime)//让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态
Thread.currentThread().join()//在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完之后,线程a才结束阻塞状态。
  	在启动后再插队!!
Thread.currentThread().yield()//释放当前CPU的执行权
Thread.currentThread().isAlive()//判断当前线程是否存活
  1. 线程的优先级:

    • MAX_PRIORITY:10
    • MIN_PRIORITY:1
    • NORM_PRIORITY:5 --> 默认优先级
  2. 获取当前线程的优先级:

Thread.currentThread().getPriority()
  1. 设置当前线程的优先级:
Thread.currentThread().setPriority()
  • 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

3 线程的生命周期

1598518744904

4 线程的同步

①同步代码块

synchronized(同步监视器){

​ //需要被同步的代码

}

  1. 操作共享数据的代码,即为需要被同步的代码。(不能多了,也不能少了)

  2. 共享数据:多个线程共同操作的变量。比如ticket

  3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要公用同一把锁。

  4. 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

    在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

继承Thread类
/**
 * 继承Thread类+Synchronized
 **/
public class WindowTest01 {
    public static void main(String[] args) {
        Window01 w1 = new Window01();
        Window01 w2 = new Window01();
        Window01 w3 = new Window01();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();

    }
}

class Window01 extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (Window01.class) {
                if (ticket > 0) {
                    //添加一个sleep(),增大出错的概率
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + "卖票:票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
实现Runnable接口
/**
 * 实现Runnable接口的方式+同步代码块
 **/
public class WindowTest02 {
    public static void main(String[] args) {
        Window2 w2 = new Window2();

        Thread t1 = new Thread(w2);
        Thread t2 = new Thread(w2);
        Thread t3 = new Thread(w2);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
class Window2 implements Runnable{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true){
            synchronized (this){
                if (ticket > 0){
//                    this.notifyAll();
                    try {
               Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                   System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                    ticket--;
//                    try {
//                        this.wait();
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                }else {
                    break;
                }
//                this.notifyAll();
            }
        }
    }
}

②同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

关于同步方法的总结:

  • 同步方法仍然设计到同步监视器,只是不需要我们显示的声明。
  • 非静态的同步方法,同步监视器是:this本身
  • 静态的同步方法,同步监视器是:当前类本身
继承Thread类
public class WindowTest03 {
    public static void main(String[] args) {

        Window03 w1 = new Window03();
        Window03 w2 = new Window03();
        Window03 w3 = new Window03();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

class Window03 extends Thread{
    private static int ticket = 100;
    public static boolean flag = true;

    @Override
    public void run() {
        while (flag){
            show();
        }
    }

    private static synchronized void show(){
        if (ticket > 0){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
            ticket--;
        }else {
            flag = false;
        }
    }
}
实现Runnable接口
public class WindowTest04 {
    public static void main(String[] args) {
        Window04 w4 = new Window04();

        Thread t1 = new Thread(w4);
        Thread t2 = new Thread(w4);
        Thread t3 = new Thread(w4);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class Window04 implements Runnable{

    private static int ticket = 100;
    private static boolean flag = true;

    @Override
    public void run() {
        while (flag){
            show();
        }
    }

    private static synchronized void show(){

        if (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":售票,票号为:"+ticket);
            ticket--;
        }else {
            flag = false;
        }
    }
}

③同步方法和同步代码块也存在缺陷~

  • 同步代码块和同步方法,解决了线程的安全问题,不会出现错票和重票的问题了
  • 但是操作同步代码块的时候,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
④改进的单例模式-懒汉式
//1 饿汉式
class Bank{
    private Bank(){}
    
    private static Bank bank = new Bank();
    
    public static Bank getBank(){
        return bank;
    }  
}

//2懒汉式:存在线程安全问题
class Bank{
    private Bank(){}
    private static Bank bank = null;
    public static Bank getBank(){
        if(bank == null){
            bank = new Bank();
        }
        return bank;
    }
}

//3懒汉式:解决线程安全问题
//方式1,效率差一点
class Bank{
    private Bank(){}
    private static Bank bank = null;
    public static Bank getBank(){
        synchronized(Bank.class){
            if(bank == null){
                bank = new Bank();
            }
            return bank;
        }
    }
}

//方式2:效率高一些
class Bank{
    private Bank(){}
    private static Bank bank = null;
    public static Bank getBank(){
        if(bank == null){
            synchronized(Bank.class){
                if(bank == null){
                    bank = new Bank();
                }
            }
        }
        return bank;
    }
}

④解决线程安全的第三种方式lock - unlock

注意!!

  • 使用ReentrantLock lock的方式,一定要注意所有线程必须公用一把锁!!!

Lock锁 ----> JDK5.0中新增的

  • 面试题:synchronized 与 Lock的异同

  • 相同:都解决了线程安全问题

  • 不同:synchronized机制在执行完相应的同步代码后,会自动的释放同步监视器

    ​ Lock需要手动的启动同步(Lock),同步结束后也需要手动的释放锁(unLock)

  • 在开发中这三种解决线程安全的方法的使用顺序:

    ​ Lock(unLock) —> 同步代码块(已经进入了方法体,分配了相应资源)—> 同步方法(在方法体之外)

  • 利弊:

    • 同步的方式,解决了线程的安全问题。—好处
    • 操作同步代码块时,只能一个线程参与,其他线程等待。相当于一个单线程的过程,效率低。
实现Runnable接口的方式
public class LockTest22 {
    public static void main(String[] args) {
        Window22 w22 = new Window22();

        Thread t1 = new Thread(w22);
        Thread t2 = new Thread(w22);
        Thread t3 = new Thread(w22);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class Window22 implements Runnable{

    private static int ticket = 100;
    //1. 在实现类中实例化一个reentranLock的对象
    private ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {

        while (true){
            //注意!如果同步的代码有异常,需要将unlock写入finally语句块中。
            try {
                //2. 在对共享数据操作的地方上锁。
                lock.lock();
                if (ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                    }
                    System.out.println(Thread.currentThread().getName() + ": 抢票, 票号为:" + ticket);
                    ticket--;
                }else {
                    break;
                }
            } finally {
                //3. 当一个线程执行完后,释放锁。
                lock.unlock();
            }
        }
    }
}
继承Thread类的方式
  • 解决继承Thread类的方式去解决线程安全问题呢,需要注意一个点!!!
    • 因为会实例化三个子类对象,那么这样每个对象都会获得一把锁,这就相当于每个人有自己的锁,所以无法解决线程安全问题。
    • 解决的方法:将子类中的锁声明为static,那么三个子类对象就会共用一把锁。
public class LockTest11 {
    public static void main(String[] args) {
        Window11 w1 = new Window11();
        Window11 w2 = new Window11();
        Window11 w3 = new Window11();
        
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}
class Window11 extends Thread{

    private static int ticket = 100;
    //!!!注意这个锁对象一定要声明为static的,不然每个对象都有自己的一把锁
    private static ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                if (ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                    }
                    System.out.println(getName()+":售票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

5 线程的通信

  1. 线程通信涉及到的三个方法

    • **wait():**一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
    • **notify():**一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,唤醒优先级高的那个。
    • **notifyAll():**一旦执行此方法,就会唤醒所有被wait的线程。

    如果是3个线程,notify(),会唤醒被

    ​ notifyAll(),虽然唤醒了另外的2个,因为优先级一样,那么就随机唤醒,

  2. 线程通信的注意事项

    • wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中。
    • wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器!!!否则会出现IllegalMonitorStateException异常
    • wait()、notify()、notifyAll()三个方法是定义在java.lang.Object类中。
  3. 面试题:sleep()和wait()的异同?

    • 相同点:一旦执行方法,都会使当前线程进入阻塞状态
    • 不同点:
    • 声明的位置不同,sleep在Thread类中,wait在Objcet类中
    • 调用的要求不同,sleep在任何位置,wait只能在同步代码块或同步方法中
    • 关于是否释放同步监视器:sleep进入阻塞但是不会释放锁,wait会释放锁。
  4. 释放锁和不释放锁的操作

    1598585609288

    1598585620480

①+实现Runnable

  • 解决实现Runnable接口的方式,遇到的一个线程拿到锁不释放的情况。

    public class CommunicationTest {
        public static void main(String[] args) {
            Window06 w6 = new Window06();
    
            Thread t1 = new Thread(w6);
            Thread t2 = new Thread(w6);
            Thread t3 = new Thread(w6);
    
            t1.setName("窗口1");
            t2.setName("窗口2");
            t3.setName("窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    class Window06 implements Runnable{
        private static int ticket = 100;
    
        @Override
        public void run() {
            while (true){
                synchronized (this) {
    				//此时的notifyAll()就是this调用的,也就是Window06 w6这个实例对象调用的,只有一个。是可以的。
                    notifyAll();
    
                    if (ticket > 0){
    
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                        ticket--;
                        //wait()
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        break;
                    }
                }
            }
        }
    }
    

继承Thread类

  • 继承Thread类的方式,因为是实例化多个Threadi子类对象,在同步代码块中,如果调用notifyAll和wait的方法,就相当于每个对象调用自己的notifyAll和wait方法。但是我们的同步锁,必须是只有一个才行。

所以可以实力一个静态的Objcet对象作为实例对象同步锁。这样大家就共用一个锁了。

  • 注意之前的同步监视器,可以是Window07.class也就是让类去充当同步监视器。这个时候就不行了会报一个IllegalMonitorStateException。因为notify和wait这三个方法的调用着,必须是同步代码块中的同步监视器,但咱们使用Window07.class类锁去充当同步监视器,他又不是Object的子类,所以没有这三个方法。
public class CommunicationTest02 {
    public static void main(String[] args) {
        Window07 w1 = new Window07();
        Window07 w2 = new Window07();
        Window07 w3 = new Window07();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

class Window07 extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj) {
                //这个时候必须用obj.notifyAll(),如果不加的话,因为三个对象,三个对象调用了不同的notifyAll()
                //这个时候还不能用Window07.class,因为这.class是一个类锁,而下面的notifyAll和wait都是Object父类的方法。所以这个同步监视器还必须是一个实例对象锁。
                obj.notify();
                if (ticket > 0){
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + ":售票,票号为:" + ticket);
                    ticket--;
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}

6 JDK5.0新增线程创建方式

③实现Callable接口的方式

  • 使用实现Callable接口的方式的优点:
    • call()可以有返回值
    • call()可以抛出异常,被外面的操作捕获,获取异常的信息。
    • Callable是支持泛型的。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadNew {
    public static void main(String[] args) {
        //3. 实例化一个实现Callable的实现类
        NumThread numThread = new NumThread();
        //4. 将上一步的实现类放到FutureTask构造器中,创建FutureTask对象
        FutureTask task = new FutureTask(numThread);
        //5. 将FutureTask对象放到Thread类构造器中,创建Tread类的对象,调用Thread类的start(),开始线程
        //可以是匿名对象
        new Thread(task).start();

        try {
            //6. 获取Callable中的call方法的返回值
            //get()返回值是FutureTask构造器参数实现Callable实现类重写的call()的返回值
            Object o = task.get();//自动装箱
            System.out.println("总和为:" + o);//自动拆箱
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.创建一个实现Callable的实现类
class NumThread implements Callable {
    //2.重写Callable接口的call方法。
    //call方法具有Object类型的返回值。基本数据类型可以自动装箱
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

④使用线程池的方式

  • 优点:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理(线程池的属性)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没任务时最多保持多长时间后会终止
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
 * @Author jiweicode
 * @Date 2020/8/28 - 17:29
 * @Version 1.0
 * @Description
 * 使用线程池创建线程
 **/
public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池(ExecutorService是一个接口,Executor是一个类(多态))
        ExecutorService service = Executors.newFixedThreadPool(10);
//        System.out.println(service.getClass());//活得当前接口的ThreadPoolExecutor

        //设置线程池的属性
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的实现类
        service.execute(new NumberThread());//适合用于Runnable
        service.execute(new NumberThread1());//
//        service.submit()//适合用于Callable

        //3. 关闭连接池
        service.shutdown();
    }
}
//线程1
class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
//线程2
class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

7死锁

 **/
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        //匿名对象,继承Thread类的方式创建线程
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    //线程1拿到s1这把锁以后,sleep一会,再去拿s2
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        }.start();

        //runnable实现类的匿名对象,创建线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    //线程2拿到s2这把锁以后sleep一会
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            }
        }).start();
    }
}

8 匿名内部类创建子线程

public class Demo02 {
    public static void main(String[] args) {
        //匿名内部类继承Thread类的方式创建子线程
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (i % 2 != 0){
                        System.out.println(this.getName() + ":" + i);
                    }
                }
            }
        }.start();
        //匿名内部类实现runnable接口的方式创建子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }).start();
    }
}

9 StringBuffer线程安全,和StringBuilder线程不安全的体现

证明方式一:
package www.codejiwei;
/**
 * @Author jiweicode
 * @Date 2020/9/9 - 17:18
 * @Version 1.0
 * @Description
 * 使用实现Runnable的方式证明StringBuffer时线程安全的,StringBuilder是线程不安全的
 **/
public class StringBuilderThreadTest1 {
    public static void main(String[] args) {
        StringBuilderThread sbt1 = new StringBuilderThread();
        Thread t1 = new Thread(sbt1);
        Thread t2 = new Thread(sbt1);
        Thread t3 = new Thread(sbt1);
        t1.start();
        t2.start();
        t3.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(new StringBuilderThread().getSbd());     
    }
}
class StringBuilderThread implements Runnable{
    private static StringBuffer sbd = new StringBuffer();
//    private static StringBuilder sbd = new StringBuilder();

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sbd.append(i);
        }
    }
    public  StringBuffer getSbd() {
        return sbd;
    }
}
证明方式二:
package www.codejiwei.homework;
/**
 * @Author jiweicode
 * @Date 2020/9/9 - 19:14
 * @Version 1.0
 * @Description
 * 使用继承Thread类的方式证明StringBuilder的线程不安全性
 **/
public class StringBuilderThreadTest2 {
    public static void main(String[] args) {
        StringBuilderTest2 t1 = new StringBuilderTest2();
        StringBuilderTest2 t2 = new StringBuilderTest2();
        StringBuilderTest2 t3 = new StringBuilderTest2();
        t1.start();
        t2.start();
        t3.start();
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(StringBuilderTest2.getSbd());
    }
}
class StringBuilderTest2 extends Thread{
    //虽然是继承Thread类有三个StringBuilderTest2对象,但是因为sbd是一个私有的引用类型,还是会有一个StringBuilder对象,类似于单例模式。
    private static StringBuilder sbd = new StringBuilder();
//    private static StringBuffer sbd = new StringBuffer();

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sbd.append(i);
        }
    }
    public static StringBuilder getSbd() {
        return sbd;
    }
//    public static StringBuffer getSbd() {
//        return sbd;
//    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最佳第六六六人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值