Java视频学习笔记-18-多线程

进程

进程就是正在运行的程序
如果一个程序(进程)只有一条执行路径,那么程序就是单线程程序
如果一个程序(进程)有多条执行路径,那么该程序就是多线程程序

进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
多进程可以在一个时间段内执行多个任务(并不是同时进行,是CPU在做着程序间的高效切换),可以提高CPU的使用率

线程

线程是程序的执行单元,执行路径,是程序使用cpu的最基本单位
多线程不是提高程序的执行速度,而是提高应用程序的使用率
如果一个进程的执行路径(线程)较多,那么会更有几率抢到CPU资源
线程的执行有随机性

并行和并发

并行是在某一个时间内同时运行多个程序,并发是在某一个时间点同时运行多个程序

Java程序的运行原理

由java命令启动JVM,JVM启动就相当于启动了一个进程,接着由该进程创建了一个主线程去调用main方法
JVM虚拟机的启动是多线程的,原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出,所以最低启动了两个线程

Java实现多线程的程序

由于线程是依赖进程而存在的,所以先创建一个进程,进程由系统创建,所以需要调用系统功能创建一个进程,而Java是不能直接调用系统功能的,所以没有办法直接实现多线程程序,但是Java可以去调用C/C++写好的程序来实现多线程程序,由C/C++去调用系统功能创建进程,然后由Java去调用,提供一些类供我们使用,从而实现多线程程序

实现多线程程序的两种方式

方法1:继承Thread类

  1. 自定义类MyThread继承Thread类
  2. MyThread类里面重写run()
    由于不是类中的所有代码都能被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码
  3. 创建对象
  4. 启动线程
    run()和start()的区别:run()仅仅是封装被线程执行的代码,直接调用是普通方法;start()首先启动了线程,然后再由jvm去调用该线程的run()方法
    import itcast1.MyThread;
    
    public class MyThreadDemo {
    	public static void main(String[] args) {
    		MyThread mt1 = new MyThread();
    		MyThread mt2 = new MyThread();
    		mt1.start();
    		mt2.start();
    	}
    }
    public class MyThread extends Thread{
    	//重写run方法
    	public void run() {
    		for(int x=0;x<300;x++) {
    			System.out.println(x);
    		}
    	}
    }

    获取线程的名称:public final String getName
    设置线程的名称:public final String setName(String name)
    线程休眠:public static void sleep(long millis)
    礼让线程(等待该线程终止):public final void join()
    后台线程(标记为守护线程):public final void setDaemon(boolean on)
    中断线程:public final void stop():让线程终止,但是还可以使用
                      public void interrupt():中断线程,把线程的状态终止,并抛出一个异常
    线程优先级仅仅表示线程获取的CPU时间片的几率,存在随机性

方法2:实现Runnable接口

  1. 自定义类MyRunnable接口
  2. 重写run()方法
  3. 创建MyRunnable的对象
  4. 创建Thread类的对象,并把步骤3的对象作为构造参数传递
import itcast1.MyRunnable;

public class MyRunnableDemo {
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();
		//将mr作为构造参数传递
		Thread t1 = new Thread(mr);
		Thread t2 = new Thread(mr);
		
		t1.start();
		t2.start();
	}
}
public class MyRunnable implements Runnable{
	//重写run()方法
	public void run() {
		for(int x=0;x<300;x++) {
			//获取当前执行线程的名称
			System.out.println(Thread.currentThread().getName()+"---"+x);
		}
	}
}

方式2实现接口方式的好处:

解决java单继承带来的局限性(如果有一个类已经有父类,就无法继承Thread)
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想(比如方式1 定义了一个成员变量,在调用时就有两个成员变量,而方式2只有一个)

两种方式实现电影院售票

方法1

public class SellTicket extends Thread{
	//定义100张票
	//private int tickets = 100;
	//为了让多个线程对象共享这100张票,用static修饰
	private static int tickets = 100;
	public void run() {
		//模拟一直有票
		while(true) {
			if(tickets > 0) {
				System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
			}
		}
	}
}
/**
 * 继承Thread类实现
 * @author Emotion
 *
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		//三个售票窗口,此时建了三个tickets变量,其实不太合适,顾方法2好一些
		SellTicket st1 = new SellTicket();
		SellTicket st2 = new SellTicket();
		SellTicket st3 = new SellTicket();
		
		//给线程对象起名字
		st1.setName("窗口1");
		st2.setName("窗口2");
		st3.setName("窗口3");
		
		//启动线程
		st1.start();
		st2.start();
		st3.start();
	}
}

方法2

 如何解决线程安全问题?

  1.  是否是多线程环境
  2.  是否有共享数据
  3.  是否有多条语句操作共享数据

 
 1和2改变不了,所以改变3
 具体思想:把多条语句操作共享数据包成一个整体,让某个线程在执行的时候,别人不能来执行
 即Java提供的同步机制
 多个线程必须是同一把锁(即同一个对象)

/*
 * 通过加入延迟后发现
 * 1.相同的票卖了多次
 *   CPU的一次操作必须是原子性的(即最简单基本的操作,比如当A操作的时候B就不操作)
 * 2.出现了负数票
 *   随机性和延迟导致
 */

public class SellTicket implements Runnable{
	//定义100张票
	private int tickets = 100;
	//创建锁对象
	private Object obj = new Object();
	
	public void run() {
		while(true) {
			synchronized (obj) {
				if(tickets > 0) {
					//为了模拟更真实的场景,稍作休息
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName()+"正在售卖第"+(tickets--)+"张票");
				}
			}
		}
	}
}
public class SellTicketDemo {
	public static void main(String[] args) {
		SellTicket st = new SellTicket();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		Thread t3 = new Thread(st);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

同步

 同步代码块:
 synchronized(对象){
        需要同步的代码;
 }

同步的好处:解决了多线程的安全问题
同步的弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率;容易产生死锁
同步的两种方式:同步代码块和同步方法

同步方法的格式及锁对象问题:(如果锁对象是this,就可以考虑使用同步方法,否则能使用同步代码块的尽量使用同步代码块)

  • 同步方法关键字加载方法上
  • 同步方法锁对象是this

静态方法锁对象是类的字节码文件对象(class)

Lock锁

可以明确知道在哪里上了锁,在哪里释放了锁

  • void lock():获取锁
  • void unlock():释放锁

ReentrantLock是Lock的实现类

死锁

两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象

线程间通信问题

不同种类的线程间针对同一个资源的操作

线程池

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,使用线程池可以提高性能
JDK5之后有Executors工厂类来产生线程池

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int x=0; x<100; x++) {
			System.out.println(Thread.currentThread().getName()+":"+x);
		}
	}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsDemo {
	public static void main(String[] args) {
		//创建一个线程池对象,控制要创建几个线程对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		
		//可以执行Runnable对象或Callable对象代表的线程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		
		//结束线程池
		pool.shutdown();
	}
}

实现多线程程序的方式3(和线程池结合):Callable接口

用Callable接口的话和上面步骤差不多,但是有以下好处和弊端:

  • 可以有返回值
  • 可以抛出异常
  • 代码比较复杂,所以一般不用

定时器

可以在指定的时间做某件事情,还可以重复的做某件事情,依赖Timer和TimerTask这两个类来实现
Timer定时,TimerTask做任务

常见问题

sleep()和wait()方法的区别

  • sleep():不释放锁;必须指定时间
  • wait():释放锁;可以不指定时间,也可以指定

为什么wait(),notify()等方法定义在Object类中?
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁,而Object代码是任意的对象,所以要定义在这里面

状态转换图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值