基础知识:Java多线程编程
关于线程和进程
线程是进程的一个执行单元,它和进程一样拥有独立的执行控制,由操作系统负责调度,它们俩的区别可以用一句话概括之,那就是进程是程序的一次执行,而线程可以理解为进程执行的一段程序片段,也就是说它们是一种包含关系,线程不能独立运行,必须依存在进程之中
另外从资源分配的角度上看,进程是系统进行资源分配和调度的一个独立单位,而线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
进程间通信和线程间通信的区别
在通信方面也是,进程间通信比较困难,因为进程之间相互独立,而线程之间共享一块内存区域,通信方便
进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET.
而进程间通信主要包括共享内存,比如经典的生产者和消费者通过共享内存的方式进行通信;它们共享的内存是:SyncStack对象;生产者通过SyncStack的同步方法pop向其中添加对象;消费者通过SyncStack的同步方法pop方法在SyncStack对象中获取对象;这是对象间共享内存(或共享数据区域)的方式进行的通信。
零散补充
另外多说一句一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
让一个类成为线程类
让一个类成为线程类有两种方式,一种是实现java.lang.Runnable接口,另外一种是基础java.lang.Thread类
Runnable接口和Thread类的区别
它们的区别在于三点,一是Thread类中还提供了额外的方法(其实Thread也是实现了Runnable接口的),二是从继承的角度看,接口比类更灵活,因为只能继承一个类而可以继承多个接口,三是如果类实现Runnable接口,那么当调用这个线程的对象开辟多个线程时,可以让这些线程调用同一个变量,如果类继承Thread的话,那么需要用到内部类来实现类似的功能,原理就是利用内部类可以访问任意外部变量这一特性
如何启动一个线程
通过以上两种方法实现一个线程类之后,线程的实例并没有被创建,所以也没有运行起来,要启动它,就要调用Thread的start方法,注意这里并不是run方法(既不是继承Thread类重写的run方法,也不是实现Runnable接口的run方法,run方法中包含的是线程要干的事儿的主体,跟线程的启动没有关系)
继承Thread的线程类的启动
Thread threadTest=new ThreadTest();
threadTest.start();
实现Runnable的线程类的启动
Thread thread=new Thread(new RunnableTest());
thread.start();
用synchronized保证线程间同步
如果不用synchronized同步
class MyThread extends Thread {
public static int index;
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + index++);
}
}
}
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
输出结果
Thread-1:13
Thread-2:16
Thread-0:15
Thread-2:18
Thread-1:17
Thread-2:20
Thread-0:19
用synchronized同步
synchronized同步原理
用synchronized同步的原理是每个对象都有一个线程锁,synchronized可以用任一对象的线程锁来锁住一段代码,任何想进入该代码段的线程必须在解锁以后才能执行,只有占用该锁资源的线程执行完毕,该锁资源才被释放,其他线程才能进入
class MyThread extends Thread {
public static int index;
public static Object object=new Object();
public void run() {
synchronized (object) {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + index++);
}
}
}
}
另外除了把synchronized加在对象上,也可以加在方法上,这叫做同步方法,其实这时候的锁是加在this所引用的对象上的
生产者消费者模型
首先主类Store,相当于线程要争夺的资源,定义一个最大值,以及一个现有值,构造函数中将最大值作为参数传进去,并且将现有值赋值为0
接下来就是增加和删除两个方法,分别用synchronized标记
在增加方法中,如果满了,则调用this的wait方法,不然的话就现有值增加,然后通知其他线程
在删除方法中,如果空了,则调用this的wait方法,不然的话就现有值减少,然后通知其他线程
public class Store {
private final int MAX_SIZE;
private int curCount;
public Store(int n){
MAX_SIZE=n;
curCount=0;
}
public synchronized void add(){
while (curCount>MAX_SIZE) {
System.out.println("仓库满了");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
curCount++;
System.out.println(Thread.currentThread().toString()+"put"+curCount);
this.notifyAll();
}
public synchronized void remove() {
while(curCount<0){
System.out.println("仓库是空的");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().toString()+"get"+curCount);
curCount--;
this.notify();
}
}
然后就是要创建生产者类和消费者类,它们都要是线程类,也就是继承Thread
两个线程类要和公共资源联系起来,就需要在两个类中分别定义一个公共资源的成员变量,然后在构造函数中传入进来赋值
生产者线程类的run方法中为永真循环,不停的添加,添加一个,让线程睡1秒
消费者线程类的run方法中为永真循环,不停的删除,删除一个,让线程睡1.5秒
class Producer extends Thread{
private Store store;
Producer(Store store){
this.store=store;
}
public void run() {
while (true) {
store.add();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer extends Thread{
private Store store;
Consumer(Store store)
{
this.store=store;
}
public void run() {
while (true) {
store.remove();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然后再在main函数中建立这些线程和启动线程
Store store=new Store(5);
Thread producer1=new Producer(store);
Thread producer2=new Producer(store);
Thread consumer1=new Consumer(store);
Thread consumer2=new Consumer(store);
producer1.setName("pro1");
producer2.setName("pro2");
consumer1.setName("con1");
consumer2.setName("con2");
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
使用Java线程池
线程池属于对象池,对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。
并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。
线程池所对应的类是java.util.concurrent
线程池的构造方法
ThreadPoolExecutor
(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
)
corePoolSize: 线程池维护线程的最少数量 (core : 核心)
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
handler: 线程池对拒绝任务的处理策略
一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。
利用Execute执行器来管理线程对象
不必显式的管理线程的生命周期,可以使用Executors的静态方法newFixedThreadPool 或者newCachedThreadPool来创建ExecutorService对象;
同样也是调用ExecutorService对象的execute方法来添加线程到线程池
· ASP.NET Core - 日志记录系统(二)
· .NET 依赖注入中的 Captive Dependency
· .NET Core 对象分配(Alloc)底层原理浅谈
· 聊一聊 C#异步 任务延续的三种底层玩法
· 敏捷开发:如何高效开每日站会
· 终于决定:把自己家的能源管理系统开源了!
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(一):从.NET IoT入
· C#实现 Winform 程序在系统托盘显示图标 & 开机自启动
· ASP.NET Core - 日志记录系统(二)
· 实现windows下简单的自动化窗口管理