[Java学习笔记]多线程基础(二)

Thread类中常用API

Thread类中有static方法也有非静态方法,static方法是通过Thread类直接调用作用在此时正在执行的线程,而非静态方法则作用于具体的线程对象

  • static Thread currentThread()
    返回对当前正在执行的线程对象的引用。

  • static boolean interrupted()
    测试当前线程是否已经中断。

  • static void yield()
    暂停当前正在执行的线程对象,并执行其他线程。

  • static void sleep(long millis)
    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

  • long getId()
    返回该线程的标识符。

  • String getName()
    返回该线程的名称。

  • int getPriority()
    返回线程的优先级。 (优先级仅仅是为该线程分配时间片的概率,仅此而已)

  • void interrupt()
    中断线程。

  • void join()
    等待该线程终止。

  • void join(long millis)
    等待该线程终止的时间最长为 millis 毫秒。

  • void start()
    使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

  • boolean isAlive()
    测试线程是否处于活动状态。

  • boolean isDaemon()
    测试该线程是否为守护线程。

补充几个在Objct类内的方法

  • void wait()
    在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
  • void wait(long timeout)
    在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待
  • void notify()
    唤醒在此对象监视器上等待的单个线程。
  • void notifyAll()
    唤醒在此对象监视器上等待的所有线程。
  • void wait(long timeout, int nanos)
    在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。

为了能够掌握这些API的使用,我们需要理解Thread线程在执行的状态转换过程:
在这里插入图片描述
值得注意的是:sleep()方法是不会释放线程同步锁的,因此在sleep时间内,同锁的线程是不能占用CPU的,而obj.wait()方法释放线程同步锁,其他线程是可以分配到CPU时间片的,而该阻塞线程在唤醒之前无权占用CPU

sleep和wait区别检测
先来看看sleep()

public class TestSleep {
	static Object lock = new Object();
	public static void main(String[] args) {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("t1获取了锁进入了同步语句块");
					try {
						long sleepbegin = System.currentTimeMillis();
						Thread.sleep(5000);
						System.out.println("睡眠了"+(System.currentTimeMillis()-sleepbegin)+"ms");
						for(int i = 0 ; i < 20 ; i ++)
							System.out.println(Thread.currentThread().getName()+":"+i);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("切换到t2");
					for(int i = 0 ; i < 20 ; i ++)
						System.out.println(Thread.currentThread().getName()+":"+i);
				}
				
			}
		});
		t1.start();
		t2.start();
	}
}

结果如下:我们看到sleep()并不会释放锁供其他同锁线程任务使用CPU。

t1获取了锁进入了同步语句块
睡眠了5000ms
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
Thread-0:17
Thread-0:18
Thread-0:19
切换到t2
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Thread-1:11
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-1:19

接下来是wait的测试

public class TestWait {
	static Object lock = new Object();
	public static void main(String[] args) {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("t1线程开始释放锁等待CPU唤醒!");
					try {
						lock.wait();
						System.out.println("t1线程重新得到锁");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (lock) {
					System.out.println("切换到t2");
					lock.notify();
					for(int i = 0 ; i < 50 ; i ++)
						System.out.println(Thread.currentThread().getName()+":"+i);
				}
				
			}
		});
		t1.start();
		t2.start();
	}
}

结果如下:我们发现即便是在同锁的同步语句内,一旦wait后只能释放锁lock让出CPU分配权。

t1线程开始释放锁等待CPU唤醒!
切换到t2
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Thread-1:11
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-1:19
Thread-1:20
Thread-1:21
Thread-1:22
Thread-1:23
Thread-1:24
Thread-1:25
Thread-1:26
Thread-1:27
Thread-1:28
Thread-1:29
Thread-1:30
Thread-1:31
Thread-1:32
Thread-1:33
Thread-1:34
Thread-1:35
Thread-1:36
Thread-1:37
Thread-1:38
Thread-1:39
Thread-1:40
Thread-1:41
Thread-1:42
Thread-1:43
Thread-1:44
Thread-1:45
Thread-1:46
Thread-1:47
Thread-1:48
Thread-1:49
t1线程重新得到锁

线程同步与线程异步

  • 线程同步:是指多线程在并发执行任务的时候,对于特定的资源,只有当一个线程对资源访问结束后,另一个线程才能够访问,也即线程有序地访问资源。
  • 线程异步:是指多线程在执行任务时,按照随机给定的时间片,各自抢夺特定的资源,因此异步虽然不需要时刻检查是否有线程正在访问一个资源,效率高,但线程必然不安全。

打个比方:火车站的买票机制,像这类高并发的情形,倘若采用不安全的线程机制,就会导致最后一张票可能同时会被多个用户购买,也就出现了很大的问题了,而此时采用同步机制,每张票在已经有用户申请购买的过程中,不会允许其他用户试图买这张票,显然线程安全了。

特殊情况——线程死锁

public class sisuo {
	static Object LockA = new Object();
	static Object LockB = new Object();
	public void a(){
		System.out.println(Thread.currentThread().getName()+"开始执行a方法");
		synchronized (LockA) {
			System.out.println(Thread.currentThread().getName()+"拿到了LockA");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("准备执行b方法");
			b();
		}
	}
	public void b() {
		System.out.println(Thread.currentThread().getName()+"开始执行b方法");
		synchronized (LockB) {
			System.out.println(Thread.currentThread().getName()+"拿到了LockB");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("准备执行a方法");
			a();
		}
	}
	
	public static void main(String[] args) {
		sisuo k = new sisuo();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				k.a();
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				k.b();
			}
		});
		t1.start();
		t2.start();
	}
}

结果如下:我们发现在Thread0执行b方法时无法进入synchronized(lockB),而Thread1在执行a方法时无法进入synchronized(lockA)。这是因为Thread0 run方法在a方法执行完之前永远不会释放lockA,导致Thread1永远无法获得lockA完成a方法而永远不会释放lockB,而Thread0需要lockB来完成b方法。

Thread-1开始执行b方法
Thread-0开始执行a方法
Thread-1拿到了LockB
Thread-0拿到了LockA
Thread-0准备执行b方法
Thread-0开始执行b方法
Thread-1准备执行a方法
Thread-1开始执行a方法
(此后一直程序运行阻塞。。。)

线程池

线程池的概念
• 首先创建一些线程,它们的集合称为线程池,当服务器接收一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
• 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。
• 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

  • 总接口: ExecutorService
  • 实体类:ThreadPoolExecutor
  • 工具类:Excutors——用于构建不同分配任务策略的线程池ThreadPoolExecutor对象
public class ThreadPoolDemo {
	static int index = 0 ;
	public static void main(String[] args) {
		ExecutorService es = Executors.newFixedThreadPool(2);//设定该线程池内线程个数
		for(int i = 0 ; i < 10 ; i ++) {//建立十个任务对象分配给线程池
			es.execute(new Runnable() {//将任务对象封装入线程池,线程池分配任务给池内空闲线程,并开启线程执行任务
				@Override
				public void run() {//只有空闲线程对象才能被授予任务
					synchronized (es) {
						System.out.println(Thread.currentThread().getName()+": "+ index ++);
					}
					try {
						Thread.sleep(2000);
						System.out.println(Thread.currentThread().getName()+": 执行完毕");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
				}
			});
			
		}
		es.shutdown();
	}
}

结果如下:我们看到——实际上总共就2个线程在反复的分配任务,只有池内有空闲的线程才能执行任务

pool-1-thread-1: 0
pool-1-thread-2: 1
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
pool-1-thread-1: 2
pool-1-thread-2: 3
pool-1-thread-2: 执行完毕
pool-1-thread-1: 执行完毕
pool-1-thread-1: 4
pool-1-thread-2: 5
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
pool-1-thread-1: 6
pool-1-thread-2: 7
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕
pool-1-thread-1: 8
pool-1-thread-2: 9
pool-1-thread-1: 执行完毕
pool-1-thread-2: 执行完毕

线程池的submit和execute方法区别
线程池中的execute方法,即开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果。下面简要介绍一下两者的三个区别:

1、submit有返回值,而execute没有
用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。
然后我就可以把所有失败的原因综合起来发给调用者。

个人觉得cancel execution这个用处不大,很少有需要去取消执行的。

而最大的用处应该是第二点。
2、submit方便Exception处理
意思就是如果你在你的task里会抛出checked或者unchecked exception,
而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

public class RunnableTestMain {

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        
        /**
         * execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。
         */
        pool.execute(new RunnableTest("Task1")); 
        
        /**
         * submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。请看下面:
         */
        Future future = pool.submit(new RunnableTest("Task2"));
        
        try {
            if(future.get()==null){//如果Future's get返回null,任务完成
                System.out.println("任务完成");
            }
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
            //否则我们可以看看任务失败的原因是什么
            System.out.println(e.getCause().getMessage());
        }

    }

}

public class RunnableTest implements Runnable {
    
    private String taskName;
    
    public RunnableTest(final String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println("Inside "+taskName);
        throw new RuntimeException("RuntimeException from inside " + taskName);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值