多线程
1. 多线程的第一种启动方式
继承Thread类的方式进行实现
1.自己定义一个类继承Thread。
2.重写run方法。
public class Mythread extends Thread{
@Override
public void run(){
System.out.println("Mythread is running");
}
}
3.创建子类对象,开启线程。
Mythread t1 = new Mythread();
t1.start();
*** 注意:不得直接调用run()方法,否则将是直接调用方法而未开启线程***
2.多线程的第二种启动方式
实现Runnable接口的方式进行实现
自己定义一个类,实现Runnable接口
重写run方法
public class MyRun implements Runnable
{
@Override
public void run()
{
System.out.println("Thread is running");
}
}
创建Thread对象并开启线程
//创建MyRun对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t = new Thread(mr);
t.start();
3.多线程的第三种启动方式
实现Callable接口的方式进行实现
特点:可接受多线程运行结果
自己定义一个类,实现Callable接口
重写call方法
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1到100的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
Callabe接口中包含一个泛类,为接受结果的类型。
创建Mycallable的对象和FutureTask的对象,FuterTask对象用于管理多线程运行的结果
public class ThreadTest3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
//创建FuterTask对象,管理多线程运行的结果。
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
t1.start();
// 获取线程执行结果
Integer i = ft.get();//get()方法报异常,直接抛出即可。
System.out.println("线程执行结果:" + i);
}
}
三种实现方式对比
总结:需要接受运行结果用实现Callable接口。继承Thread类可以扩展性差,不能再继承其他的类,实现Runnable接口扩展性强,实现接口的同时可以继承其他的类。
线程的优先级
JAVA中,线程的调度方式为:抢占式调度。
抢占式调度:随机性调度。优先级越大,这条线程抢到CPU的概率越大。
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"A");
Thread t2 = new Thread(mr,"B");
System.out.println(t1.getPriority());// 5
System.out.println(t2.getPriority());// 5
优先级默认5,最小1,最大10。
保护线程
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"A");
t1.setDaemon(true);
使用.setDaemon(true)将改线程设置为保护线程。
保护线程:也叫备胎线程。当非守护线程执行完毕之后,守护线程没有存在的必要,将陆续结束。(不是立即结束)
例:聊天框中的文件传输线程,当聊天框关闭,文件传输将陆续停止。
礼让线程
public class MyRun implements Runnable
{
@Override
public void run()
{
System.out.println("Thread is running");
Thread.yield();
}
}
Thread.yield() 表示出让CPU的执行权。可以尽量让结果均匀。
插入线程
public class ThreadTset2 {
public static void main(String[] args) throws InterruptedException {
//创建MyRun对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
Thread t = new Thread(mr);
t.start();
t.join();
for (int i = 0; i < 10; i++) {
System.out.println("main"+i);
}
}
}
.join()方法表示将t线程插入到当前线程(main线程)之前,插入线程运行结束后,再执行当前线程。
线程的生命周期
创建:创建线程对象 start() -> 就绪(不停的抢cpu执行权):有执行资格,无执行权
抢到CPU的执行权 ** -> 运行:有执行资格,有执行权** 执行完毕 -> 死亡: 线程死亡,变成垃圾。
运行状态时,若被其他线程抢走CPU执行权,将回到 就绪状态,重新抢夺CPU执行权。若调用了sleep()或其他阻塞式方法,会进入阻塞等着状态,没有执行资格也没有执行权,直到阻塞结束后回到就绪状态。
若遇到sleep()和wait()状态,也会进入阻塞等着状态。
注意! 阻塞方式结束后,无法立即执行下面代码,因就绪状态还需抢夺CPU执行权,抢夺后才能继续运行。
线程安全问题
例如购票时 高并发产生的超卖情况。
public class Mythread extends Thread{
static int ticket = 0;
static Object obj = new Object();
@Override
public void run(){
while (true){
if(ticket<100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println("线程"+Thread.currentThread().getName()+"卖票,票号为:"+ticket);
}else{
break;
}
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
Mythread t1 = new Mythread();
Mythread t2 = new Mythread();
Mythread t3 = new Mythread();
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
可能出现异常结果:线程2卖票,票号为:101
同步代码块
public class Mythread extends Thread{
static int ticket = 0;
//锁对象
static Object obj = new Object();
@Override
public void run(){
while (true){
//同步代码块
synchronized (obj){
if(ticket<100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println("线程"+Thread.currentThread().getName()+"卖票,票号为:"+ticket);
}else{
break;
}
}
}
}
}
使用synchronized锁锁定代码块。
sychronized(锁对象),锁对象任意, 甚至是Object类,但是要保证锁对象唯一。
等待唤醒机制
wait() 当先线程等待,直到被其他线程唤醒
notify()随机唤醒单个线程
notifyAll()唤醒所有线程
public class Desk {
public static int foodFlag = 0;//桌子上食物状态:0无,1有
public static int count = 10;//上菜最多10次
public static Object lock = new Object();//锁
}
中间件:桌子
public class Foodie extends Thread{
@Override
public void run(){
while(true){
synchronized (Desk.lock){
if(Desk.count == 0)
{
break;
}else {
if(Desk.foodFlag == 0){
try {
Desk.lock.wait();//由锁调用wait方法,使当前线程和锁绑定,wait会释放锁!
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Desk.count--;
System.out.println("Foodie is eating"+"还能吃"+Desk.count+"次");
Desk.lock.notifyAll();//唤醒等待的厨师
Desk.foodFlag = 0;
}
}
}
}
}
}
消费者:食客
public class Cook extends Thread{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.foodFlag == 1){//判断桌子上是否有一份食物
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
System.out.println("厨师做了一碗饭");
Desk.foodFlag = 1;//上菜一份
Desk.lock.notify();
}
}
}
}
}
}
生产者:厨师
public class ThreadDemo {
public static void main(String[] args) {
Cook c1 = new Cook();
Foodie f1 = new Foodie();
c1.setName("Chef");
f1.setName("Foodie");
c1.start();
f1.start();
}
}
阻塞队列实现等待唤醒机制
阻塞队列实现类:
- ArrayBlockingQueue : 底层是数组,有界,创建时需要指定大小
- LinkedBlockingQueue : 底层是链表,无界,但不是真正的无界,最大为int最大值
生产者
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
queue.put("吮指原味鸡");//此处不用加锁,因为put()方法中已经有锁,再加锁会出现锁的嵌套,容易死锁
System.out.println("上菜一份");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
String food = queue.take();//此处不用加锁,因为put()方法内已经有锁,再加锁会出现锁的嵌套,容易死锁
System.out.println("获取到食物:"+food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//指定队列大小1,存储String类型数据
//创建线程对象,并把队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
c.start();
f.start();
}
}
可能出现重复打印语句的情况,因为打印语句在锁外面,打印重复不影响数据操作。
线程池
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池
public class MyThreadPoolDemo {
public static void main(String[] args) {
// ExecutorService pool1 = Executors.newCachedThreadPool();//获取线程池对象
ExecutorService pool1 = Executors.newFixedThreadPool(3);//获取线程池对象
//提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.shutdown();//关闭线程池
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" is running "+ i);
}
}
}
- 创建一个池子,池子中是空的
- 提交任务时,池子创建新线程对象,任务执行完毕,线程归还给池子,下回再提交任务的时候,不需要创建新线程,直接复用已有的线程
- 若提交任务时,池子中无空闲线程,也无法创建新的线程,任务将排序等待
任务拒绝策略
ThreadPoolExecutor.AbortPolicy : 丢弃任务抛出异常(默认)
ThreadPoolExecutor.DiscardPolicy : 丢弃任务,但是不抛出异常(不推荐)
ThreadPoolExecutor.DiscardOldestPolicy : 抛弃队列中等待时间最久的任务,然后把当前任务加入队列
ThreadPoolExecutor.CallerRunsPolicy : 调用任务的run()方法,绕过线程池直接执行
自定义线程池
线程池大小:
- CPU密集型运算:项目运算较多,读取本地数据较少。 最大线程池大小为最大并行数+1
- IO密集型运算:项目读取本地数据或数据库数据较多,运算较少。(大部分项目为这个类型) 最大线程池大小为 最大并行数 * 期望利用率 * (CPU运行时间+等待时间)/CPU运行时间
- 例: 4核8线的CPU中,最大并行数为8 期望利用率100,运行时间1s,等待时间1s。公式计算: 8 * 100% * 2 = 16 则线程池合适大小为16
public class threadpoolDemo {
public static void main(String[] args) {
//核心线程数量,最大线程数,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务拒绝策略
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
4,
1,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
new ThreadPoolExecutor.AbortPolicy());
}
}