多线程
进程:正在运行的程序
线程:
在一个进程内部可以执行多个任务,而这每一个任务我们就可以看成是一个线程。
是程序使用CPU的基本单位。
多线程:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率
例:对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,
且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。
并行和并发:
并发 : 指应用能够交替执行不同的任务,快速切换;
(物理上同时发生,指在某一个时间点同时运行多个程序。)
并行 : 指应用能够同时执行不同的任务;
(逻辑上同时发生,指在某一个时间内同时运行多个程序)
例:吃饭的时候可以边吃饭边听音乐, 这两件事情可以同时执行
Java程序运行原理:
1、Java程序运行原理
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。
2、JVM的启动是多线程的,因为JVM启动至少启动了垃圾回收线程和主线程。
Thread类常用方法:
1、Thread类的基本获取和设置方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称
其实通过构造方法也可以给线程起名字
2、如何设置和获取线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级
3、线程休眠:
public static void sleep(long millis) 线程休眠
4、加入线程:
public final void join()/// 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法
5、礼让线程:
public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
6、守护线程:
public final void setDaemon(boolean on):
//将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
7、中断线程
public final void stop(): 停止线程的运行
public void interrupt():
当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,
可以通过这个方法清除阻塞
多线程程序实现的方式:
一、继承Thread类
public class test1 {
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread("吕洞宾");
th1.start();
Thread.sleep(1000);
th1.interrupt();//打断线程的阻塞状态,让线程继续运行
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
二、实现Runnable接口:
public class test2 {
public static void main(String[] args) {
Thread th = new Thread(myRunable,"萧炎");
th.start();
Thread th2 = new Thread(myRunable,"萧薰儿");
th2.start();
}
}
class MyRunable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "==" + i);
}
}
}
三、实现 Callable 接口:
//实现 Callable 接口。相较于实现 Runnable 接口的方式,
方法可以有返回值,并且可以抛出异常。
public class test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);
Thread th = new Thread(integerFutureTask);
th.start();
Integer integer = integerFutureTask.get();
//获取线程执行完之后,返回的结果
System.out.println(integer);
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"啦啦啦啦啦");
return 100;
}
}
线程安全问题:
判断一个多线程应用程序是否有问题的标准:
1、是否是多线程环境
2、是否存在共享数据
3、是否存在多条语句同时操作共享数据
同步代码块:
1、格式:
synchronized(对象){
需要同步的代码;
}
2、优缺点:
好处:同步的出现解决了多线程的安全问题。
弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,
无形中会降低程序的运行效率。
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
3、java的内置锁:
每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
同步方法: 就是把同步关键字加到方法上
同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
Lock锁:
常见方法:
public void lock() 获取锁
public void unlock() 释放锁
例:完成多个窗口共卖100张票的程序,并测试
public class test1 {
public static void main(String[] args) {
CellRunable cellRunable = new CellRunable();
Thread th1 = new Thread(cellRunable);
Thread th2 = new Thread(cellRunable);
Thread th3 = new Thread(cellRunable);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class CellRunable implements Runnable {
static int piao = 100;
static Object obj = new Object();
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock(); //加锁
if (piao > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售卖 " + (piao--) + " 张票");
}
lock.unlock(); //释放锁
}
}
}
死锁问题:
1、如果出现了同步嵌套,就容易产生死锁问题;
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
例: //两个或者两个以上的线程, 在抢占CPU的执行权的时候, 都处于等待状态
public class test2 {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}
interface LockeInterface {
//定义两把锁
public static final Object objA=new Object();
public static final Object objB=new Object();
}
class MyThread extends Thread {
boolean b;
public MyThread(boolean b) {
this.b = b;
}
@Override
public void run() {
if (b) {
synchronized (LockeInterface.objA) {
System.out.println("true objA 进来了");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockeInterface.objB) {
System.out.println("true objB 进来了");
}
}
} else {
synchronized (LockeInterface.objB) {
System.out.println("false objB 进来了");
synchronized (LockeInterface.objA) {
System.out.println("false objA 进来了");
}
}
}
}
}
线程的状态转换图:
新建:线程被创建出来
就绪:具有CPU的执行资格,但是不具有CPU的执行权
运行:具有CPU的执行资格,也具有CPU的执行权
阻塞:不具有CPU的执行资格,也不具有CPU的执行权
死亡:不具有CPU的执行资格,也不具有CPU的执行权
线程池:
线程池可以很好的提高性能,线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
JDK5新增了一个Executors工厂类来产生线程池,有以下几个方法:
public static ExecutorService newCachedThreadPool():
根据任务的数量来创建线程对应的线程个数
public static ExecutorService newFixedThreadPool(int nThreads):
固定初始化几个线程
public static ExecutorService newSingleThreadExecutor():
初始化一个线程的线程池,这些方法的返回值是ExecutorService对象,
该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
它提供了如下方法:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
使用步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
匿名内部类的方式实现多线程程序:
例:
public class test {
public static void main(String[] args) {
//方式1
new Thread() {
@Override
public void run() {
System.out.println("线程执行了");
}
}.start();
//方式2
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行了");
}
}).start();
}
}
定时器:
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。
通过Timer和TimerTask类来实现定义调度的功能
Timer:
public Timer()
public void schedule(TimerTask task, long delay):
public void schedule(TimerTask task,long delay,long period);
public void schedule(TimerTask task, Date time):
public void schedule(TimerTask task, Date firstTime, long period):
TimerTask:
public abstract void run()
public boolean cancel()
例:
public class MyTest {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
MyTimerTask myTimerTask = new MyTimerTask(timer);
String str = "2019-05-17 19:00:00";
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);
timer.schedule(myTimerTask, date);
// timer.cancel();取消定时器
}
}
class MyTimerTask extends TimerTask {
Timer timer;
public MyTimerTask(Timer timer) {
this.timer=timer;
}
@Override
public void run() {
System.out.println("叮叮叮,起床啦");
}
}