java 多线程编程常用类和接口

Java并发编程:多个线程同时访问共同的一块资源。

  1. synchronized关键字:
    1. synchronized 用法,

Object o = new Object();

public void m(){

synchronized(o){

return 123;

}

这里的o表示的是想要执行m方法,需要去获取到锁,而这个锁就是objectg对象,需要注意的是,要获取这个锁,不是争夺o的对象引用,而是要去堆内存中争夺new出来的object对象锁。

如果在方法前面直接加上synchronized关键字,其实和这个用法是一致的,为了简便书写可以将o写成this,表示调用的是对象本身。

2、volatile关键字:

注意:volatile只能保证可见性,不能保证一致性和原子性。

a)在一个全局变量前面加上volatile关键字,表示这个变量可以使得多个线程都可见。

b)比如现在有两个线程,cpu有两块区域运行这两个线程,由于cpu内部是有缓存机制的,所以每一个线程会去java虚拟机的主内存中,将变量克隆到cpu的缓冲区当中,如果要使用这个变量,直接只用缓冲区中的变量就可以了,这样就不用每次都去主内存中重新获取一次当前变量。但是问题也就来了,如果不加volatile关键字,这个变量就感知不到主内存中的变量值是否有变化,如果加上volatile关键字之后,如果主内存中的变量被某个线程修改了,就会通知其他所有线程这个变量发生了变化。

c)cpu缓冲存的数据是数据的数值,所以变量的值变化,cpu是不知道的。

3、一个被synchronized修饰的方法在运行的时候,是可以执行其他非synchronized方法的。也是可以被打断的。

4、a线程正在执行一个对象中的同步方法,b线程也是可以执行同一个对象中的非同步方法的。

5、wait和notify:

Wait让一个锁等待,会释放锁。Notify唤醒一个线程的锁,不会释放锁。这个锁就是同步时候选择的锁对象。

Object lock = new Object();

Synchronized(lock);

Lock.wait();

Lock.notify();

记住,是锁的唤醒和等待,而不是线程的。再说,唤醒锁是不能指定一个线程的来执行接下来的操作的,是随机选择的。比如你去上厕所,上厕所的时候是需要上锁的,但是你上完厕所出来之后,你不能指一个后面排队的人去上厕所的,毕竟是公用的,这时候,就是由cpu指定其他排队的人争夺上厕所的权利。谁能抢到这个锁,谁就可以去上厕所。

6、如何提高锁的性能:

加锁的代码块尽量的少。可以提高锁的性能。

7、java高并发编程:

a)同步器synchronizer

b)同步容器

c)线程池 threadpool、executor

8、替代synchronized的手工锁,reentrantkock:

Lock lock = new ReentrantLock();

Void m(){

Lock.lock();  ==》》相当于   synchronized(this)

Code……;

Lock.unlock();

一定要注意 需要手动释放锁!!!

Synchronized 手动上锁,自动释放 非公平锁(每次竞争锁都是随机的,是不可控的)

Reentrantlock 手动上锁,手动释放  公平锁(可以指定)

使用Synchronized加锁的线程,如果a线程正在执行,那么b线程就需要一直等待,不会再进行其他的操作。

使用reentrantlock加锁的线程,如果a线程正在执行,那么b线程可以使用lock.tryLock()尝试获取锁,返回一个boolean类型数据,如果获取到锁了,那么就需要进行lock.unlock()

而且还可以使用trylock来看是否还需要继续等待a线程执行完。使用reentrantlock中的方法可以打断另一个线程。

9、生产者、消费者模式:

写法规范,wait和while百分之九十都是配合使用的。原因是如果一个线程满足条件,lock.wati();了,当lock.notifyall(),之后,所有的线程都会重新竞争锁,只是如果用的是if判断,那么只会判断一次,当下一个线程获取到锁拥有执行权的时候,他会接着之前wati的地方继续向下执行,容易出现逻辑的错误。但是如果使用while判断,当下一个线程获取到锁拥有执行权的时候,,他会回到判断的哪行代码,先进行判断,然后继续向下执行。这在生产者、消费者模式中经常见到。

还可以使用reentrantlock实现精确指定线程。

Reentrantlock lock = new ReentrantLock();

可以指定条件

Condition producer = lock..newCondition(); 指定生产者

Condition consumer = lock.newCondition(); 指定消费者

接下来当被锁定之后,producer.await();生产者线程等待。consumer.signalAll();唤醒所有消费者线程。

10、ThreadLocal:线程局部变量

ThreadLocal t = new ThreadLocal(); 创建一个线程局部变量。

这样就可以每个线程使用单独自己的变量。

ThreadLocal和synchronized的区别:

ThreadLocal:空间换时间  相当于想成将一个一般的变量copy一份到了线程内部,所有的操作都是在操作自己线程内部的变量。 效率更高。

synchronized:时间换空间   

  1. java并发容器:

Queue(队列):应用最多的。

对于map/set来说:

不需要加锁:hashmap,treemap,linkedhashmap

并发性不是很高的情况下需要枷锁:hashtable,collections.synchronizedXXX()(将 一个不包含锁的map/set转换成加锁的)。

并发性很高的情况下需要枷锁:concurrenthashmap,concurrentskiplistmap(是包 含了排序的)。

对于Queue(队列)来说:

不需要加锁:arraylist,linkedlist

并发要求不高的情况下需要加锁:vector,collections.synchronizedXXX()。

高并发的情况下可以使用两种锁:

concurrentLinkedQueue   内部加锁的

BlockingQueue   阻塞式队列:阻塞是指程序一直停在某行代码一直不动了。

LinkedBlockingQueue  无界队列,可以一直往队列中添加元素。

ArrayBlockingQueue 有界队列,默认可以添加10个元素。

DelayQueue:执行定时任务的。

TransferQueue:主要用于实时消息传输。生产者线程直接将生产出来的数据传输给消费者。所以是会阻塞的。

  1. Java 高并发线程池:

a、Executor(执行器)(接口)

B、ExecutorService

C、Runnablle和callable的区别:

Runnable  是没有返回值的。

Callable   是有返回值的。

D、Executors(工具类)

E、ThreadPool(newFixedThreadPool):

/**

 * 定义一个大小为5的线程池,在启动6个线程执行任务。但是在线程池中,总会有一个线程在等待在队列中。直到有空闲线程之后,继续执行。

 * @author 胡少东

 *

 */

public class MyThreadPool {

 

public static void main(String[] args) {

ExecutorService service = Executors.newFixedThreadPool(5);

for(int i=0;i<6;i++){

service.execute(()->{

try {

TimeUnit.MILLISECONDS.sleep(500);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName());

});

}

 

System.out.println(service);

 

service.shutdown();

System.out.println(service.isTerminated());

System.out.println(service.isShutdown());

System.out.println(service);

 

try {

TimeUnit.SECONDS.sleep(5);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println(service.isTerminated());

System.out.println(service.isShutdown());

System.out.println(service);

}

}

F、Future:他不属于线程池,他是未来获取任务!!!

/**

 * 使用future 未来获取

 * @author 胡少东

 *

 */

public class MyFuture {

 

public static void main(String[] args) {

// 使用futuretask+callable获取到线程返回值。

FutureTask<Integer> task = new FutureTask<Integer>(()->{

TimeUnit.MILLISECONDS.sleep(500);

return 1000;

});

 

new Thread(task).start();

try {

System.out.println(task.get()); //阻塞代码,直到获取到task的返回值才能继续执行下面的代码.

} catch (InterruptedException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

} catch (ExecutionException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

 

// 使用executorserivece的submit方法执行callable线程,获取返回值。

ExecutorService service = Executors.newFixedThreadPool(5);

Future<Integer> f = (Future<Integer>) service.submit(()->{

try {

TimeUnit.MILLISECONDS.sleep(500);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return 1;

});

 

System.out.println(f.isDone());

try {

System.out.println(f.get());

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (ExecutionException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println(f.isDone());

 

}

 

}

G、CachePool:在线程池中本来一个线程都没有,来一个任务就启动一个线程。每个线程空闲时间超过60s,就自动销毁。

H、SingleThreadPool:线程池里面永远只有一个线程。好处是可以保证线程的运行次序。

I、ScheduledPool:定时执行一个任务。

J、WorkStealingPool:工作窃取线程池。假设有一堆线程在执行很多任务,之前的线程都是需要分配线程去运行。而WorkStealingPool,会主动去寻找未完成的任务,将未完成的任务分配给空闲的线程。线程池的大小默认是cpu核数。但是要注意,如果主线程结束了话,那么WorkStealingPool会自己停止(守护线程)。

K、ForkJoinPool:和mapreduce概念比较像。

实现方法有两个:

集成RecursiveAction类   没有返回值

集成RecursiveTask类  可以有返回值

public class MyForkJoinPool {

 

static int[] nums = new int[1000000];

static final int MAX_NUM = 50000;

static Random r = new Random();

 

static {

for(int i=0;i<nums.length;i++){

nums[i] = r.nextInt(100);

}

System.out.println("使用累加方法获取到的和为:"+Arrays.stream(nums).sum());

}

 

// static class AddTask extends RecursiveAction{

//

// int start,end;

//

// public AddTask(int s,int e){

// start = s;

// end = e;

// }

//

// /**

//  * 实现recursiveAction类的compute方法,用于实现自己需要的算法。

//  */

// @Override

// protected void compute() {

// // TODO Auto-generated method stub

//

// if(end-start <= MAX_NUM){

// long sum = 0;

// for(int i=start;i<end;i++){

// sum += nums[i];

// }

// System.out.println("from:"+start+" to:"+end+" = "+sum);

// } else{

// int middle = start + (end-start)/2;

//

// AddTask subTask1 = new AddTask(start, middle);

// AddTask subTask2 = new AddTask(middle, end);

// subTask1.fork();

// subTask2.fork();

// }

// }

//

// }

 

static class AddTask extends RecursiveTask<Long>{

 

int start,end;

 

public AddTask(int s,int e){

start = s;

end = e;

}

 

 

@Override

protected Long compute() {

// TODO Auto-generated method stub

// 首先判断数组的大小,是不是我们所要分成的小份的队列的最大长度。

if(end - start <= MAX_NUM){

long sum = 0L;

for(int i=start;i<end;i++){

sum += nums[i];

}

return sum;

} else{

// 如果超过最大长度的话,就需要继续向下分割成更小的队列。

int middle = start + (end-start)/2;

 

AddTask subTask1 = new AddTask(start, middle);

AddTask subTask2 = new AddTask(middle, end);

// 分割成更小的队列。

subTask1.fork();

subTask2.fork();

// 返回合并之后的值。

return subTask1.join() + subTask2.join();

}

 

}

 

}

 

public static void main(String[] args) {

 

ForkJoinPool fjp = new ForkJoinPool();

AddTask task = new AddTask(0, nums.length);

// 调用执行器,将队列task放入ForkJoinPool线程池中执行运算。

fjp.execute(task);

 

// try {

// // 由于ForkJoinPool是一个守护线程(精灵线程),所以,当主线程结束之后,该线程池也会结束。

// // 所以需要阻塞主线程,来达到输出结果的作用。

// System.in.read();

// } catch (IOException e) {

// // TODO Auto-generated catch block

// e.printStackTrace();

// }

long result = task.join();

System.out.println("使用ForkjoinPolol累加之后的结果是:"+result);

}

}

总结:Future一般是和Callable、Executors、ThreadPool结合在一起使用的。

总共讲了六种线程池:fixed、cached、single、scheduled、worksteadling、forkjoin

底层是用的是ThreaddPoolExecutor,可以使用它来自定义线程池。

  1. java 线程池原理:

ThreadPoolExecutor:上边所学到的所有线程池,其实说白了底层都是

new  ThreadPoolExecutor();

                                                                              使用volatile保证变量的可见性

                                                      volatile只能保证可见性,不能保证原子性,和synchronized的区别

                                                                 生产者消费者模式为什么要用while而不用if

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值