1、创建一个继承于Thread的子类
2、重写run()方法
3、创建Thread类的子类对象
4、通过对象调用start()方法
start方法的作用:启动当前线程 调用当前线程的run方法(启动一个新线程)
如果直接调用run()方法不会启动新线程,还是在主进程里执行。
不能让已经start()的线程再次执行第二次,需要再次新建一个
最终结果是交替执行,执行结果和顺序不确定
public class ThreadDemo { public static void main(String[] args) { // 创建线程的匿名子类方法 new Thread() { @Override public void run() { for(int i = 0;i < 100;i++){ if(i%2 == 0){ System.out.println(Thread.currentThread().getName()+":" + i); } } } }.start(); new Thread() { @Override public void run() { for(int i = 1;i < 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName()+":" + i); } } } }.start(); } }
Thread.currentThread().setName("")....
yield():释放cpu的执行权,有可能cpu还会执行此线程
join():在线程a中调用线程b的join方法,此时线程a进入阻塞状态,b进入运行态直到线程b完全执行完毕,线程a结束阻塞状态
sleep(long milltime):休眠阻塞一段时间
等级
方法
优先级高的表示被cpu执行的概率高
/** 创建多线程Runnable接口 * 1、创建一个实现了Runnable接口的类 * 2、实现类去实现Runnable中的抽象方法run() * 3、创建实现类的对象 * 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 * 5、通过Thread类的对象调用start()方法 * @author DingJun * @date 2021/7/12 19:29 */ // 1、创建一个实现了Runnable接口的类 class MThread implements Runnable { // 2、实现类去实现Runnable中的抽象方法run() @Override public void run() { for(int i = 0;i < 100; i++){ if(i % 2 == 0){ System.out.println(i); } } } } public class ThreadTest1 { public static void main(String[] args) { // 3、创建实现类的对象 MThread mThread = new MThread(); // 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 Thread t1 = new Thread(mThread); // 通过Thread类的对象调用start() // 1、启动进程 // 2、调用当前线程的run()--->调用了Runnable类型target中的run t1.start(); } }
源码Thread的构造函数中有public Thread(Runnable target)
的构造方法,源码中Thread.run()方法如下:
public void run() { if(target != null) { target.run(); } }
优先选择实现Runnable接口的方式
原因: 1、实现方式没有类的单继承的局限性
2、实现的方式更适合处理多个线程有共享数据的情况
联系: public class Thread implements Runnable
都要重写run方法
因为线程同步问题,cpu抢占时机不对而造成对共享资源的不正当访问而发生错误的现象。
synchronized(同步监视器) {
// 需要被同步的代码(操作共享数据的代码)
// 这里面的代码当有线程进入时,则其他线程不能进入
}
同步监视器 锁:任何一个类的对象都可以充当锁
要求:多个线程必须共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,我们可以使用this充当同步监视器
在继承Thread 类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类(类.class)充当同步监视器。
public synchronized void run()
,在函数返回类型前面添加关键字
在同步代码内部是操作共享的代码,注意进行分割和组合
同步监视器:this
当使用同步方法对继承Thread类的多线程时,使用static synchronized void show()
,同步监视器是当前的类(唯一的,Window.class)。
private ReentrantLock lock = new ReentrantLock(); lock.lock() ------ lock.unlock()
wait、notify、notifyAll
三个方法必须使用在同步代码块或同步方法中
这三个方法调用都省略了this(也就是当前对象),注意要与同步代码块或同步方法中的同步监视器一致...
这三个方法都是定义在java.lang.Object类中。
/** 线程通信,两个线程交替打印1到100 * @author DingJun * @date 2021/7/13 21:04 * wait(): 一旦执行此方法:当前线程进入阻塞状态,并释放同步监视器(锁) * notify():唤醒wait的一个线程,如果有多个线程被wait,优先唤醒优先级高的线程 * notifyAll():唤醒所有被wait的线程 */ class Number implements Runnable { private int num = 1; @Override public void run() { while (true) { synchronized (this) { // 唤醒线程 notify(); if(num <= 100) { // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + ":" +num); num++; try { // 使得当前进程进入阻塞状态 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } public class CommuncitionTest { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.start(); t2.start(); } }
1、同:都可以使得当前的线程进入阻塞状态
2、不同: 1) 两个方法声明位置不同:Thread类中声明sleep(),Object类中声明 wait().
2)调用的范围要求不同:sleep可以在任何需要的场景调用。wait()必须使用在同步代码块或同步方法中.
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁.
package com.ding; /** 消费者和生产者问题 * 生产者将产品交给店员,消费者取走产品,店员一次只能持有固定数量的产品,如果生产者试图生产更多的产品,如果有空位放产品了再通知生产者 * 继续生产;如果没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品 * 共享数据:店员、产品数量 * @author DingJun * @date 2021/7/14 10:59 */ class Clerk { private int productCount = 0; // 生产产品 public synchronized void produceProduct() { if(productCount < 20) { productCount++; System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品"); // 唤醒消费者线程,此时店员处有产品 notify(); }else { // 等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } // 消费产品 public synchronized void consumerProduct() { if(productCount > 0) { System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品"); productCount--; // 唤醒生产者线程,此时产品被消费了一个肯定小于20 notify(); }else { // 等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 生产者线程 class Producer extends Thread { private Clerk clerk = new Clerk(); public Producer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName() + ":生产者开始生产..."); while (true) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } clerk.produceProduct(); } } } // 消费者线程 class Consumer extends Thread { private Clerk clerk = new Clerk(); public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName() +":消费者正在消费"); while (true) { try { Thread.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); } clerk.consumerProduct(); } } } public class TransportTest { public static void main(String[] args) { Clerk clerk = new Clerk(); // 声明店员对象 // 共用同一个店员 Producer p1 = new Producer(clerk); Consumer c1 = new Consumer(clerk); p1.setName("生产者"); c1.setName("消费者"); p1.start(); c1.start(); } }
get返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
实现Callable接口的方式创建多线程比实现Runnable接口更强大?
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响比较大
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用
提高响应速度,降低资源消耗,便于线程管理
// 1、指定线程数量的线程池,ExecutorService是一个接口 ExecutorService service = Executors.newFixedThreadPool(10); // 设置线程池的属性 ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; service1.setCorePoolSize(15); service1.setKeepAliveTime(); maximumPoolSize(); // 2、执行指定的线程操作,需要提供实现Runnable 接口或Callable接口实现类的对象 service.execute(new NumThread()); service.execute(new NumThread1()); // 适用于Runnable接口 // service.submit(Callable callable); // 适用于Callable接口实现类 // 3、关闭连接池 service.shutdown();
上面的ExecutorService
只是一个接口,ThreadPoolExeutor类实现了此接口,该类有设置线程池属性的方法,进行强转。