多线程概述
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和资源。
线程:
是进程的单个顺序控制流,或者说就是一个单独执行的路径
一个进程如果只有一条执行路径,称之为单线程
一个进程如果有多条执行路径,称之为多线程
线程是包含在进程中
创建线程的第一种方式:继承Thread类
1、创建一个自定义类继承Thread类
2、这个类要重写Thread类中的run方法
当线程启动之后,执行的代码逻辑仅是run()方法的代码逻辑
3、根据这个类创建线程对象
4、启动线程
注意事项:
1、启动线程调用的是start()方法
2、线程的先后启动顺序,对结果没有影响
调用start()与调用run()的区别
run()方法中仅仅是封装了被线程执行的代码,但是呢,直接调用run()与调用普通的方法方式没有任何区别
start()方法的调用,首先单独启动了一个线程,然后再由JVM去调用该线程类中的run()方法
package com.shujia.wyh.day24;
public class MyThreadDemo2 {
public static void main(String[] args) {
//模拟多线程环境
//至少创建2个及两个以上的线程对象
MyThread1 myThread1 = new MyThread1();
MyThread1 myThread2 = new MyThread1();
//启动线程
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
//写我们要线程执行的逻辑代码
for(int i=1;i<200;i++){
System.out.println(i);
}
}
}
通过构造方法给线程起名字:
Thread(String name) 分配一个新的 Thread对象。
获取线程的名字
public final String getName()返回此线程的名称
package com.shujia.wyh.day24;
public class MyThreadDemo3 {
public static void main(String[] args) {
//创建线程对象
//通过构造方法给线程起名字
//由于我们要模拟多线程环境,所以创建线程的个数为2个或2个以上
MyThread2 t1 = new MyThread2();
MyThread2 t2 = new MyThread2();
MyThread2 t3 = new MyThread2();
//public final void setName(String name)将此线程的名称更改为等于参数name 。
t1.setName("小明");
t2.setName("小王");
t3.setName("小周");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
class MyThread2 extends Thread {
public MyThread2() {
}
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(getName() + ":" + i);
}
}
}
线程休眠 public static void sleep(long millis)
package com.shujia.wyh.day24;
public class ThreadSleepDemo {
public static void main(String[] args) {
MySleepThread t1 = new MySleepThread();
MySleepThread t2 = new MySleepThread();
MySleepThread t3 = new MySleepThread();
//给线程设置名字
t1.setName("小明");
t2.setName("小王");
t3.setName("小周");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
class MySleepThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(getName() + ":" + i);
//加入休眠的方法
//public static void sleep(long millis)
//停1秒钟 1秒=100毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
加入线程 public final void join()
其他的线程等待调用join的线程执行完毕后再执行。
注意:
join的调用必须在start之后。
package com.shujia.wyh.day24;
public class ThreadJoinDemo {
public static void main(String[] args) {
MyJoinThread t1 = new MyJoinThread();
MyJoinThread t2 = new MyJoinThread();
MyJoinThread t3 = new MyJoinThread();
//给线程设置名字
t1.setName("小明");
t2.setName("小王");
t3.setName("小周");
//启动线程
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
class MyJoinThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(getName() + ":" + i);
}
}
}
礼让线程 public static void yield()
暂停当前正在执行的线程对象,并执行其他线程
它的作用是为了让多个线程之间运行的时候看起来更加和谐,并不能保证多个线程一人一次。
package com.shujia.wyh.day24;
public class ThreadYieldDemo {
public static void main(String[] args) {
//创建2个线程对象模拟多线程
MyYieldThread t1 = new MyYieldThread();
MyYieldThread t2 = new MyYieldThread();
//给线程起名字
t1.setName("小明");
t2.setName("小王");
//启动线程
t1.start();
t2.start();
}
}
class MyYieldThread extends Thread{
@Override
public void run() {
for(int i=1;i<=200;i++){
System.out.println(getName()+":"+i);
Thread.yield();
}
}
}
后台线程:(守护线程)public final void setDaemon(boolean on)
Java中有两类线程:用户线程,守护线程
用户线程:在学习多线程之前,运行起来的一个个的线程都是用户线程
守护线程:所谓守护线程,指的是程序在运行的时候,在后台提供了一个通用的服务线程。比如说垃圾回收线程,他就是一个守护线程,并且这种线程并不是一定存在的,所以反过来说,只要程序存在守护线程,程序就不会终止。
注意:
1、守护线程必须在启动之前进行设置
2、当运行的程序只有一个线程的时候并且这个线程是守护线程的时候,Java虚拟机退出(程序停止)
package com.shujia.wyh.day24;
public class ThreadDaemonDemo {
public static void main(String[] args) {
//创建3个线程
MyDaemonThread t1 = new MyDaemonThread();
MyDaemonThread t2 = new MyDaemonThread();
MyDaemonThread t3 = new MyDaemonThread();
//给线程起名字
t1.setName("刘备"); //此线程结束守护线程也结束
t2.setName("关羽");
t2.setDaemon(true);
t3.setName("张飞");
t3.setDaemon(true);
//启动线程
t1.start();
t2.start();
t3.start();
}
}
class MyDaemonThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(getName() + ":" + i);
}
}
}
中断线程 public void interrupt()
打断睡眠,run方法后面的代码继续执行,执行完后,抛出异常
package com.shujia.wyh.day24;
import java.util.Date;
public class ThreadStopDemo {
public static void main(String[] args) {
MyStopThread t1 = new MyStopThread();
t1.setName("小花");
t1.start();
try {
Thread.sleep(3000);
t1.interrupt(); //打断睡眠,run方法后面的代码继续执行,执行完后,抛出异常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyStopThread extends Thread {
@Override
public void run() {
System.out.println("开始执行时间:" + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束时间:" + new Date());
}
}
多线程的实现方案二:实现Runnable接口
1、自定义一个类实现Runnable接口
2、实现run()方法
3、创建自定义类对象
4、创建Thread线程对象,将自定义的对象作为参数传递到构造方法中
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
System.out.println(Thread.currentThread().getName());
package com.shujia.wyh.day24;
public class MyRunnableDemo1 {
public static void main(String[] args) {
//创建自定义类对象
MyRunnable1 myRunnable1 = new MyRunnable1();
//创建2个线程对象
Thread t1 = new Thread(myRunnable1);
Thread t2 = new Thread(myRunnable1);
//给线程起名字
t1.setName("小明");
t2.setName("小王");
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//由于Runnable接口中没有getName()方法,所以这里无法使用获取线程对象名字
//间接调用,我们可以先获取当前线程的对象,然后再调用Thread类中getName()方法
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
线程安全
要想去解决这个问题,就要搞清楚哪些原因导致的问题出现:
(三点总结出是否会出现线程安全问题,缺一不可)
1、是否存在多线程环境
2、是否存在共享数据/共享变量
3、是否有多条语句操作着共享数据/共享变量
解决问题的思想:
要是有一个办法可以将多条语句操作共享数据的代码给包成一个整体,在某个线程执行的时候,别的线程进不来就可以了。直到某个线程执行完一次run方法后,其他线程才能进入执行。
Java提供了一个机制给我们使用,来解决线程安全的问题:同步安全机制
解决方案一:同步代码块
语句格式:
synchronized(对象){需要同步的代码;}
1、这里的对象是:任意对象,但是需要注意的是,多个线程之间的锁对象要一样
2、需要同步:多条语句操作共享数据的代码
同步的好处:
解决了多线程的安全问题
同步的弊端:
加了一个同步代码块后,就相当于加了一把锁,每次进入同步代码块的时候都会去判断一下,降低了我们执行效率。
例:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
package com.shujia.wyh.day24;
public class SellTicketDemo4 {
public static void main(String[] args) {
//1、创建自定义类对象
TicketWindow4 ticketWindow2 = new TicketWindow4();
//2、创建3个线程对象,模拟3个窗口
Thread window1 = new Thread(ticketWindow2);
Thread window2 = new Thread(ticketWindow2);
Thread window3 = new Thread(ticketWindow2);
//3、给线程对象起名字
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
//4、启动线程
window1.start();
window2.start();
window3.start();
}
}
class TicketWindow4 implements Runnable{
private int tickets = 500;
private Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object){
if (tickets > 0) {
//为了模拟更加真实的售票场景,我们加入延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
}
解决方案二:Lock锁
Lock(接口)
具体的子类:Class ReentrantLock
lock() 加锁
unlock() 释放锁
package com.shujia.wyh.day25;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketDemo3 {
public static void main(String[] args) {
TicketWindow3 ticketWindow1 = new TicketWindow3();
//使用Thread类创建多个线程对象
Thread window1 = new Thread(ticketWindow1);
Thread window2 = new Thread(ticketWindow1);
Thread window3 = new Thread(ticketWindow1);
//给线程起名字
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
//启动线程
window1.start();
window2.start();
window3.start();
}
}
class TicketWindow3 implements Runnable {
private int tickets = 100;
//创建锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
lock.unlock();
}
}
}