多线程是java基础里一块非常重要的知识点
1.之前我们的程序都是从上至下,一行一行执行;
2.很明显的特征就是:下面的代码总要等待上面的代码执行完毕,才能获得执行;
这种程序只有一条执行路径,被称为:单线程程序;
一.进程:
1.什么是进程:它是"操作系统"中的概念;对于操作系统来说,每个单独运行的"程序"就是一个"进程";
2.什么是多进程:是指操作系统可以同时维护多个应用程序的同时运行;统一分配资源;
3.多进程的意义:
1).充分的利用系统资源;
2).可以提高用户的体验度;使用户有非常好的使用体验;
二.线程:
1.什么是线程:线程是指,由一个"进程"中启动的一个可以独立运行的代码块;它是某个进程的一部分;
线程可以跟"主进程"一同抢夺系统资源;一个主进程可以开出任意多的线程;
2.什么是多线程:是指一个程序可以开出多个"线程"同时执行;
3.多线程的意义:
1).充分的利用系统资源;
2).对于一个软件来说,就好像同时在有多个功能在一起运行;
三.多线程程序和单线程程序:
1.单线程程序:只有一条执行路径;后面的代码必须等待前面的代码执行完毕才能获得执行;
2.多线程程序:可以有多条执行路径,就好像多个功能在同时运行;
四.并行和并发:
1.并行:是指两个线程在"某个时间段内",同时在运行;
2.并发:是指多个线程在"某个时间点上",同时访问同一资源;
实现多线程的方式1:
实现线程的方式一:Java中的线程,使用:Thread类表示;
1.自定义线程类,继承自Thread;
2.重写run();
3.启动线程:
1).实例化自定义线程类对象;
2).调用线程对象的start()方法启动线程;
注意:
1.对于同一个Thread(包括子类)对象,不能多次调用start()方法;
2.对于同一个Thread(包括子类)类,可以产生多个对象,每个对象都可以单独start(),
成为一个独立的线程;
我们知道,对于同一线程类,可以实例化多个对象,同时start()作为线程运行;
由于每个线程内的代码都是一样的
1.实际上,每个线程,都有一个"线程名称";默认名称:Thread-(索引)
getName():获取线程名;
setName(String n):设置线程名称;
2.我们可以在任何的线程中使用:Thread.currentThread()获取当前正在运行的线程对象;并调用getName()即可以获取此线程对象的线程名称;
案例:线程类:
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 100 ; i++){
System.out.println(this.getName() + " i = " + i);
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("张学友");
t2.setName("章子怡");
t3.setName("周海媚");
t1.start();
t2.start();
t3.start();
for(int k = 0;k < 100 ; k++){
System.out.println( Thread.currentThread().getName() + "k = " + k);
}
}
}
线程的优先级:
线程的优先级:
1.Java中线程优先级的范围:1--10(从低到高);
2.设置优先级:
getPriority():获取优先级;
setPriority():设置优先级;(范围一定从1--10)
3.注意:我们不要依赖于"线程优先级"的技术,期望某个线程先执行完毕;因为它不保证线程优先级高的
就一定先执行完毕,这个要由"操作系统"来统一调度;"高优先级"的线程只代表:具有更多的机会
被操作系统执行而已,并不代表一定会先执行完毕;另外,如果线程中所做的事情比较简单,线程"优先级"
的效果会不明显;
线程类的一些方法:
线程休眠 public static void sleep(long millis):
线程加入
public final void join():调用此方法的线程,将会保证先执行完毕,其它线程才可以被执行;
public static void yield():使调用的线程退回到"就绪"状态,等待操作系统再次分配,很有可能被操作系统再次分配到执行权;
后台线程
public final void setDaemon(boolean on):如果on为true,则表示为:守护线程
1.我们之前写的线程都是"非守护线程":当主线程执行完毕时,程序不会立即结束,会等待所有开出的线程执行完毕,主进程才结束;
2.守护线程:当主线程结束时,开出的"守护线程"也会随着终止(但是不会立即终止,会有个小缓冲)
中断线程
public final void stop():不建议使用
public void interrupt():前提条件:线程的内部,一定要处于以下三种阻塞状态时:
1.Object-->wait()
2.Thread-->sleep()
3.Thread-->yield()
当在外部调用线程的interrupt()方法时,将促使上述方法抛出异常;
实现线程的方式2:
* 实现线程的方式二:
*
* 1.自定义类,实现:Runnable接口;
* 2.重写run()方法;
* 3.启动线程:
* 1).实例化自定义类对象;
* 2).实例化一个Thread对象,并将自定义对象传递给Thread的构造方法;
* 3).调用Thread对象的start()方法启动线程;
*/
public class Demo {
public static void main(String[] args) {
MyRunnable myRun = new MyRunnable();
Thread t = new Thread(myRun);
t.start();//启动线程
for(int j = 0;j < 100 ;j++){
System.out.println("j = " + j);
}
}
}
线程类:
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100 ;i++){
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
}
}
两种方式的对比:
方式一:由于方式一需要"继承",而Java中只能单继承。子类将不能再继承其它类了,这就对子类形成了一种限制;
方式二:使用"接口"的方式,重写run()方法。Java中的子类可以同时实现多个接口;
经典案例,Thread实现卖票程序:
测试类:
public class Demo {
public static void main(String[] args) {
//1.需要一个票池对象
TicketPool tp = new TicketPool();
//2.需要三个线程对象,模拟三个窗口售票
MyThread t1 = new MyThread(tp);
MyThread t2 = new MyThread(tp);
MyThread t3 = new MyThread(tp);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
抢票线程:
public class MyThread extends Thread{
private TicketPool tp = null;
private int count = 0;
public MyThread(TicketPool p){
this.tp = p;
}
@Override
public void run() {
while(true){
int n = this.tp.getTicket();
if(n == 0){//没票了
break;
}else{
count++;
System.out.println(this.getName() + " 抢到票:" + n);
}
}
System.out.println(this.getName() + " 停止抢票了! 共抢到:" + count + "张票");
}
}
票池类:
public class TicketPool {
private int tickets = 100;
//获取一张票
public int getTicket(){//窗口1,窗口2
if(this.tickets > 0 ){//窗口1,窗口2
return tickets--;
}else{
return 0;
}
}
}
由于线程并发访问一个票池对象,会造成数据失真。于是,需要对共享数据加锁,才能保证结果的数字准确:对共享数据加锁的方式是使用synchronized 关键字,上锁的代码块也叫同步代码块
public class TicketPool {
private int tickets = 100;
//获取一张票
public int getTicket(){//窗口2
//同步代码块
synchronized (this) {//窗口1(锁)
if(this.tickets > 0 ){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return tickets--;
}else{
return 0;
}
}
}
}
同步的好处和弊端:
同步的语法:
1. 同步代码块
synchronized(被锁的对象){
//同步的代码
}
被锁的对象:表示如果当前线程访问"被锁对象"的synchronized的代码块时,其它线程
不能访问此代码块,另外,也不能访问"被锁对象"中的其它synchronized的代码块;
2.同步的好处:解决了多线程并发访问的问题,使多线程并发访问的共享数据能够保持一致性;
3.同步的弊端:
1).使当前的对象的工作效率降低;因为要处理线程同步的问题;
经典案例:银行账户存取款问题
/*
* 同步代码的形式:
*
* 1.同步代码块:
* synchronized(被锁的对象){
* //同步的代码
* }
* 2.同步的方法:(比较常见的方式)(默认锁的就是当前对象)
* public synchronized void setMoney(int m){
* //同步的代码
* }
* 3.静态方法能否包含同步代码块?可以,一般"锁的对象"是某个类的Class对象;
* 4.静态方法是否可以被声明为同步的?可以的。
*/
public class Demo {
public static void main(String[] args) {
Account acc = new Account();
SetThread setThread = new SetThread(acc);
GetThread getThread = new GetThread(acc);
setThread.start();
getThread.start();
System.out.println("主线程等待1秒......");//确保存钱、取钱两个线程执行完毕
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程醒来......");
System.out.println("最终余额:" + acc.getBalance());
}
}
public class Account {
private int balance = 10000;
//同步方法
public synchronized void setMoney(int m){
this.balance += m;
}
public synchronized void getMoney(int m){
this.balance -= m;
}
public int getBalance(){
return this.balance;
}
//静态方法能否包含同步代码块?可以,一般"锁的对象"是某个类的Class对象;
public static void show(){
synchronized (Account.class) {
System.out.println("静态方法中的同步代码块");
}
}
//静态方法是否可以被声明为同步的?可以的。
public synchronized static void show2(){
}
}
public class GetThread extends Thread{
private Account acc;
public GetThread(Account acc){
this.acc = acc;
}
public void run() {
for(int i = 0;i < 1000 ;i++){
this.acc.getMoney(1000);
}
}
}
public class SetThread extends Thread {
private Account acc;
public SetThread(Account acc){
this.acc = acc;
}
@Override
public void run() {
for(int i = 0;i < 1000 ; i++){
acc.setMoney(1000);
}
}
}
lock锁:
1.在JDK5之前我们使用synchronized定义同步代码块,同步方法;这是一种锁的方式;
2.在JDK5之后,出现了一种新的锁的方式,需要使用的:Lock(接口),Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
3.使用方式:JDK的帮助文件中:
Lock l = ...;
l.lock();//加锁
try {
// access the resource protected by this lock
} finally {
l.unlock();//释放锁
线程死锁:
单生产单消费案例:
1.共享资源:包子铺
2.生产线程:SetThread-->添加包子
3.消费线程:GetThread-->买包子
由于两个线程无序访问,所以,很可能"消费者"会得到一个null值,这不是我们想看到的。
我们希望做到的:不论是否有包子,消费者都能得到一个包子;
1.在包子铺:获取包子的方法:getBaozi(),判断,如果没有包子了,让消费者"等待(Object-->wait())"
设置包子的方法:setBaozi(),没设置一个包子,都会"唤醒(Object-->notifyAll()/notify())"所有等待的线程;
2.注意:
1).此例只使用与"单生产"与"单消费",不适用"多生产"和"多消费";
2).wait()方法和notify()/notifyAll()方法的调用,一定要在"同步方法/代码块"内,否则会抛异常;
public class Demo {
public static void main(String[] args) {
BaoZiPu bzp = new BaoZiPu();
SetThread set = new SetThread(bzp);
GetThread get = new GetThread(bzp);
set.start();
get.start();
}
}
import java.util.ArrayList;
public class BaoZiPu {
private ArrayList<String> bzList = new ArrayList<>();
//添加包子
public synchronized void setBaozi(String s){
this.bzList.add(s);
//唤醒所有等待的线程
notifyAll();//或者notify()
}
//获取包子
public synchronized String getBaozi(){
if(this.bzList.size() <= 0){//锅里面没有包子了
//让当前访问的线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取第一个包子
String s = bzList.get(0);
//移除第一个包子
bzList.remove(0);
//返回
return s;
}
}
public class GetThread extends Thread{
private BaoZiPu bzp;
public GetThread(BaoZiPu b){
this.bzp = b;
}
@Override
public void run() {
while(true){
String s = bzp.getBaozi();
System.out.println("我得到了一个:" + s);
}
}
}
public class SetThread extends Thread{
private BaoZiPu bzp ;
public SetThread(BaoZiPu bzp){
this.bzp = bzp;
}
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
bzp.setBaozi("包子");
}
}
}
匿名内部类的方式实现多线程:
1.new Thread(){匿名的Thread子类};
2.new Thread(匿名的Runnable的子类){};
3.new Thread(匿名的Runnable的子类){匿名的Thread子类};
线程组:
1.线程组就是将一些线程统一分组管理;
2.分组后,可以对组内的所有线程做统一的操作:比如:停止线程;
3.线程组:使用:ThreadGroup
4.在Thread类中:
ThreadGroup getThreadGroup():获取线程所在的线程组;
5.一个线程,在默认情况下,都属于"main"线程组;
6.我们可以自行设定线程组:
1).实例化一个ThreadGroup对象;
2).使用Thread的构造方法,指定线程组和相应的线程:
Thread(ThreadGroup group, Runnable target) 分配新的 Thread 对象。
7.统一停止线程组内的所有线程:
ThreadGroup-->interrupt():
线程池:
1.我们知道,对于一个线程对象,不能反复的调用start()再次运行;
要想再次运行,就要再次创建线程对象,如果创建线程对象比较耗时;
2.所以,我们期望将一些线程对象缓存起来,可以被多次的反复调用;
3.JDK5提供了一种线程池:
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool():创建一个可根据需要创建新线程的线程池
public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池
public static ExecutorService newSingleThreadExecutor():创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
返回值:ExecutorService--线程池
执行线程,并缓存线程对象:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
public class Demo {
public static void main(String[] args) {
/*MyThread t1 = new MyThread();//5秒
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("想再次运行线程......");
t1 = new MyThread();//5秒
t1.start();*/
MyThread t1 = new MyThread();
//获取一个线程池
ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(t1);
System.out.println("主线程休息2秒钟......");
try {
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程醒来,再次调用线程......");
service.submit(t1);
}
}
多线程实现方式之3:
回顾之前实现线程的方式:
1).继承Thread;
2).实现Runnable接口
今天: 3).
1>.实现:Callable接口;
2>.重写call()方法
3>.启动线程:
1.获取线程池;
2.调用线程池的submit()方法启动并缓存线程;
public class Demo {
public static void main(String[] args) {
//1.获取线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//2.调用线程池的submit()方法执行线程
MyCallable myCall = new MyCallable();
service.submit(myCall);
for(int k = 0 ; k < 100 ; k++){
System.out.println("k = " + k);
}
service.shutdown();
}
}
import java.util.concurrent.Callable;
public class MyCallable implements Callable{
@Override
public Object call() throws Exception {
for(int i = 0;i < 100 ; i++){
System.out.println("i = " + i);
}
return null;
}
}
定时器:
import java.util.Timer;
import java.util.TimerTask;
/*
* Java中的定时器:
*
* 1.可以在指定的时间,开始做某件事情;
* 2.可以从指定的时间开始,并且间隔多长时间,会重复的做某件事情;
*
* 实现定时器:
* 1.java.util.TimerTask(抽象类):定义执行的任务
* 1).自定义类,继承自TimerTask;
* 2).重写run()方法;(将要执行的任务定义在这里)
* 2.java.util.Timer(类):计时,并启动任务;
* 构造方法:
* Timer();
* 成员方法:
* public void schedule(TimerTask task, long delay):在指定的delay延迟之后,启动任务task;
* public void schedule(TimerTask task,long delay,long period):在指定的延迟之后,启动任务,并间隔指定时间重复的执行任务;
*/
class MyTimerTask extends TimerTask{
private Timer timer;
public MyTimerTask(Timer t){
this.timer = t;
}
@Override
public void run() {
System.out.println("duang......");
// this.timer.cancel();//如果重复执行,这里就不能取消了;
}
}
public class Demo {
public static void main(String[] args) {
Timer timer = new Timer();
System.out.println("3秒后,开始执行,之后每隔1秒重复执行一次......");
// timer.schedule(new MyTimerTask(timer), 1000 * 3);
timer.schedule(new MyTimerTask(timer), 1000 * 3 , 1000 );
}
}
Sleep 和wait的区别:1.sleep():
1).Thread类中定义的;
2).都要指定时间;当休眠时间到时,会自动醒来;
3).在同步方法中,不会释放锁;
2.wait():
1).Object类中定义的;
2).可以指定时间,也可以不指定时间;如果指定时间,当时间到时,会自动醒来;
如果不指定时间,使用notify()或notifyAll()唤醒;
3).在同步方法中,会释放锁;
设计模式之简单工厂模式:
简单工厂模式:
1.目的:让前端与后端的类脱离,不需要直接实例化后端类的对象;
2.简单工厂模式,需要单独建立一个类,叫:工厂类,这个工厂类中会提供一些方法来获取具体的对象;
简单工厂模式之核心代码:
public class Factory {
//*********简单工厂模式:方式一***************//
public static Cat getCat(){
return new Cat();
}
public static Dog getDog(){
return new Dog();
}
//*********简单工厂模式:方式二***************//
public static Object getAnimal(String type){
if(type.equals("猫")){
return new Cat();
}else if(type.equals("狗")){
return new Dog();
}
return null;
}
}
工厂方法简述:
工厂方法模式的概述和使用:
1.之前的"简单工厂模式"的弊端:如果增加新产品,就要修改工厂类的源码;
2.使用"工厂方法模式",可以在增加新产品的同时,不需要修改任何代码:
1).首先要先定义两个接口:
1>.产品:IAnimal
2>.工厂:IFactory
工厂方法模式:
优点:增加新产品时,不需要更改源码;
弊端:创建的类太多;
单例模式:
1.单例:例:实例(对象),单:一个;单例:一个对象;
2.在整个应用程序运行期间,有些类只需要一个对象即可,这时可以将这个类设计为:单例模式;
例如:整个应用程序只需要一个Cat对象,表示:波斯猫。
此时需要将Cat类设计为单例模式;
步骤:
1.Cat类的对象,不能任意的实例化;将Cat类的构造方法私有化;
2.提供一个私有的、静态本类对象的引用;
3.提供一个公有的、静态的方法,返回本类对象的引用;直接在本类实例化一个自己的对象,这种叫做饿汉式
public class Cat {
private static final Cat cat = new Cat("波斯猫");
String name;
private Cat(String s){
this.name = s;
}
public static Cat getCat(){
return cat;
}
}
单例模式:懒汉式
/*
* 懒汉式与饿汉式的区别:
*
* 相同点:
* 1.将构造方法私有化;
* 不同点:
* 1.饿汉式:定义成员变量时,就直接实例化对象;
* 懒汉式:定义成员变量时,不实例化对象;
* 2.饿汉式:方法内,直接返回引用;
* 懒汉式:方法内,先判断,再返回引用;
*/
public class Demo {
public static void main(String[] args) {
Cat c1 = Cat.getCat();//第一次访问,getCat()方内部会实例化一个Cat对象
Cat c2 = Cat.getCat();//第二次访问,getCat()方法直接return成员属性
}
}
public class Cat {
private static Cat cat = null ;
private Cat(){
}
public synchronized static Cat getCat(){
if(cat == null){
cat = new Cat();
}
return cat;
}
}
多线程最后一个知识点:Runtime类
java.lang.Runtime:每个 Java 应用程序都有"一个" Runtime类实例,使应用程序能够与其运行的环境相连接。
可以通过 getRuntime 方法获取当前运行时。
public class Demo {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
try {
rt.exec("notepad");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行的是打开记事本操作
多线程部分思维导图: