Java - 【线程】Thread

目录

  • 引入
  • 多进程与多线程的作用
  • 线程五态模型
  • 线程创建的三种方法
  • 线程的基本操作
  • 临界区问题(临界资源同步)
    • 同步代码块
    • 同步非静态方法
    • 同步静态方法
  • 生产者消费者问题
    • BlockingQueue
  • 线程池(ExecutorService)
  •  
  • 如何证明线程中指令会发生乱序?


引入一(Semaphore)

编写两个进程,循环交替输出数字和26个大写英文字母;

输出示例:1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z27A28B

package xyz.xx;

import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 两个线程交替输出1A2B3C4D5E...
 *  由于此处使用了信号量,两个线程不会出现同时操作临界区的情况,程序中的num可以使用一般的int类型进行替代;
 *      解决方案:
 *          使用信号量Semaphore类(JDK1.5中新增的额java.util.concurrent包下的类)
 *              两个信号量交替使用;
 *                  sem.acquire()   ->  P
 *                  sem.release()   ->  V
 */
public class T01_interlaced {
    private static AtomicInteger num = new AtomicInteger(0);
    private static final Object lock = new Object();
    private static Semaphore sem1 = new Semaphore(1);
    private static Semaphore sem2 = new Semaphore(0);

    public static void main(String[] args) {
        Thread numberThread = new Thread("number_thread"){
            @Override
            public void run() {
                    for (; ; ) {
                        try {
                            sem1.acquire();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        num.incrementAndGet();
                        System.out.print(num);
                        // CAS : Compare And Swap
                        sem2.release();
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
            }
        };

        Thread charThread = new Thread(()->{
                for (; ; ) {
                    try {
                        sem2.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.print(Character.toChars(((num.get()-1) % 26) + 65));
                    sem1.release();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        },"charThread");

        numberThread.start();
        charThread.start();
    }

}

引入二(CountDownLatch)

import java.util.concurrent.CountDownLatch;

/**
 * 测试CountDownLatch类的使用(本质上是一个计数器
 *
 *      JDK1.5中新增的API,同时增加的类还有Semaphore等
 *
 * 初始化构造参数为线程的数量
 *      线程执行完毕后手动调用countDown进行-1
 *
 *      主线程使用await进行阻塞式等待,当CountDownLatch对象值为0时再次开始执行
 */
public class CountDownLatchTest {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(2);

        Thread th1 = new Thread("th1"){
            @Override
            public void run() {
                for(int i=0;i<100000;i++){
                    System.out.println("hello");
                }
                latch.countDown();
            }
        };

        Thread th2 = new Thread(()->{
            for(int i=0;i<100000;i++){
                System.out.println("world");
            }
            latch.countDown();
        },"th2");

        th1.start();
        th2.start();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

<一> 多进程与多线程的作用

1> 多进程:提高CPU利用率

2> 多线程:提高程序执行效率

<二> 线程五态模型

<三> 线程创建的三种方法

1> Thread类构造函数

2> 实例

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

/**
 * 测试线程的三种创建方式
 *      1. Thread
 *          start
 *      2. Runnable
 *          start
 *      3. Callable + FutureTask (可以获得线程结束返回值)
 *          run
 *          get
 *
 *      FutureTask类
 *          (实现)RunnableFuture接口(->仅仅提供了run()方法)
 *               (继承)Runnable接口+Future接口
 */
public class ThreadTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Thread的匿名子类
        Thread th1 = new Thread("Thread_1"){
            @Override
            public void run() {
                for(;;){
                    System.out.println("hello");
                }
            }
        };

        // Runnable的Lambda表达式形式
        Thread th2 = new Thread(()->{
            for(;;){
                System.out.println("world");
            }
        },"Thread_2");

        // 使用Callable
        testThread3();
    }

    private static void testThread3() throws InterruptedException, ExecutionException {
        Callable<String> myCallable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "end";
            }
        };
        FutureTask<String> task = new FutureTask<>(myCallable);
        new Thread(task).start();

        // TimeUnit枚举类调用sleep底层还是调用了当前线程的sleep方法
        TimeUnit.SECONDS.sleep(1);

        System.out.println(task.get());
    }
}

3> 相关类的继承关系

 

<四> 线程的基本操作

1> 获取/设置线程名称:getName()、setName()

2> 获取/设置线程优先级:getPriority()、setPriority(...)

3> 线程优先级范围:[1,10]

4> 线程开启:start()

5> 线程结束:interrupt()

6> 获取当前正在执行的线程名:Thread.currentThread().getName()

7> run()与start()有何区别?run()为类中的一般方法,start()执行时会创建线程并调用线程的run()

8> 如果子类想要实现多继承,该如何操作?

<五> 临界区问题(临界资源同步)

package kyleeo.util_02;

/*
 *	新建类实现Runnable接口,该接口中只有一个run方法
 *  注意这种方式与extends Thread的区别?
 *  	a) 解决了单继承的局限性(如果子类想要实现多线程,不能多继承,但是可以多实现)
 *      b) 多个进程可以共享一份数据(将数据与代码解耦合)
 *  
 *  同步(线程安全):
 *  	
 */
public class MyThread implements Runnable {
	private static int ticket = 100;
	private Object lock = new Object();

同步代码块
//	@Override
//	public void run() {
//		synchronized (lock) {
//			while (true) {
//				if (ticket > 0) {
//					for (int i = 0; i < 100; i++) {
//						System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票!");
//					}
//				}
//			}
//		}
//	}
	
同步方法
//	@Override
//	public void run() {
//		synchronized (this) {
//			sellTicket();
//		}
//	}
//
//	 public synchronized void sellTicket() { 
//		 while (true) {
//				if (ticket > 0) {
//					for (int i = 0; i < 100; i++) {
//						System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票!");
//					}
//				}
//			}
//	 }

同步静态方法
	@Override
	public void run() {
		//同步类的class文件,静态方法随类的加载而产生
		synchronized (MyThread.class) {
			sellTicket();
		}
	}

	 public static synchronized void sellTicket() { 
		 while (true) {
				if (ticket > 0) {
					for (int i = 0; i < 100; i++) {
						System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票!");
					}
				}
			}
	 }
}

<六> 生产者消费者问题

JDK1.5:BlockingQueue接口

JDK1.6:BlockingDeque接口 -> LinkedBlockingDeque实现类初始化时推荐指定大小

public class Producer implements Runnable {
	private final BlockingQueue<String> queue;
	private int i;

	public Producer(BlockingQueue<String> q){
		queue = q;
	}
	@Override
	public void run() {
		try{
			while(true){
				queue.put(produce());
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	public synchronized String produce(){
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		String putData = String.valueOf(i++);
		System.out.println("Produce:"+ putData);
		return putData;
	}
}

public class Consumer implements Runnable {
	private final BlockingQueue<String> queue;

	public Consumer(BlockingQueue<String> q) {
		queue = q;
	}

	@Override
	public void run() {
		try{
			while(true){
				consume(queue.take());
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	public void consume(String data) {
		System.out.println("Consume:"+data);
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


public class ComsumerProducerDemo {
	public static void main(String[] args) {
		BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
		Producer p1 = new Producer(queue);
		Consumer c1 = new Consumer(queue);
		Consumer c2 = new Consumer(queue);
		new Thread(p1).start();
		new Thread(c1).start();
		new Thread(c2).start();
	}
}

<七> Volatile线程通信

package xyz.kyleeo.vola;

public class VolatileDemo {

	public static void main(String[] args) throws Exception {
		
		volatileTest();
		
	}
	/**
	 * volatile会为该变量在编译时自动添加写锁
	 */
	private static volatile boolean flag = false;

	private static void volatileTest() throws Exception {
		Thread th1 = new Thread("线程1") {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					if (i == 5) {
						flag = true;
						try {
							Thread.sleep(500L);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						break;
					}
					System.out.println(Thread.currentThread().getName() + "====" + i);
				}
			}
		};

		Thread th2 = new Thread("线程2") {
			@Override
			public void run() {
				while (true) {
					while (flag) {
						System.out.println(Thread.currentThread().getName() + "收到通知");
						System.out.println("th2 do something");
						try {
							Thread.sleep(500L);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						return;
					}
				}
			}
		};
		Thread th3 = new Thread("线程3") {
			@Override
			public void run() {
				while (true) {
					while (flag) {
						System.out.println(Thread.currentThread().getName() + "收到通知");
						System.out.println("th3 do something");
						try {
							Thread.sleep(500L);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						return;
					}
				}
			}
		};

		th2.start();
		th3.start();
		Thread.sleep(1000L);
		th1.start();

	}
}

<八> 线程池

JDK1.5新增内容,方便管理与使用线程;

不推荐使用Executors直接创建线程池(容易OOM),而是使用ThreadPoolExecutor代替(Executors实现类)!

参数分析:

  • corePoolSize:线程池的核心线程数,即便线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
  • maximumPoolSize:最大线程数,不管提交多少任务,线程池里最多工作线程数就是maximumPoolSize。
  • keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程池中该线程停止,通过定时来动态调整线程池大小。
  • Unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。
  • BlockingQueue:一个阻塞队列,提交的任务将会被放到这个队列里,LinkedBlockingQueue为其一种实现类;
  • threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
  • handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。

线程工厂一般使用默认即可,构造函数使用前两个,关于拒绝策略:

关于内部类的使用,请参考《 JAVA - 【类的嵌套】内部类 》;

具体使用

public class Main {
	public static void main(String[] args) {
		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(5);
		// P6:默认线程工厂
		// P7:LRU拒绝策略
		ExecutorService threadPool1 = new ThreadPoolExecutor(5, 5, 10, TimeUnit.SECONDS, queue,
				new ThreadPoolExecutor.DiscardOldestPolicy());

		threadPool1.execute(new Runnable() {
			private int i;
			@Override
			public void run() {
				while (true) {
					System.out.println("AAA:" + i++);
					try {
						Thread.sleep(300);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}

		});
		threadPool1.execute(new MyRun());
	}
}

☞ 线程池原理解析(分析基于上述程序参数)

核心线程数5:进程池到时间后收缩回到的容量(最初创建该数量的线程,结束时候不会被回收,一直运行的线程);

队列长度5:当进程数超出核心线程数时线程会被放入该队列(不会被立刻创建);

线程池大小5:当核心线程数满且队列满时,该线程立马创建,线程结束时候被放入线程池(并不是立刻终止);

如果现在立刻提交运行16个线程,前五个会立刻创建新线程,随后不释放维持核心线程,

6-10个会被放入线程队列不执行,

11-15个会立刻创建执行,11-15线程结束后并不会被立刻释放,在等待keepAliveTime后关闭这些进程,过期时间可以确保线程池动态调节大小,

第16个线程大小超出5+5+5,会被关闭,随后执行拒绝策略;

当15个线程结束时,最终剩余5个线程,虽然没有任务,但是一直在运行,有新线程任务传入时会直接调用存在的线程执行新任务。



DATE:2020-10-14更新

如何证明线程中指令会发生乱序?

/**
 * 线程指令乱序证明
 */
public class MessInstruction {
    private static int x = 0,y = 0;
    private static int a = 0,b = 0;
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for(;;){
            i++;
            Thread one = new Thread(()->{
                a = 1;
                x = b;
            });
            Thread other = new Thread(()->{
                b = 1;
                y = a;
            });
            one.start();other.start();
            one.join();other.join();
            if(x==0&&y==0){
                String res = i+"次: x=0 y=0";
                System.out.println(res);
                break;
            }
        }
    }
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值