线程介绍
进程
线程
单、多线程
1.单线程:同一个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发和并行
线程的使用
创建线程的两种方法
在java中线程来使用有两种方法。
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法
机制说明
当main线程通过start()方法启动一个子线程Thread-0,并不代表主线程会阻塞,它会继续执行,并且与子线程交替执行
主线程结束但是任有子线程不会造成应用程序的结束
主线程可以开多个子线程,子线程也能开新的子线程
举例说明(继承Thread类重写run()方法实现线程)
创建一个进程每隔一秒输出内容,输出8次后停止
package Threaduse;
//演示通过继承Thread 类创建线程
public class Test {
public static void main(String[] args) {
//创建Cat对象,可以当做线程使用
cat cat = new cat();
cat.start();//启动线程-> 最终会执行cat的run方法
}
}
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class cat extends Thread{
//重写run()方法
@Override
public void run() {//在这里写上自己要实现的业务逻辑
super.run();
int times=0;
while(true){
System.out.println("熊猫烧香"+times++);
//休眠一秒钟(一毫秒为单位),抛出异常ctrl+alt+t
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times==8){
break;
}
}
}
}
为什么调用start()不直接调用run()方法?
cat.run()//是一个普通方法,由主线程直接调用run()方法,无法实现线程
cat.start()//启动线程,最终会执行cat.run()方法
start()方法源码解读:
run()方法何时执行取决于CPU
举例(实现Runnable接口实现线程)
说明
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
- java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
深入内容到之后再讲,先介绍怎么实现
package Threaduse;
public class Thread02 {
public static void main(String[] args) {
//底层使用了代理模式(一种设计模式)
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable {
@Override
public void run() {
//设置自己想设计的逻辑
int num=0;
while(true){
System.out.println("熊猫烧香"+(++num)+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num==8){
break;
}
}
}
}
继承Thread和Runnable接口对比
售票系统(出现了超票问题,该怎么解决)
package Threaduse;
public class SellTicketSystem {
public static void main(String[] args) {
//使用Thread实现线程
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
//使用Runnable接口
SellTicket02 sellTicket02 = new SellTicket02();
Thread thread = new Thread(sellTicket02);
Thread thread2 = new Thread(sellTicket02);
Thread thread3 = new Thread(sellTicket02);
thread.start();
thread2.start();
thread3.start();
}
}
class SellTicket01 extends Thread {
private static int nums=100;
@Override
public void run() {
while(true){
if(nums<=0){
System.out.println("票卖完了");
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票口"+Thread.currentThread().getName()+"卖出一张票"+"剩余票数"+(nums--));
}
}
}
class SellTicket02 implements Runnable {
private int nums=100;//这里不用定义static,因为始终使用的是同一个SellTicket02生成的对象
@Override
public void run() {
while(true){
if(nums<=0){
System.out.println("票卖完了");
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票口"+Thread.currentThread().getName()+"卖出一张票"+"剩余票数"+(nums--));
}
}
}
线程终止
●基本说明
1.当线程完成任务后,会自动退出。
2.还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
package Threaduse;
public class StopThread {
public static void main(String[] args) throws InterruptedException {
//需求:启动一个线程t,要求在main线程中去停止线程t,请编程实现.
如果希望main线程去控制t1 线程的终止,必须可以修改 loop
// 让t1 退出run方法,从而终止t1线程->通知方式
让主线程休眠10秒,再通知和线程退出
T t = new T();
t.start();
Thread.sleep(10000);
t.setLoop(false);
}
}
class T extends Thread {
private boolean loop=true;
@Override
public void run() {
while(loop){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程在运行");
}
}
public void setLoop(boolean b){
loop=b;
}
}
线程的常用方法
第一组
1.setName /设置线程名称,使之与参数name相同
2.getName//返回该线程的名称
3. start//使该线程开始执行;Java虚拟机底层调用该线程的start()方法
4. run//调用线程对象run方法;
5. setPriority //更改线程的优先级
6.getPriority//获取线程的优先级
7.sleep//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
8. interrupt //中断线程,但没有结束线程,所以一般用于中断休眠的线程
线程的优先级范围:
public static final int MIN_PRIORITY=1;
public static final int NORM_PRIORITY=5;
public static final int MAX_PRIORITY=10;
package Threaduse;
import javax.crypto.spec.PSource;
public class ThreadMethod {
public static void main(String[] args) throws InterruptedException {
//利用主线程设置子线程名,并中断正在休眠的线程
Z z = new Z();
z.setName("张李浩");//设置线程名
z.setPriority(Thread.MIN_PRIORITY);
z.start();
//主线程在输出5个i后,interrupt子线程
for(int i=0;i<5;i++){
Thread.sleep(1000);
System.out.println(i);
}
z.interrupt();
}
}
class Z extends Thread {
@Override
public void run() {
//先输出,休眠50秒,interrupt,然后再输出
while(true){
int nums=0;
for(int i=0;i<10;i++){
System.out.println("线程"+Thread.currentThread().getName()+"正在运行"+(++nums));
}
try {
System.out.println("线程正在休眠");
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println("线程被打断了");
}
}
}
}
第二组
yield()线程礼让,假如CPU充足则不一定成功
join()线程插队
例子
package Threaduse;
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
person person = new person();
Thread thread = new Thread(person);
//主线程每个一秒,输出hi,共10次
for(int i=1;i<=10;i++){
Thread.sleep(1000);
System.out.println("hi"+i);
if(i==5){
//启动子线程,让其执行完再运行主线程
thread.start();
thread.join();
}
}
System.out.println("主线程结束");
}
}
class person implements Runnable {
@Override
public void run() {
while(true){
for(int i=1;i<=10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello"+i);
}
System.out.println("子线程结束");
break;
}
}
}
用户线程和守护线程
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程:垃圾回收机制
如何将线程设置为守护线程
守护线程当所以用户现场结束,守护线程也自动结束
mydaemoThread.setDaemo(true);//设为守护线程
package com.hspedu.method;
/**
* @author 韩顺平
* @version 1.0
*/
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//,只需将子线程设为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for( int i = 1; i <= 10; i++) {//main线程
System.out.println("宝强在辛苦的工作...");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
public void run() {
for (; ; ) {//无限循环
try {
Thread.sleep(1000);//休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
}
}
}
线程的生命周期
细分7种,总的来说6种,都对
线程状态。线程可以处于以下6种状态之一:
NEW
尚未的线程处于此状态。
RUNNABLE(RUNNABLE状态可以细分为Ready和Running两种状态------>七种)
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED
已退出的线程处于此状态。
Synchronized(进程同步机制)
1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,
保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,
其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
同步具体方法-Synchronized
1.同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码;
}
2. synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
使用Synchronized解决售票问题
代码块上加锁
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;//控制run方法变量
Object object=new Object();
//同步方法(静态的)的锁为当前类本身
//老韩解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中,实现一个同步代码块.
/*
synchronized (SellTicket03.class) {
System.out.println("m2");
}
*/
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}
//老韩说明
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
public void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
synchronized (/*this*/ object) {
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));//1 - 0 - -1 - -2
}
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一共同步方法
}
}
}
方法上加锁
class SellTicket01 extends Thread {
private static int nums = 1000;
private boolean loop=true;
public synchronized void sell() {
if (nums <= 0) {
System.out.println("票卖完了");
loop=false;
return;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票口" + Thread.currentThread().getName() + "卖出一张票" + "剩余票数" + (nums--));
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
互斥锁
基本介绍
1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法((非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的)的锁为当前类本身。
注意事项
1.同步方法如果没有使用static修饰:默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class(static修饰没法用this)
3.实现的落地步骤:
·需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!
错误示范(锁对象不同)
线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
释放锁和不释放锁情况
本章作业
package Threaduse.HomeWork;
import java.util.Locale;
import java.util.Scanner;
//(1)在main方法中启动两个线程
//(2)第1个线程循环随机打印100以内的整数
//(3)直到第2个线程从键盘读取了“Q”命令。
public class Homework01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);
a.start();
b.start();
}
}
class A extends Thread {
private boolean loop=true;
public void setLoop(boolean b){
loop=b;
}
@Override
public void run() {
//随机打印100以内的整数
while(loop){
System.out.println((int)(Math.random()*100+1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程A结束");
}
}
class B extends Thread {
private A a;
Scanner scanner = new Scanner(System.in);
public B(A a){
this.a=a;
}
@Override
public void run() {
//接收键盘输入,当接受到"Q"后将loop=false;打断线程1
while(true){
char s=scanner.next().charAt(0);
if(s=='Q') {
a.setLoop(false);
System.out.println("线程B结束");
break;
}
}
}
}
package Threaduse.HomeWork;
public class Homework02 {
public static void main(String[] args) {
user user1 = new user();
Thread thread1= new Thread(user1);
Thread thread2 = new Thread(user1);
thread1.setName("用户1");
thread2.setName("用户2");
thread1.start();
thread2.start();
}
}
class user implements Runnable{
private int money=10000;
public void put(){
while (true){
synchronized (this) {
//不断从卡里取钱,每次取1000
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();
}
}
}
@Override
public void run() {
put();
}
}