Java多线程

本文深入探讨Java多线程核心技术,包括线程控制方法如wait/notify、线程池使用及配置、Callable与Future实现机制等。同时介绍了Executor框架、线程安全集合类以及原子变量的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.(wait/notify/sleep/yield/join)

synchronized, wait, notify结合:典型场景生产者消费者问题


1、sleep方法
   sleep方法的作用是让当前线程暂停指定的时间(毫秒),需要注意的是其与wait方法的区别。最简单的区别是,wait方法依赖于同步,而sleep方法可以直接调用。而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁,而wait方法则需要释放锁。简单点理解就是使用sleep方法,线程是处于阻塞状态的,而调用wait方法,线程会在被唤醒后重新进入就绪状态竞争CPU资源。

2、yield方法
  yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将线程转变为就绪状态。与wait方法类似,但是线程在调用yield方法后,不需要被唤醒,而是直接进入就绪参加CPU资源竞争。 
  在实际开发过程中这个方法的使用场景很少,主要使用的地方是调试和测试。

3、join方法
  join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。JDK中提供三个版本的join方法,其实现与wait方法类似,join()方法实际上执行的join(0),而join(long millis, int nanos)也与wait(long millis, int nanos)的实现方式一致,暂时对纳秒的支持也是不完整的。

4、wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),直到notify()或notifyAll()方法来唤醒线程. 

5、wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify()或notifAll()方法的唤醒,也会自动唤醒。


2、Callable与Futura

使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
Runnable、Callable、Future、FutureTask的区别与示例

public class ThreadTest {


    public static void main(String[] args) {


        Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象


        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
                thread.start();                      //线程进入到就绪状态
            }
        }


        System.out.println("主线程for循环执行完毕..");
        
        try {
            int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }
}




class MyCallable implements Callable<Integer> {
    private int i = 0;


    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
        return sum;
    }

}
首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。
于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。
执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢?
原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。


3、java中Executor、ExecutorService、ThreadPoolExecutor介绍
Executor:java多线程管家,Executor允许你管理异步任务的执行,而无须显示地管理线程的生命周期。
ExecutorService扩展了Executor接口,添加了一些用于生命周期管理的方法。


Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
	final int index = i;
	try {
		Thread.sleep(index * 1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}


	cachedThreadPool.execute(new Runnable() {


		@Override
		public void run() {
			System.out.println(index);
		}
	});
}


线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为: 


ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, 
long keepAliveTime, TimeUnit unit, 
BlockingQueue<Runnable> workQueue, 
RejectedExecutionHandler handler) 
corePoolSize: 线程池维护线程的最少数量 
maximumPoolSize:线程池维护线程的最大数量 
keepAliveTime: 线程池维护线程所允许的空闲时间 
unit: 线程池维护线程所允许的空闲时间的单位 
workQueue: 线程池所使用的缓冲队列 
handler: 线程池对拒绝任务的处理策略 


一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。 


当一个任务通过execute(Runnable)方法欲添加到线程池时: 


如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 
如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。 
如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。 
如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。 


也就是:处理任务的优先级为: 
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 


当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。 


unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性: 
NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。 


workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue 


handler有四个选择: 
ThreadPoolExecutor.AbortPolicy() 
抛出java.util.concurrent.RejectedExecutionException异常 
ThreadPoolExecutor.CallerRunsPolicy() 
重试添加当前的任务,他会自动重复调用execute()方法 
ThreadPoolExecutor.DiscardOldestPolicy() 
抛弃旧的任务 
ThreadPoolExecutor.DiscardPolicy() 
抛弃当前的任务 
int corePoolSize = 5;

 int maxPoolSize = 10;

 long keepAliveTime = 5000; 

ExecutorService threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() );

自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,主要有以下几个:
  ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
  LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
  PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
  DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。


4、多线程集合

ArrayBlockingQueue
LinkedBlockingQueue
数组是连续的一片内存
链表是不连续的一片内存
 传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合。
java5中提供了如下一些同步集合类:
  > 通过看java.util.concurrent包下的介绍可以知道有哪些并发集合
  > ConcurrentHashMap 可以进行并发操作的HashMap,并发的HashMap还有 Collections.synchronizedMap(m) ,有了ConcurrentHashMap后,Collections.synchronizedMap(m)不怎么使用了。
  >ConcurrentSkipListMap 实现了SortedMap<K,V>  ,类似于TreeMap
  >ConcurrentSkipListSet 实现了SortedSet, 类似于TreeSet
  > CopyOnWriteArrayList
  > CopyOnWriteArraySet
传统方式下的Collection在迭代时,不允许对集合进行修改。


5、Java多线程并发编程之原子变量与非阻塞同步机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值