java多线程与并发
进程与线程
进程:程序在处理机上的一次执行过程,一个具有独立功能的程序,每一个进程都有它自己的地址空间(一个程序的运行)
进程的状态:进程执行的间断性,决定了进程具有多种状态
- 就绪状态(Ready)
- 运行状态(Running)
- 阻塞状态(Blocked)
线程:线程是在进程基础上的进一步划分,一个进程启动后,里面的若干程序可以划分为若干个线程,线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
并行:两个任务同时运行(多核CPU)
并发:两个任务同时请求运行,而处理器一次只能接受一个任务,就会把两个任务安排轮流执行,由于CPU时间片运行时间短,感觉两个任务在同时运行
线程的基本使用
package concurrency;
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();//启动线程(实际上是在运行时由虚拟机才启动,只是准备就绪的意思)
MyRunnable mr = new MyRunnable();
Thread t2 = new Thread(mr);
mt.start();//启动线程(实际上是在运行时由虚拟机才启动,只是准备就绪的意思)
t2.start();
}
}
/*
* 实现线程的第一种方式:继承thread类
*/
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);//获取当前线程的名字
}
}
}
/*
* 实现线程的第二种方式:实现Runnable接口
* @ 推荐这种方式
*/
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
线程的休眠
静态方法sleep
以指定毫秒数暂停,释放CPU的时间片
异常:
- IllegalArgumentException:如果参数millis为负数
- InterruptedException:如果任何线程中断当前线程,当抛出此异常时,当前线程的中断状态将被清除
join与中断线程
join方法:等待线程死亡(即执行完),与join(0)相同
interrupt:标记中断这个线程
静态interrupted:测试当前线程是否中断,同时清楚中断标记
中断线程的方式:
- 使用interrupt方法来中断线程,设置一个中断状态(标记),但是很多方法会清除该标记,需要重新标记
- 使用自定义标记的方式(一般使用这种方式)
package concurrency;
public class ThreadDemo {
public static void main(String[] args){
MyRunable mr = new MyRunable();
Thread t = new Thread(mr);
MyRunnable2 mr2 = new MyRunnable2();
Thread t2 = new Thread(mr2);
t.start();
t2.start();
for (int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==20){
// try {
// t.join();//让t线程先执行指定时间或执行完毕
// } catch (InterruptedException e) {
// //抛出的该异常会清楚中断标记
// e.printStackTrace();
// }
// t.interrupt();//中断线程,只是做了一个中断标记
mr2.flag = false;
}
}
}
}
class MyRunable implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
if(Thread.interrupted()){
//测试中断状态,使方法会把中断状态清除
break;
}
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
//该异常会清除中断标记
e.printStackTrace();
Thread.currentThread().interrupt();//重新标记异常
}
}
}
}
/**
* 自定义标记
*/
class MyRunnable2 implements Runnable{
public boolean flag = true;//标记
@Override
public void run() {
int i=0;
while(flag){
System.out.println(Thread.currentThread().getName()+"=="+(i++));
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
守护线程
线程分为守护线程和用户线程public final void setDaemon(boolean on)
: 将此线程标记为daemon线程或用户线程,当运行的唯一线程是守护线程时,虚拟机将退出public final boolean isDaemon()
: 测试该线程是否是守护线程public static void yield()
: 暂停当前执行的线程对象,让出本次CPU执行的时间片,并执行其他线程long getId()
:返回该线程的标识符String getName()
:返回该线程的名称void setName(String name)
:改变线程名称boolean isAlive()
:是否处于活跃状态void setPriority(int newProprity)
:改变线程的优先级(抢到时间片概率的高低)
- MAX_PRIORITY:最高优先级
- MIN_PRIORITY:最低优先级
- NORM_PRIORITY:默认
package concurrency;
public class ThreadDemo2 {
public static void main(String[] args){
MyRunnable3 mr3 = new MyRunnable3();
Thread t3 = new Thread(mr3);
t3.setDaemon(true);//设置为守护线程
System.out.println(t3.isAlive());//线程是否是活跃状态(false)
t3.start();
System.out.println(t3.isAlive());//true
for (int i = 0; i < 50; i++) {
System.out.println("main--"+i);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("--"+i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程同步
- 多线程共享数据
多个线程同时处理同一个资源,多线程共享数据时,会发生线程不安全的情况(一个数据被使用多遍的情况),这种情况下必须使用同步 - 同步
多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行
实现同步的三种方法:
(1) 同步代码块
package concurrency;
public class ThreadDemo3 {
public static void main(String[] args){
MyRunnable4 mr4 = new MyRunnable4();
Thread t1 = new Thread(mr4);
Thread t2 = new Thread(mr4);
//两个窗口售票
t1.start();
t2.start();
}
}
class MyRunnable4 implements Runnable{
private int ticket = 10;//售票
//private Object obj = new Object();//同步对象,检查是否是同一对象,同一对象才能同步(确定是否是同一把锁)
@Override
public void run() {
for (int i = 0; i < 300; i++) {
if(ticket > 0){
//第一种方法,同步代码块(同步锁)
//执行到这里时,会对该同步对象加上锁,另一个线程无法进入,只能等该线程执行完释放锁
synchronized (this) {
try {
Thread.sleep(1000);//模拟售票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票已剩余" + ticket-- + "张");
}
}
}
}
}
(2) 同步方法
同步的对象是当前对象(this)
private synchronized void method(){
try {
Thread.sleep(1000);//模拟售票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票已剩余" + ticket-- + "张");
}
(3) Lock
ReentrantLock lock = new ReentrantLock();//互斥锁
private void method2() {
lock.lock();//锁
//使用try和final,防止死锁
try {
try {
Thread.sleep(1000);//模拟售票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票已剩余" + ticket-- + "张");
}finally {
lock.unlock();//释放锁
}
}
- 原则
- 同步会牺牲性能,同步代码块要尽可能简短
- 不要阻塞
- 在持有锁的时候,不要对其他对象调用方法
生产者与消费者案例
package concurrency;
public class ProducterCustomerDemo {
public static void main(String[] args) {
Food food = new Food();
Producter producter = new Producter(food);
Customer customer = new Customer(food);
Thread t1 = new Thread(producter);
Thread t2 = new Thread(customer);
t1.start();
t2.start();
}
}
/**
* 生产者
*/
class Producter implements Runnable{
private Food food;
public Producter(Food food){
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2 == 0){
food.set("板栗","一斤25");
}else{
food.set("樱桃","一斤70");
}
}
}
}
/**
* 消费者
*/
class Customer implements Runnable{
private Food food;
public Customer(Food food){
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
food.get();
}
}
}
/**
* 食物
*/
class Food{
private String name;
private String desc;
private boolean flag = true;//true表示可以生产,false表示可以消费
/**
* 生产产品
* @param name
* @param desc
*/
public synchronized void set(String name,String desc){
//不能生产
if(!flag){
try {
this.wait();//Object方法,线程进入等待状态,会释放监视器的所有权(对象锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(desc);
flag = false;
this.notify();//唤醒其中一个等待线程
}
/**
* 消费产品
*/
public synchronized void get(){
//不能消费
if(flag){
try {
this.wait();//Object方法,线程进入等待状态,会释放监视器的所有权(对象锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"->"+this.getDesc());
flag = true;
this.notify();//唤醒其中的一个等待线程
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Food() {
}
public Food(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
线程池
线程池是预先创建线程的一种技术,线程池在任务还没有到来之前,创建一定数量的线程放入空闲队列中,然后对这些资源进行复用,减少频繁的创建和销毁对象
线程池的顶级接口是Executor
,线程池接口是ExecutorService
Executor接口
:执行已经提交的Runnable任务的对象ExecutorService接口
:提供管理终止的方法,以及跟踪一个或多个异步任务执行状况而生成的Future方法Executors类
:定义了Executor,ExecutorService等的工厂和实用方法
(1) newSingleThreadExecutor
单线程线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行
(2)newFixedThreadPool
创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程池到达最大大小,一旦达到最大值就保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
(3)newCachedThreadPool
创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务
(4)newScheduledThreadPool
创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求
package concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadDemo4 {
public static void main(String[] args) {
//创建一个单线程的线程池
ExecutorService es1 = Executors.newSingleThreadExecutor();
ExecutorService es2 = Executors.newFixedThreadPool(2);//指定线程池数量,等于1相当于单线程
ExecutorService es3 = Executors.newCachedThreadPool();
ExecutorService es4 = Executors.newScheduledThreadPool(3);//大小无限,但仍然需要指定大小
es1.execute(new MyRunnable5());//执行线程
es1.execute(new MyRunnable5());
es2.execute(new MyRunnable5());
es2.execute(new MyRunnable5());
es3.execute(new MyRunnable5());
es3.execute(new MyRunnable5());
((ScheduledExecutorService) es4).schedule(new MyRunnable5(),3000, TimeUnit.MILLISECONDS);
es1.shutdown();//关闭线程
es2.shutdown();
es3.shutdown();
es4.shutdown();
}
}
class MyRunnable5 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run--"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}