ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

day19-多线程

2021-07-11 20:01:51  阅读:168  来源: 互联网

标签:同步 Thread void public 线程 new 多线程 day19


  1. 线程的生命周期

  2. 线程安全问题的举例和解决措施

  3. 同步代码块处理实现Runnable的线程安全问题

  4. 同步代码块处理继承Thread类的线程安全问题

  5. 同步方法处理实现Runnable的线程安全问题

  6. 同步方法处理继承Thread类的线程安全问题

  7. 线程安全的懒汉式单例模式

  8. 死锁问题

  9. Lock锁方式解决线程安全问题

  10. 同步机制练习

  11. 线程通信的例题

  12. sleep()和wait()的异同

  13. 线程通信的生产者消费者例题

  14. 创建多线程的方式三:实现Callable接口

  15. 使用线程池的好处

  16. 创建多线程的方式四:使用线程池

 

1,线程的生命周期

    Java用Thread类的内部类State(枚举类)定义了线程的几种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

    当调用start()创建一个线程后,该线程不会立即被执行,而是进入就绪状态,此时可能有其他之后创建的线程也进入了就绪状态,CPU选择一个线程执行,这个线程可能是先创建的,也可能是后创建的;运行时的线程可能会因为CPU调度而失去执行权,变为就绪状态,等待CPU下一次调度而再被执行;而如sleep()等操作会让正在执行的线程进入阻塞状态,此时CPU的执行权不可能轮到它,CPU只会选择就绪状态的线程,所以这是一种不同于就绪的状态,当线程结束阻塞就会回到就绪状态,此时它可以被CPU调度执行;所有线程的结局只有一个,就是执行完或者发生其他情况变为死亡的状态

 

2,线程安全问题的举例和解决措施

    以取钱为例:在每次取钱的操作之前,都要先判断余额是否大于要取的钱。如果两个线程同时对一个余额进行取钱操作,当第一个线程执行时先判断,发现3000<2000,恰好此时CPU发生调度,该线程进入就绪状态,第二个线程被执行,发现3000<2000,然后取走了2000,轮到第一个线程接着上次的状态继续执行,也取走了2000,此时余额变为-1000。这种情况显然是不允许的

    两个线程对两份不同的数据进行操作不会出现线程安全问题,只有对同一数据操作时可能会出现。在之前的卖票例子中、单例模式的懒汉式实现中都可能出现线程安全问题

 

3,同步代码块处理实现Runnable的线程安全问题

    线程同步:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作

package com.atguigu.java;

/**
 * 卖票过程中出现重票、错票的线程安全问题
 * 原因:某个线程在操作车票的过程中,操作尚未完成就切换到其他线程执行
 * 解决:当线程a操作ticket时,其他线程不能参与进来,直到a操作完,其他线程才可以操作ticket,
 *     这种情况即使a线程出现了阻塞也不能改变
 * Java中通过同步机制解决线程安全问题
 *     方式一:同步代码块
 *     synchronized(同步监视器) { // 同步监视器即锁,任何一个类的对象都可以充当锁,要求多个线程必须共用同一把锁
 *         // 需要被同步的代码    (操作共享数据的代码,共享数据是多个线程共同操作的变量,比如ticket)
 *     }
 *
 *     方式二:同步方法
 *
 * 同步的方式可以解决线程安全问题。但在执行同步代码时,只能有一个线程参与,其他线程必须等待,这相当于是
 *     单线程执行,效率变低
 */

public class WindowTest1 {

    public static void main(String[] args) {
        Window1 w = new Window1();

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

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

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

class Window1 implements  Runnable {
    private int ticket = 100;
    Object obj = new Object(); // 只有一个Window1对象,也就只有一个Object对象

    @Override
    public void run() {
        // Object obj = new Object(); // 此时每个线程执行run()时都会创建一个Object对象,也就不是共用同一把锁了,所以会出现线程安全问题
        while(true) {
            synchronized(obj) { // 不加这一行时,就会出现下面的重票、错票情况
            // synchronized(this) // this表示唯一的那个Window1对象,所以这样写也是可以的
                if (ticket > 0) { // 当某个线程刚判断完就轮到另一个线程执行,就可能出现错票,比如出现为0或-1的票
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); // 当某个线程刚打印完,就轮到另一个线程执行,就可能出现重票
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

 

4,同步代码块处理继承Thread类的线程安全问题

package com.atguigu.java;

/**
 * synchronized包含的代码既不能多也不能少,如果还有操作共享数据的代码没有包含,则可能
 *     出现线程安全问题,如果包含多了,比如这个例子中将while循环包含进去,则会出现一个
 *     线程执行完所有的操作,其他线程根本得不到执行机会
 */

public class WindowTest2 {

    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();

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

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

class Window2 extends Thread {

    private static int ticket = 100;
    private static Object obj = new Object(); // 创建了三个Window2对象,所以要将obj定义为static以保证只有一个对象

    @Override
    public void run() {
        while(true) {
            synchronized(Window2.class) { // 正确的,说明类也是对象,且这个类只加载一次,只有一个对象。Class clazz = Window2.class,将一个类赋值给类类型的引用变量
            // synchronized(this) // 错误的,此时不同的Window2对象调用run(),this代表不同的对象
            // synchronized(obj) //正确的,Object对象只有一个
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

 

5,同步方法处理实现Runnable的线程安全问题

package com.atguigu.java;

/**
 * Java同步机制解决线程安全问题的方式之二:同步方法
 *     将操作共享数据的代码放在一个方法中,并将此方法声明为同步的
 */

public class WindowTest3 {

    public static void main(String[] args) {
        Window3 w = new Window3();

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

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

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

class Window3 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while(true) {
             show(); // 调用同步方法,也能解决线程安全问题
        }
    }

    private synchronized void show() { // 同步方法,也有同步监视器,要求是唯一的,默认为this
        // synchronized(this) { // 同步代码块的方式
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
        // }
    }
}

 

6,同步方法处理继承Thread类的线程安全问题

package com.atguigu.java;

public class WindowTest4 {

    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();

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

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

class Window4 extends Thread {

    private static int ticket = 100;

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

    private static synchronized void show() { // 同步监视器:Window4.class即当前类本身
        // private synchronized void show() {} // 错误的,此时同步监视器是this,本例中就是三个Window4对象
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); // 在静态方法中不能调用非静态结构
            ticket--;
        }
    }
}

 

7,线程安全的懒汉式单例模式

package com.atguigu.java1;

/**
 * 采用同步方法或同步代码块的方式
 */
 
public class BankTest {
    
}

// 懒汉式单例模式
class Bank {

    private Bank() {}

    private static Bank instance = null;

    // public static synchronized Bank getInstance() // 同步方法,同步监视器为Bank.class
    public static Bank getInstance() { // 如果两个线程都通过run()调用getInstance(),第一个线程在判断instance为null时
                                    // 发生调度,第二个线程调用该方法并创建了对象,回到第一个线程它又会创建一个对象

        // 方式一:效率较差,当创建了单例对象,之后的线程调用该方法都要先判断instance是否为null
//        synchronized(Bank.class) { // 同步代码块
//            if (instance == null) {
//                instance = new Bank();
//            }
//            return instance;
//        }

        // 方式二:效率较高
        if(instance == null) {
            synchronized(Bank.class) {
                if(instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

 

8,死锁问题

package com.atguigu.java1;

public class ThreadTest {

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

        new Thread() { // 通过Thread类的匿名子类的匿名对象调用start()开启一个线程执行重写的run()
            @Override
            public void run() {
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(100); // 增加发生死锁的概率,当开启的第一条线程执行并拿到s1锁后进入阻塞,
                                                // 此时开启的第二天线程执行并拿到s2锁,就形成了接下来二者都想
                                                // 拿到对方拥有的锁的局面,就形成了死锁,此时程序无法结束
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() { // 通过向Thread类传递一个实现Runnable接口的匿名实现类的匿名对象创建
                                    // Thread类的匿名对象,调用start()开启一个线程并执行重写后的run()
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");

                    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);
                    }
                }
            }
        }) {
        }.start();
    }
}
    另一个例子,有时候可能发生死锁的地方比较隐蔽,程序执行没问题不一定代表不可能发生死锁,应尽量避免死锁的产生

package com.atguigu.java1;
//死锁的演示
class A {
	public synchronized void foo(B b) { //同步监视器:A类的对象:a
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了A实例的foo方法"); // ①
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException ex) {
//			ex.printStackTrace();
//		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用B实例的last方法"); // ③
		b.last();
	}

	public synchronized void last() {//同步监视器:A类的对象:a
		System.out.println("进入了A类的last方法内部");
	}
}

class B {
	public synchronized void bar(A a) {//同步监视器:b
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了B实例的bar方法"); // ②
//		try {
//			Thread.sleep(200);
//		} catch (InterruptedException ex) {
//			ex.printStackTrace();
//		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用A实例的last方法"); // ④
		a.last();
	}

	public synchronized void last() {//同步监视器:b
		System.out.println("进入了B类的last方法内部");
	}
}

public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("主线程");
		// 调用a对象的foo方法
		a.foo(b);
		System.out.println("进入了主线程之后");
	}

	public void run() {
		Thread.currentThread().setName("副线程");
		// 调用b对象的bar方法
		b.bar(a);
		System.out.println("进入了副线程之后");
	}

	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		new Thread(dl).start();


		dl.init();
	}
}

 

9,Lock锁方式解决线程安全问题

package com.atguigu.java1;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全的方式三:Lock锁
 *     1,实例化ReentrantLock
 *     2,将可能出现线程安全的代码放在try{}中,并调用ReentrantLock对象的lock方法
 *     3,在finally中调用ReentrantLock对象的unlock方法
 *
 * 面试题:synchronized和Lock的异同
 *     相同点:都可以解决线程安全问题
 *     不同点:synchronized机制(同步代码块或同步方法)在执行完相应的代码后,自动释放同步监视器,
 *         Lock则需要手动启动同步、手动结束同步
 * 面试题:解决线程安全问题的方式有哪些
 *     synchronized机制(同步方法和同步代码块)、Lock锁
 */

public class LockTest {

    public static void main(String[] args) {
        Window w = new Window();

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

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

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

class Window implements Runnable {

    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock(); // 如果是继承Thread的方式,就要加上static,避免每个对象都有一把锁

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock(); // 线程执行这一步后就获得了锁,在执行之后的代码时不会切换为其他线程,除非它释放了这把锁

                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }finally { // 无论什么情况,都会执行
                lock.unlock(); // 释放锁
            }
        }
    }
}

 

10,同步机制练习

package com.atguigu.exer;

public class AccountTest {

    public static void main(String[] args) {
        Account acct = new Account(0);

        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();
    }
}

class Account {

    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public synchronized void deposit(double amt) { // 同步监视器使用唯一的Account对象
    // public void deposit(double amt) // 会出现线程安全问题
        if(amt > 0) {
            balance += amt;

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

            System.out.println(Thread.currentThread().getName() + "存钱成功,余额为:" + balance);
        }
    }
}

class Customer extends Thread {

    private Account acct;

    public Customer(Account acct) {
        this.acct = acct;
    }

    @Override
    public void run() {
        for(int i = 0 ; i < 3 ; i++) {
            acct.deposit(1000);
        }
    }
}

 

11,线程通信的例题

package com.atguigu.java2;

/**
 * 两个线程交替打印1-100
 * 这个过程是:
 *     一开始线程一拿到锁(同步监视器),执行notify()没有影响,调用sleep()使自己进入阻塞状态但并不会切换
 *         线程执行,因为锁还是线程一的,执行完对共享数据的操作后,wait()使线程一进入阻塞,并且释放了锁
 *     接下来一定是线程二被执行,它先拿到锁,执行notify()唤醒了线程一,因为线程二拿着锁,所以不会切换到
 *         线程一,同样线程二执行sleep()进入阻塞,然后执行对共享数据的操作,接着执行wait()使自己进入
 *         阻塞并释放了锁。二者如此交替执行
 * 三个和线程通信相关的方法
 *     wait():执行此方法,该线程就会进入阻塞状态,并释放同步监视器
 *     notify():执行此方法,该线程就会唤醒一个wait()过的阻塞线程;如果有多个线程执行过wait(),则唤醒优先级最高的那个
 *     notifyALL():执行此方法,该线程唤醒所有wait()过的阻塞线程
 * 注意点:
 *     1,三个方法必须放在同步代码块或同步方法中(不能放在Lock中)
 *     2,三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则出现异常,这也是为什么三个方法不能放在
 *         Lock中的原因,因为Lock中没有同步监视器
 *     3,三个方法是定义在Object类中的
 */

class Number implements Runnable {

    private int number = 1;
    private Object obj = new Object();

    @Override
    public void run() {
        while(true) {
            synchronized (this) {
            // synchronized(obj)
                notify(); // 唤醒另一个阻塞的线程,notifyAll()唤醒所有阻塞的线程。相当于this.notify(),
                        // 同步监视器即为调用者,所以没报异常;如果同步监视器变为obj则会报异常
                // obj.notify(); // 正确写法

                if (number <= 100) {

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

                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try { // 使得调用wait()的线程进入阻塞状态,不同于sleep(),调用wait()的线程会释放
                        // 锁(同步监视器)
                        wait(); // this.wait(); // 同上notify()
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } else {
                    break;
                }
            }
        }
    }
}

public class CommunicationTest {

    public static void main(String[] args) {
        Number number = new Number();

        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程一");
        t2.setName("线程二");

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

 

12,sleep()和wait()的异同

    面试题:         相同点:调用这两个方法都会使当前线程进入阻塞状态         不同点:1,声明位置不同,sleep()声明在Thread类中,wait()声明在Object类中                        2,调用的要求不同,sleep()可以在任何场景下调用,wait()必须在同步代码块或同步方法中调用                        3,是否会释放同步监视器,如果都在同步代码块或同步方法中被调用,sleep()不会释放锁,wait()会释放锁  

13,线程通信的生产者消费者例题

package com.atguigu.java2;

/**
 * 是多线程问题:生产者线程和消费者线程
 * 有共享数据:店员或产品
 * 涉及到线程通信的问题
 */

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");

        p1.start();
        c1.start();
    }
}

class Clerk {

    private int productCount = 0;

    public synchronized void produceProduct() { // 生产产品的方法。唯一的Clerk对象作为同步监视器
        if(productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");

            notify(); // 从生产第一个产品开始就唤醒消费的线程。如果不加notify()就会出现这样的情况,当两个线程
                    // 都开启后,消费者线程先判断没有可消费的,就wait()进入阻塞,接着生产者执行,生产到20时
                    // 执行wait()进入阻塞
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consumeProduct() { // 消费产品的方法
        if(productCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread { // 生产者

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品...");

        while(true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.produceProduct();
        }
    }
}

class Consumer extends Thread { // 消费者

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始消费产品...");

        while(true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }
    }
}

 

14,创建多线程的方式三:实现Callable接口

package com.atguigu.java2;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * JDK5.0新增的方式
 * 1,创建一个实现Callable接口的实现类,并实现call(),将此线程要执行的操作写在call()中
 * 2,创建Callable接口实现类的对象,并将此对象作为参数传递给FutureTask类的构造器以创建FutureTask类的对象
 * 3,将上面FutureTask类的对象作为参数传递给Thread类的构造器,并创建Thread类的对象,然后调用start()开启一个线程
 * 4,如果需要call()的返回值,通过FutureTask对象的get()就可以拿到
 * 相较于Runnable方式创建线程,Callable方式更强大的点
 *     1,call()可以有返回值
 *     2,call()可以抛出异常并被外面捕获
 *     3,Callable支持泛型
 */

public class ThreadNew {

    public static void main(String[] args) {
        NumThread numThread = new NumThread();

        FutureTask futureTask = new FutureTask(numThread);

        new Thread(futureTask).start();

        try {
            Object sum = futureTask.get(); // get()可以拿到call()的返回值
            System.out.println("总和为:" + sum);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class NumThread implements Callable {

    @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; // 自动装箱,Integer类型
    }
}

 

15,使用线程池的好处

 

16,创建多线程的方式四:使用线程池

package com.atguigu.java2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 开发中一般都是使用线程池创建线程
 * 1,创建指定线程数量的线程池
 * 2,要创建一个线程并执行指定操作,需要提供实现Runnable接口或Callable接口实现类的对象
 * 3,关闭线程池
 * 面试题:创建多线程有几种方式
 */

public class ThreadPool {

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10); // 创建一个大小为10的线程池,
                                                                // 该线程池是一个ExecutorService接口实现类的对象
        System.out.println(service.getClass()); // 查看service的实现类,ThreadPoolExecutor
//        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service; // 使用下面两个方法需要强转为实现类类型,因为接口中没有这些方法
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        service.execute(new NumberThread()); // 开启一个线程
        service.execute(new NumberThread1()); // 只能传入一个实现了Runnable接口的类的对象,该类实现的是Run(),线程执行的也是run()
        // service.submit(); // 可以传入一个实现了Callable接口的类的对象,该类实现的是Call(),可以有返回值,submit()也返回该值
        service.shutdown(); // 用完后关闭线程池
    }
}

class NumberThread implements Runnable {
    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++) {
            if(i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable {
    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i++) {
            if(i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

标签:同步,Thread,void,public,线程,new,多线程,day19
来源: https://blog.51cto.com/u_15268710/3036593

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有