程序、进程、线程
程序:是为了完成特定任务、用某种语言编写的一组指令的集合(也就是写的代码)
进程:进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
线程:1)线程由进程创建,是进程的一个实体 2)一个进程可以有多个线程
(比如,迅雷下载东西,启动迅雷就是一个进程,里面的每一个下载任务就是一个线程)
单线程:同一个时刻,只允许执行一个线程
多线程:同一时刻,可以执行多个线程(比如,一个qq进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件)
并发、并行
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行
- 线程使用——实现Runnable接口
Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程是不可能的,因此Java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。
package thread_;
/**
* 编写程序,创建两个线程,一个线程每隔1秒输出“hello,world”,输出10次,退出
* 一个线程每隔1秒输出“hi”,输出5次退出
*/
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread = new Thread(t1);
Thread thread1 = new Thread(t2);
thread1.start();
thread.start();
}
}
class T1 implements Runnable{
int count = 0;
@Override
public void run() {
while (true){
System.out.println("hello,world" + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10)
break;
}
}
}
class T2 implements Runnable{
int count = 0;
@Override
public void run() {
while (true){
System.out.println("hi" + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5)
break;
}
}
}
主要看下两种方式实现多线程
package thread_;
/**
* 使用多线程模拟三个窗口同时售票
* 主要看下两种方式实现多线程
*/
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
//
// SellTicket01 sellTicket02 = new SellTicket01();
//
// SellTicket01 sellTicket03 = new SellTicket01();
// // 这里会出现票数超卖
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
System.out.println("==========使用实现接口的方式来售票==============");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start(); // 第1个线程-窗口
new Thread(sellTicket02).start(); // 第2个线程-窗口
new Thread(sellTicket02).start(); // 第3个线程-窗口
}
}
// 使用Thread方式
class SellTicket01 extends Thread{
private static int ticketNum = 100; // 让多个线程共享 ticketNum
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束");
break;
}
// 休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票 " + "剩余票数" + (--ticketNum));
}
}
}
// 实现接口的方式
class SellTicket02 implements Runnable{
private int ticketNum = 100; // 让多个线程共享 ticketNum
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束");
break;
}
// 休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票 " + "剩余票数" + (--ticketNum));
}
}
}
// 上面也会出现票超卖的情况
通知线程退出
线程终止:
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
- interrupt,中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠的线程
- sleep,线程的静态方法,使当前线程休眠
线程插队
- yield:线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
package thread_;
public class ThreadMethodExercise extends Thread{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new T4());
for (int i = 1; i < 11; i++) {
System.out.println("hi");
if (i == 5){
thread.start(); // 启动子线程
thread.join(); // 立即将该子线程插入到main,让该子线程先执行
}
}
}
}
class T4 implements Runnable{
int count = 10;
@Override
public void run() {
while (true){
System.out.println("hello" + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10)
break;
}
}
}
守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
- 使用关键字 Synchronized
package thread_;
/**
* 使用多线程模拟三个窗口同时售票
* 主要看下两种方式实现多线程
*/
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
//
// SellTicket01 sellTicket02 = new SellTicket01();
//
// SellTicket01 sellTicket03 = new SellTicket01();
// // 这里会出现票数超卖
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
// System.out.println("==========使用实现接口的方式来售票==============");
// SellTicket02 sellTicket02 = new SellTicket02();
// new Thread(sellTicket02).start(); // 第1个线程-窗口
// new Thread(sellTicket02).start(); // 第2个线程-窗口
// new Thread(sellTicket02).start(); // 第3个线程-窗口
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start(); // 第1个线程-窗口
new Thread(sellTicket03).start(); // 第2个线程-窗口
new Thread(sellTicket03).start(); // 第3个线程-窗口
}
}
// 使用synchronized解决同步问题
class SellTicket03 implements Runnable{
private int ticketNum = 100; // 让多个线程共享 ticketNum
private boolean loop = true; // 控制run 方法的变量
public synchronized void sell(){ // 同步方法 给sell方法上锁 在同一时刻,只能有一个线程来执行sell方法
if (ticketNum <= 0){
System.out.println("售票结束");
loop = false;
return;
}
// 休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票 " + "剩余票数" + (--ticketNum));
}
@Override
public void run() {
while (loop){
sell();
}
}
}
-
Java语言中,引入对象互斥锁的概念,来保证共享数据操作的完整性
-
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
-
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
-
同步的局限性:导致程序的执行效率要降低
-
同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
-
同步方法(静态的)的锁为当前类本身 默认锁对象:当前类.class
-
线程的死锁:多个线程都占用了对方的锁资源,但不肯相让,导致了死锁
释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep(), Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
- 应尽量避免使用suspend() 和 resume()来控制线程
练习
package thread_;
import java.util.Scanner;
/**
* 在main方法中启动两个线程,在第一个线程循环随机打印100以内的整数,直到第二个线程从键盘读取了“Q”命令
*/
public class Homework01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a); // 一定要把a传给线程b,不然呢没办法控制a
a.start();
b.start();
}
}
class A extends Thread{
private boolean loop = true;
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
while (loop){
System.out.println((int) (Math.random() * 100 + 1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B extends Thread{
private A a;
Scanner scanner = new Scanner(System.in);
public B(A a) { // 构造器中直接传入A类对象
this.a = a;
}
@Override
public void run() {
while (true){
System.out.println("输入Q表示退出");
char key = scanner.next().toUpperCase().charAt(0);
if (key == 'Q'){
// 以通知方式结束a线程
a.setLoop(false);
System.out.println("b线程退出");
break;
}
}
}
}
练习
package thread_;
public class Homework02 {
public static void main(String[] args) {
T t = new T();
Thread thread = new Thread(t);
thread.setName("t1");
Thread thread1 = new Thread(t);
thread1.setName("t2");
thread.start();
thread1.start();
}
}
// 每次取1000元
// 因为这里涉及到多个线程共享资源,所以使用实现Runnable方法
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
// 1. 这里使用synchronized 实现了线程同步
// 2. 当多个线程执行到这里时,就会去争夺this对象锁
// 3. 哪个线程正道到this对象锁,就执行synchronized代码块,执行完就释放this对象锁
// 4. 争夺不到this对象锁,就blocked,准备下次争夺
synchronized (this){
if (money < 1000) {
System.out.println("余额不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + " : " + money);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}