程序、进程、线程
-
程序:一段静态的代码,静态对象
- 进程:正在运行的一个程序,是一个动态的过程,有生命周期。进程作为资源分配的单位,系统在运行时会为每个进程分配内存区域。
- 线程:进程可以进一步细化为线程,同一个进程可以支持多个线程就叫多线程,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器
- 每个线程各自有一套虚拟机栈和程序计数器
- 每个进程各有一份方法区和堆
- 单核CPU和多核CPU:一个JAVA应用程序至少有三个线程,main主线程,gc()垃圾回收线程,异常处理线程
- 并行和并发:
并行——多个CPU同时执行多个任务,如:多个人同时做不同的事儿。
并发——一个CPU同时执行多个任务,如:秒杀、多个人做同一件事。
使用多线程的优点
1、提高程序的响应。对图形化界面更有意义,可增强用户体验
2、提高计算机系统CPU的利用率
3、改善程序结构,进程分为线程独立运行,有利于理解和修改
线程的创建和使用
方式一:继承于Thread类
1、创建Thread类的子类
2、重写Thread类的run()方法——>将此线程执行的操作声明在run方法中
3、创建Thread类的子类对象
4、通过此对象调用start():作用(1)启动当前线程;(2)调用当前线程的run()
package java5;
//1、创建Thread类的子类
class MyThread extends Thread{
//2、重写Thread类的run()方法
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类对象
//MyThread t1 = new MyThread();
MyThread t1 = new MyThread();//这里new MyThread()之后直接alt + enter就可以造对象了
//4、通过此对象调用start()
t1.start();
}
}
问题:
- 不能通过run()的方式执行线程,用run()只是普通的调用方法,一定!一定!要是 start()
- 如果还要再启动一个线程,就再新造一个MyThread对象,再start()——要想启动多个线程,就要去造多个对象,再去调start()方法。
package java5;
public class ThreadDemo {
/*
练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个遍历100以内的奇数。
*/
public static void main(String[] args) {
// MyThread1 m1 = new MyThread1();
// MyThread2 m2 = new MyThread2();
// m1.start();
// m2.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{
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{
public void run(){
for (int i = 0; i < 100;i++){
if(i % 2 != 0)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
Thread里的常用方法:
- start():①启动当前线程 ②调用当前线程的run()
- run():通常需要重写此方法
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前CPU的执行权
- join():在线程A中调用B的该方法,线程A进入阻塞状态直到线程B执行完之后线程A才继续执行
- sleep():让当前线程强制阻塞
线程的优先级:
- MAX——10,MIN——1,NORM——5(默认的优先级)
- 涉及的方法:getPriority()——返回线程的优先值,setPriority()——改变线程的优先级
- 高优先级的线程不一定就先执行,只是大概率先执行。
方式二:实现Runnable接口
- 创建一个实现Runnable接口的类.
- 实现类实现Runnable中的抽象方法:run()方法.
- 创建实现类的对象.
- 将对象作为参数传递到Thread类的构造器中,创建Thread类的对象.
- 通过此对象调用start()方法.
package java5;
/*
例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
*/
class Window1 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket --;
}else{
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);//这里形参都是m,本身就可以实现共享数据
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
两种方式的对比
开发中:优先选择,实现Runnable接口的方式。
原因:Runnable没有单继承性的局限性,更适合多个线程共享数据的情况.
相同点:都需要重写run并且将线程要执行的逻辑声明在run()中.
线程的生命周期
线程的同步(解决线程安全问题)
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码,包含的代码一定要刚刚好,不能少也不能多。
共享数据:多个线程共同操作的变量。如卖票中的ticket。
2.同步监视器俗称“锁”。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。(实现的方式中一般写在run()方法前面;继承的方法中,要把synchronized声明为static,确保共享数据是唯一的)
实现类方法
package java5;
/*
例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
*/
class Window1 implements Runnable{
private static int ticket = 100;
Object obj = new Object();//锁,这个obj一定要是共用的,唯一的,放的位置要正确
public void run(){
synchronized(obj){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket --;
}else{
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t1.setName("窗口二");
t1.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
(1)实现类方法可以使用this,就不用每次再造对象了。
(2)继承类方法可以用类去充当同步监视器——synchronized(Window2.class——类也是对象,万物皆对象)这里的Window2只会加载一次,this要慎用,一定要注意唯一性!!
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,就把这个方法弄成同步方法。
1、同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2、非静态的同步方法(一般是实现Runnable类的方式),同步监视器是this;静态的同步方法(一般是继承thread类的方式),同步监视器是:当前类本身
//同步方法+实现Runnable
class MyRun implements Runnable{
private static int ticket=100;
@Override
public void run(){
show();//先同步方法,然后把这个方法包到run方法中
}
public synchronized void show(){//这里的同步监视器就是this
for (tikect>0) {
System.out.println(getName()+"买票,票号为:"+ticket);
ticket--;
}
}
}
public static void main(String[] args) {
MyRun r=new MyRun();
Thread t1=new Thread(r);
Thread t2=new Thread(r);
Thread t3=new Thread(r);
t1.start();
t2.start();
t3.start();
}
//同步方法+实现Thread类
class MyThread extends Thread{
private static int ticket=100;
@Override
public void run(){
show();
}
public static synchronized void show(){//必须是静态的,这里的同步监视器是当前类
for (tikect>0) {
System.out.println(Thread.currentThread.getName()+"买票,票号为:"+ticket);
ticket--;
}
}
}
同步使得速度变慢,但是为了安全,还是需要用的。
线程安全的单例模式:懒汉式
原始的两种模式:
(1)懒汉式(延迟对象的创建,线程不安全)
package java5;
public class SingleTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
//懒汉式,什么时候需要用,什么时候造对象
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化,对象必须为static
private static Order instance = null;//主要区分点private static Bank instance = new Bank();
//3.声明public,static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
(2)饿汉式(对象的加载时间过长,它是线程安全的)
package java5;
/**
* 单例设计模式(某个类只能存在一个实例)
*
*/
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
//饿汉式,先提前造好对象
class Bank{
//1.私有化类的构造器
private Bank(){
}
//内部创建类的静态对象
private static Bank instance = new Bank();//private用不了,所以要有公共的方法
//提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
线程安全的懒汉式
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化,对象必须为static
private static Order instance = null;//主要区分点private static Bank instance = new Bank();
//3.声明public,static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
synchronized(Bank.class){
if(instance == null){
instance = new Order();
}
}
}
return instance;
}
}
死锁的理解
1、同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃,造成了线程的死锁,没有异常没有提示,但所有线程都在阻塞状态。
Lock(锁)
1、synchronized与Lock的异同?
同:二者都可以解决线程安全问题
异:Lock自动锁定和解锁,synchronized在执行完相应的同步代码块之后,自动地释放同步监视器,Lock需要手动的启动同步——Lock(),同时结束同步也需要手动的实现——unlock()
优先顺序:
Lock——>同步代码块——>同步方法
线程的通信
涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
notifyall():一旦执行此方法,就会唤醒所有被wait的线程
说明:1.这三者必须是只能出现在同步代码块和同步方法中
2,这三个方法的调用者必须是同步代码块或同步方法中同步监视器。否则出现异常。
3.这三个方法是定义在java.lang.Object类中。
面试题:sleep()和wait()的异同:
同:一旦执行,就会使线程进入阻塞状态。
异:(1)声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
(2)调用要求不同,sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中
(3)关于是否释放同步监视器?
如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放锁,而wait()会释放锁。
线程通信例题:消费者,生产者问题
*生产者(Productor)将产品交给店员(CLerk),而消费者(Customer)从店员处取走产品,
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品
了,店员会告诉消费者等一下,如果店中有产品J通知消费者来取走产品。
问题思考:
是否是多线程问题?——生产者线程,消费者线程
是否有共享数据?——是,店员(或者产品)
如何解决线程安全问题?——同步机制,有三种方法
是否设计线程通信?是
package java5;
import java.util.Properties;
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if(productCount < 20){
productCount ++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else{
try {
wait();//有wait(),一般都要有notify()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount --;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//涉及线程问题——实现?继承?
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始生产产品。。。。");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}//让时间更久一些
clerk.produceProduct();
}
}
}
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始消费产品。。。。");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Customer c1 = new Customer(clerk);
c1.setName("消费者1");
p1.start();
c1.start();
}
}
新增多线程的方式——实现callable接口(建议使用)
使用的好处:
1、call()可以有返回值。
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息
3、callable是支持泛型的
使用线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁地创建,销毁,实现重复利用,类似于公共工具。
好处:1、提高响应速度(减少了创建线程的时间)
2、降低资源消耗
3、便于线程管理
corePoolSize:核心池的大小
maxmumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止。
●JDK 5.0起提供了线程池相关API: ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
➢void execute(Runnable command):执行任务1命令,没有返回值,一般用来执行
Runnable接口
➢<T> Future<T> submit(Callable<T> task): 执行任务,有返回值,一般又来执行
Callable接口
➢void shutdown():关闭连接池
package java7;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
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(i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);//一般用的是这种方法
//执行指定的线程的操作,需要提供实现runnable或者callable接口实现类的对象
service.execute(new NumberThread());
// service.submit()适合使用于runnable
//关闭线程池
service.shutdown();
}
}
●Executors: 工具类、 线程池的工厂类,用于创建并返回不同类型的线程池
➢Executors. newCachedThreadPool():创建一个可 根据需要创建新线程的线程池
➢Executors.newFixedThreadPolln); 创建一-个可重 用固定线程数的线程池
➢Executors.newSingle ThreadExecutor():创建一-个只有 一-个线程的线程池
➢Executors.newScheduledThreadPool(n): 创建一 个线程池,它可安排在给定延迟后运
行命令或者定期地执行。
总结:
实现多线程的方式:
(1)Thread继承类
(2)runnable接口
(3)callable接口
(4)使用线程池
线程安全的解决方式:
(1)同步代码块
(2)同步方法
(3)Lock锁
重点掌握:创建方式、同步、买票问题、懒汉式、饿汉式。