8 - 1 程序、线程、进程
一、对于进程和线程的理解
1.线程与进程的内存分配
每个进程都会分配一块内存
每个线程都有一份虚拟机栈和程序计数器,但所有进程都共享一个公共的方法区和堆以及本地方法栈。这就意味着多个线程都可以对堆中的实例变量等进行修改。
2..并行与并发
3.多线程的优点
4.多线程应用场景
8 - 2 线程的创建和使用
一、线程的创建(一)
1.创建一个Thread类的子类
2.重写Thread类的run() --> 将此线程执行的操作声明在run()方法中
3.创建Thread子类的对象
4.通过此对象调用start():①启动当前线程②调用当前线程的run()
package com.atguigu.java; /** * @author 张丁野 * @version v1.0 * @time 2022-04-02-21:14 * @Description * 多线程的创建: * 方式一:继承与tread类的方式 * */ class MyTread extends Thread{ //override run() @Override public void run() { for (int i = 0;i < 100;i++){ if(i % 2 == 0){ System.out.println(i); } } } } public class TreadTest { public static void main(String[] args) { //创建Thread类的对象 MyTread t1 = new MyTread(); t1.start(); for (int i = 0;i < 100;i++){ if(i % 2 == 0){ System.out.println(i + "*******main()*******"); } } } }
1、Thread类的有关方法(1)
* 测试Tread类的常用方法
* 1.start()启动当前线程,调用当前线程的run()
* 2.run():通常需要重写Thread类中的此方法,将创建的线程需要执行的操作声明在此方法中
* 3.currentThread(static):静态方法,返回执行当前方法的线程
* 4.getName():获取当前线程的名字
* 5.setName():设置当前线程的名字
* 6.yield()(static):释放当前CPU的执行权
* 7.join():在线程A中调用线程B中的join(),线程A进入阻塞状态,
* 直到线程B完成后,线程A才结束阻塞状态
* 8.stop():已过时,当执行此方法时,强制结束此线程
* 9.sleep(long millitime)(static):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒内,当前线程是阻塞状态
* 10.isAlive():判断一下当前线程是否存活
class HelloThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ try{ sleep(1); }catch (InterruptedException e){ e.getStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } // if(i % 20 ==0){ // this.yield(); // } } } public HelloThread(String name){ super(name); } } public class ThreadMethodTest { public static void main(String[] args) { HelloThread h1 = new HelloThread("THREAD:1"); h1.setName("线程一"); h1.start(); //给主线程命名 Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } if(i == 20){ try { h1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(h1.isAlive()); } }
2、线程的调度
线程的优先级:
1.MAX_PRIORITY:10
MIN_PRIORITY:1
NORMAL_PRIORITY:5
2.如何获取和设置线程的优先级:
getPriority();
setPriority();
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
问题:
1.不能通过直接调用run()的方式启动线程
2.再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报illgalThreadException
练习:
创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
package com.atguigu.exer; /** * @author 张丁野 * @version v1.0 * @time 2022-04-03-19:48 * @Description */ public class ThreadDemo { public static void main(String[] args) { // MyThread1 t1 = new MyThread1(); // MyThread2 t2 = new MyThread2(); // t1.start(); // t2.start(); //创建thread类的匿名子类的方式 new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); } } class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
创建三个窗口卖票总票数为100张
package com.atguigu.java; /** * @author 张丁野 * @version v1.0 * @time 2022-04-04-10:04 * @Description * 例子:创建三个窗口卖票总票数为100张,使用类继承的方式 */ class Window extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(this.getName() + ":买票,票号为:" + (101 - ticket)); ticket--; }else { break; } } } } public class WindowTest { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口一"); w2.setName("窗口二"); w3.setName("窗口三"); w1.start(); w2.start(); w3.start(); } }
二、创建多线程的方式(二)
* 1.创建了一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run() --调用了Runnable类型的target的run()
理解:
Thread类和用户自定义实现类同实现Runnable接口,在Thread构造器中,可以传入接口类型的target(即接口的实现类),执行run()方法时会首先判断target是否非空,若非空,则调用Runnable实现类的run()方法
不同点:多个线程使用一个对象,共享相同的属性与方法
class MThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 ==0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest1 { public static void main(String[] args) { MThread m1 = new MThread(); Thread t1 = new Thread(m1); Thread t2 = new Thread(m1); //再启动一个线程,遍历100以内的偶数 t1.start(); t2.start(); } }
比较创建多线程的两种方式
开发中:优先选择:实现Runnable接口的方式
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runnable{
}
两种方式都需要重写run(),将线程要执行的逻辑声明在run()
8 - 3 线程的生命周期
8 - 4 线程的同步
一、问题的提出
1.多个线程执行的不确定性引起执行结果的不稳定
2.多个线程对账本的共享,会造成操作的不完整性,会破坏数据
二、实例
* 创建三个窗口卖票总票数为100张,实现Runnable类的方式
* 问题:1.买票过程中出现了重票和错票 --> 出现了线程安全问题
* 2.出现的原因:当某个线程操作车票的过程中,尚未完成时,
* 其他的线程也参与进来,操作车票。
* 3.解决方式:当一个线程在操作ticket的时候,其他线程不能参与进来
* 直到线程a操作完ticke时,其他线程才可以开始操作ticket。
* 这种情况即使线程a出现了阻塞,也不能改变。
* 4.在Java中,我们通过同步机制来解决线程安全问题
三、解决方式
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码 --> 不能包含代码多了,也不能包含代码少了(循环尽量在线程外 --> 让其他线程有机会进入同步方法内,使多线程意义存在)
2.共享数据:多个线程共同操作的变量。比如:ticket
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
要求:多个线程必须使用同一把锁
补充:
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器
方式二:同步方法
将需要同步的代码写入新的同步方法中
理解 :
同步的方式,解决了线程安全问题(好处)
操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低(局限性)
重新理解单例模式
package com.atguigu.java1; /** * @author 张丁野 * @version v1.0 * @time 2022-04-07-19:42 * @Description * 使用同步机制将单例模式中的懒汉式改写为线程安全的 */ public class BankTest1 { } class Bank{ private Bank(){ } private static Bank instance = null; public static Bank getInstance(){ //方式一:效率稍差 /* synchronized (Bank.class){ if (instance == null){ instance = new Bank(); } return instance; } } */ //方式二: if (instance == null){ synchronized (Bank.class){ if (instance == null){ instance = new Bank(); } } } return instance; } }
线程死锁的问题
1.死锁的理解:不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,都形成了线程的死锁
2.说明:
1)出现死锁后不会出现异常,只是所有的线程都处于阻塞状态,无法继续
2)我们使用同步时,需要避免出现死锁
方式三:Lock锁
class Window extends Thread{ private static int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ System.out.println(this.getName() + ":买票,票号为:" + (101 - ticket)); ticket--; }else { break; } } } } public class WindowTest { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口一"); w2.setName("窗口二"); w3.setName("窗口三"); w1.start(); w2.start(); w3.start(); } }
* 1.面试题:synchronized 与 lock的异同?
* 同:二者都可以解决线程安全问题
* 不同:synchronized 机制在执行完相应的代码逻辑以后,自动的释放同步监视器
* lock需要手动的启动同步(lock()),同时结束同步也需要手动实现
四、练习
package com.atguigu.exer; import java.util.concurrent.locks.ReentrantLock; /** * @author 张丁野 * @version v1.0 * @time 2022-04-09-16:46 * @Description */ class Account1{ private double balance; public Account1() { } public Account1(double balance) { this.balance = balance; } public void setBalance(double balance) { this.balance = balance; } public double getBalance() { return balance; } public void deposit(double amt){ if (amt > 0) { balance += amt; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":成功存款,余额为" + getBalance()); } } } class Cutomer implements Runnable{ private Account1 acct; private static ReentrantLock lock = new ReentrantLock(true); public Cutomer(Account1 acct) { this.acct = acct; } @Override public void run() { for (;;) { lock.lock(); if (acct.getBalance() >= 0 && acct.getBalance() < 3000){ acct.deposit(1000); }else { break; } lock.unlock(); } } } public class AccountTest1 { public static void main(String[] args) { Account1 acc1 = new Account1(); Cutomer c1 = new Cutomer(acc1); Cutomer c2 = new Cutomer(acc1); Thread t1 = new Thread(c1); Thread t2 = new Thread(c2); t1.setName("甲"); t2.setName("乙"); t1.start(); t2.start(); } }
8 - 5 线程的通信
一、问题的引入
例题:
package com.atguigu.java2; /** * @author 张丁野 * @version v1.0 * @time 2022-04-09-17:02 * @Description * 线程通信的例子 */ class Number implements Runnable{ private static int number = 1; @Override public void run() { while (true) { synchronized(this){ this.notify(); if (number <= 100){ System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else { break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number n1 = new Number(); Thread t1 = new Thread(n1); Thread t2 = new Thread(n1); t1.setName("线程一"); t2.setName("线程二"); t1.start(); t2.start(); } }
二、线程通信的方法
1.线程通信涉及到的方法
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait()的一个线程。如果有多个线程被wait,就唤醒优先级高的线程
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
2.说明:
1)wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2)wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法区的同步监视器
3)wait(),notify(),notifyAll()三个方法定义在java.lang.obj中
面试题:sleep() 和 wait() 方法的异同?
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
2.不同点:
1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2) 调用的要求不同:sleep()可以在任何需要的场景下使用。wait()必须使用在同步代码块或同步方法当中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
练习:生产者消费者问题
8 - 6 创建线程方式三:实现Callable接口
实现过程:
1.创建一个实现Callable的实现类
2.实现call(),将此线程需要执行的操作声明在call()中。
3.创建Callable接口实现类的对象
4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中。
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread类的对象,并启动该线程
6.获取Callable中call方法的返回值
package atguigu.java2; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author 张丁野 * @version v1.0 * @time 2022-04-11-21:49 * @Description */ class NumThread implements Callable<Integer> { public Integer call() throws Exception{ int sum = 0; for (int i = 1; i <= 100; i++) { if (i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { NumThread numThread = new NumThread(); FutureTask<Integer> futuretask = new FutureTask(numThread); new Thread(futuretask).start(); try { //get()返回值即为FutureTask构造器参数Callable实现类重写的call方法的返回值 Integer sum = futuretask.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
*面试题.
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程的方式强大?
1.call()可以有返回值
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
3.Callable是支持泛型的
8 - 7 创建线程方式之四:使用线程池
创建方式:
1.提供指定线程数量的线程池
2.执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
3.关闭连接池
package atguigu.java2; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author 张丁野 * @version v1.0 * @time 2022-04-11-23:09 * @Description * 创建线程的方式四:使用线程池 * */ class NumberThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadPool { public static void main(String[] args) { //1.提供指定线程数量的线程池 ExecutorService service = Executors.newFixedThreadPool(10); //2.执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread());//适合适用于Runnable service.execute(new NumberThread1()); // service.submit();//适合适用于Callable //3.关闭连接池 service.shutdown(); } }
package atguigu.java2; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * @author 张丁野 * @version v1.0 * @time 2022-04-11-23:09 * @Description * 创建线程的方式四:使用线程池 * */ class NumberThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadPool { public static void main(String[] args) { //1.提供指定线程数量的线程池 ExecutorService service = Executors.newFixedThreadPool(10);// ThreadPoolExecutor service1 = (ThreadPoolExecutor)service; //设置线程池的属性 service1.setCorePoolSize(15); //service1.setKeepAliveTime(10); //2.执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread());//适合适用于Runnable service.execute(new NumberThread1()); // service.submit();//适合适用于Callable //3.关闭连接池 service.shutdown(); } }
优点
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maxmumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止