java线程笔记(锁线程通讯线程池)
Posted 皓洲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程笔记(锁线程通讯线程池)相关的知识,希望对你有一定的参考价值。
java线程
文章目录
创建线程方式一:继承Thread类
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,由CPU调度执行
public class Test extends Thread{
@Override
public void run(){
for(int i=0;i<20;i++){
System.out.println("我在看代码===="+i);
}
}
public static void main(String[]args){
//main线程,主线程
//创建一个线程对象
Test test=new Test();
//调用start()方法开启线程
test.start();
for(int i=0;i<200;i++){
System.out.println("I am eating lunch===="+i);
}
}
}
创建线程方式2:实现runnable接口
//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法。
public class Test implements Runnable{
@Override
public void run(){
for(int i=0;i<20;i++){
System.out.println("我在看代码===="+i);
}
}
public static void main(String[]args){
//main线程,主线程
//创建一个线程对象
Test test=new Test();
//船家女线程对象,通过线程对象来开启我们的线程,代理
new Thread(test).start();
for(int i=0;i<200;i++){
System.out.println("I am eating lunch===="+i);
}
}
}
对比
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.strat()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
多个线程操作同一个对象:买火车票
import java.util.*;
//多个线程操作同一个对象:买火车票
//发现问题:多线程操作同一个资源的情况下,线程不安全,数据紊乱
public class Test implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run(){
while(true){
if(ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"票");
}
}
public static void main(String[]args){
Test ticket = new Test();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛").start();
}
}
输出结果:
出现了两个第7票和第0票第-1票。说明存在并发问题。
同步锁synchronized
-
synchronized方法控制”对象“的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺点:若将一个大的方法声明为synchronized将会影响效率。
import java.util.*;
//多个线程操作同一个对象:买火车票
//发现问题:多线程操作同一个资源的情况下,线程不安全,数据紊乱
public class Test implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run(){
while(true){
if(ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
//加锁解决冲突
public synchronized void buy(){
if(ticketNums>0)
System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"票");
}
public static void main(String[]args){
Test ticket = new Test();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛").start();
}
}
结果:
Lock锁
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
//多个线程操作同一个对象:买火车票
//发现问题:多线程操作同一个资源的情况下,线程不安全,数据紊乱
public class Test implements Runnable{
//票数
private int ticketNums = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run(){
while(true){
try {
lock.lock();
if(ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿走了第"+ticketNums--+"张票");
}finally {
lock.unlock();
}
}
}
public static void main(String[]args){
Test ticket = new Test();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛").start();
}
}
结果:
发现票全是黄牛拿走的,因为黄牛抢到了这把锁,一直买票,最后才释放。
synchronized与Lock的对比
- Lock是显示锁(手动开启或关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用域自动释放
- Lock只有代码块锁,synchronized由代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。斌且具有很好的拓展性(提供更多子类)
- 优先使用顺序:
- Lock>同步代码块(已经进入了方法体,分配了相应的资源)> 同步方法(在方法体之外)
线程通信
- 应用场景:生产者和消费者问题
- 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,知道仓库中的产品被消费者取走为止。
- 如果仓库中有产品,则消费者可以将产品取走消费,否则停止消费并等待,知道仓库中再次放入产品为止。
- 这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间互相依赖,互为条件。
- 对于生产者,没有生产商品之前,要通知消费者等待,而产生了商品之后,又要马上通知消费者消费
- 对于消费者,再消费之后,要通知生产者 已经结束消费,需要产生新的产品以供消费。
- 再生产者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通讯)
并发协作模型“生产者/消费者模式”—>管程法
- 生产者:负责生产数据得模块(可能是方法,对象,进程、线程)
- 消费者:负责处理数据得模块(可能是方法,对象,进程、线程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
生产者将生产好的数据放入缓冲区,消费者冲缓冲区拿出数据。
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
//生产者消费者问题
public class Test{
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了第"+i+"只鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了---->第"+container.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
Chicken(int id){
this.id=id;
}
}
//缓冲区
class SynContainer{
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int cnt=0;
//生产者放入产品
public synchronized void push(Chicken chicken){
while(cnt>=10){
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有满,则生产产品
chickens[cnt++]=chicken;
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断是否能消费
while(cnt<=0){
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
cnt--;
Chicken chicken = chickens[cnt];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
结果:
使用线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的交通工具。
- 好处:
- 提高相应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximuPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- TimeUnit:时间单位
- BlockingQueue< Runable > workqueue :等待队列
- ThreadFactory:线程工厂
- RejectedExecutionHandler:拒绝策略
线程池
/**
- corePoolSize:核心池的大小
- maximuPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- TimeUnit:时间单位
- BlockingQueue< Runable > workqueue :等待队列
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
/**
- newCachedThreadPool()
- corePoolSize:核心线程数为0
- maximuPoolSize:最大线程数为int范围内最大的数2^31-1
- keepAliveTime:存活时间 60
- TimeUnit:单位 秒
- BlockingQueue< Runable > workqueue :等待队列为SynchronousQueue 大小为1
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/**
- newCachedThreadPool()
- corePoolSize:核心线程数为n
- maximuPoolSize:最大线程数为n
- keepAliveTime:存活时间 0
- TimeUnit:单位 min
- BlockingQueue< Runable > workqueue :LinkedBlockingQueue 大小为int范围内最大的数2^31-1
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/**
- newCachedThreadPool()
- corePoolSize:核心线程数为1
- maximuPoolSize:最大线程数为1
- keepAliveTime:存活时间 0
- TimeUnit:单位 min
- BlockingQueue< Runable > workqueue :LinkedBlockingQueue 大小为int范围内最大的数2^31-1
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
自定义线程池
/**
- newCachedThreadPool()
- corePoolSize:核心线程数为10
- maximuPoolSize:最大线程数为20
- keepAliveTime:存活时间 0
- TimeUnit:单位 sec
- BlockingQueue< Runable > workqueue :ArrayBlockingQueue 大小为10
*/
//自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
20,
0L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10));
提交优先级
- 核心线程
- 等待队列
- 非核心线程
以上是关于java线程笔记(锁线程通讯线程池)的主要内容,如果未能解决你的问题,请参考以下文章
Python之路第一课Day9--随堂笔记之二(进程线程协程篇)
Python学习笔记——进阶篇第九周———线程进程协程篇(队列Queue和生产者消费者模型)
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段