java中的线程
1.线程入门简介:
线程相关概念:
1. 程序:实现功能的代码(静态)
2.
(1)..进程:是指正在运行中的程序!(动态),比如:我们使用的qq,打开qq后就相当于启动了一个进程,
这时操作系统就会为该进程分配空间。
eg:当我们启动迅雷,又启动了一个程序,该操作系统将为迅雷分配新的内存空间;
(2).进程是程序的(一次)执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生,存在,消亡
(当我们打开qq界面:进程产生,关闭qq:进程消亡,释放空间);
只要进程产生:就会吃掉一定的内存,cpu;
进程关闭(消亡):就会释放
3.线程:
(1.).线程是由进程创建的,是进程的一个实体;
(2.)一个进程可以有多个线程:比如在迅雷同时下载多个文件:迅雷:进程;几个下载任务:就是几个线程
线程其它概念:
4.线程:
(1.)单线程:同一个时刻,只允许执行一个线程;
(2.)多线程:同一个时刻,可以执行多个线程;
eg:一个qq进程,可以同时打开多个聊天窗口,一个迅雷,可以同时下载多个文件
5.并发:同一时刻,多个任务!交替!执行,造成一种“貌似同时”的错觉,简单的说,单核CPU实现的多个任务就是并发;
但其实:在具体的时刻只会执行一个,反复切换
CPU--->qq
迅雷
6.并行:同一时刻,多个任务同时执行。多核CPU可以实现;
CPU--->qq
CPU--->迅雷
eg:qq和迅雷同时执行
并行也有可能出现并发!:比如两个CPU使用完的情况下,又来一个任务,则其中一个CPU要在两个任务间反复切换;
2.线程的基本使用
public class Thread01 {
/*
!!!!!!!!!!!!!!!!!
线程的基本使用:!!!!!!!!!!!!!!!!!!!!!!!!!!!Q
1.线程使用的两种方式
(1.)a:继承线程Thread类,b:重写run方法
(2.)实现Runnable接口,重写run方法
Runnable接口
|
|
Thread
2.(1.)
多线称机制说明(!!!)
*1:当点击run运行这个程序时:相当于开启了一个进程!
*/
//下面我们演示1.(1.)a:继承Thread,b:重写run方法!!!!!!!!!!!!!
public static void main(String[] args) throws InterruptedException {//*2:进入main方法:相当于开启main线程
//创建对象::可以当作线程使用
Cat cat=new Cat();
cat.start();//启动线程-->最终会执行cat的run方法:相当于创建启动一个线程!!!
//真正实现线程的方法:start0();
// (如:在迅雷开启一个下载任务!!)
//cat.run();注意:这里run()方法就是普通的方法,如果调用它,他会先把run方法执行完毕后才会执行下面的代码
//所以它没有实现启动多线程(同一时刻可以执行多个线程)--->即会发生阻塞
//*3.:执行到start()方法:main线程就会开启一个子线程:Thread01,
//且子线程并不会阻塞main线程,main线程会继续执行;!!!!!!!!!!
//即main线程和子线程:Thread0会交替执行!!!!!!!
//eg:下面的for演示:
System.out.println("主线程会继续执行,不会阻塞,主线程名字:"+Thread.currentThread().getName());
for (int i=0;i<10;i++){
System.out.println("主线程i="+i);
Thread.sleep(1000);//我们也可以让主线程休眠1s,有异常:可以catch或throws;
}
//*4.在多线程中并不一定主方法结束,进程就结束了,如果还有子线程,还会运行
//只有所有的线程都结束了,进程才会结束
}
}
class Cat extends Thread {////演示:(1.)a:继承线程类Thread
//当一个类继承了Thread类,该类就可以当作线程使用--->:Cat就可以当作线程使用了
public void run() {//b:重写run方法:写上自己的业务逻辑(run方法)
int times=0;
while (true) {
//该线程每隔1s。在控制台输出:”喵喵,我是kk“,
System.out.println("喵喵,我是kk"+(++times)+"线程的名字"+ java.lang.Thread.currentThread().getName());
//休眠1s:使用sleep();方法:它是ms单位:Thread.sleep(1000);
try {
java.lang.Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (times==8){//当times=8时就退出while循环--->也就退出了线程;!!!
break;
}
}
}
}
3.多线程使用案例:
线程使用案例3.:多个子线程
多线程的执行:原来两个案例我们都启动了1个子线程,现在我们启动2个子线程!
案例:编写一个程序,创建两个线程,一个线程每隔1s输出:"hello,world";,输出10次退出
,另一个每隔1s输出“hi”,输出5次退出(使用方式2:实现接口)
分析:1.创建两个类分别实现R接口!!
2.在main线程启动两个子线程!!!
*/
public static void main(String[] args) {//1.mian线程
//最后启动两个子线程
T1 t1=new T1();//创建实现R接口类:T1的对象:
Thread thread=new Thread(t1);//代理实现start方法,因为T1没有
thread.start();//Thread-0线程;
T2 t2=new T2();//创建实现R接口类:T2的对象:
Thread thread1=new Thread(t2);//代理实现start方法,因为T1没有
thread1.start();//Thread-1线程
//综上:上面的两个子线程是交替进行的---->即多线程!!!!
}
}
class T1 implements Runnable{//类1:实现R接口-->:启动线程
int count=0;
//2.子线程1:每隔1s输出:"hello,world";,输出10次退出
public void run(){
while(true){
System.out.println("hello,word"+(++count)+"它的线程是:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(count==10){
break;
}
}
}
}
class T2 implements Runnable{//类2:实现R接口
//3.子线程2:每隔1s输出:"hi";,输出5次退出
int num=0;
public void run(){
while(true){
System.out.println("hi"+(++num)+"它的线程是:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(num==5){
break;
}
}
}
}
4.练习
(1)售票系统初级方法:
多线程练习:模拟售票系统
eg:使用多线程模拟三个窗口!同时!售票100张;
分别使用两种方式实现
*/
public static void main(String[] args) {
//因为模拟三个窗口:所以创建三个对象
SellTickets sellTickets=new SellTickets();
SellTickets sellTickets1=new SellTickets();
SellTickets sellTickets2=new SellTickets();
sellTickets.start();//启动main子线程1
sellTickets1.start();//启动main子线程2
sellTickets2.start();//启动main子线程3
//三个子线程同时执行----->:即多线程!!!!!!!!!!!!
/*
窗口Thread-2售出1张票还剩99张票
窗口Thread-0售出1张票还剩99张票 //三个子线程同时执行----->:即多线程!!!!!!!!!!!!
窗口Thread-1售出1张票还剩99张票
*/
// 解析:因为是同时执行:当只有1/2张时三个子线程同时进入剩余的票数:因为票数<3:所以就会超卖!!!!
//方式2:实现接口方式:也会超卖2张,和上面的原因一样
//(当只剩下1张后,第一个线程会先卖出,然后休眠,现在本身已经没票了,
// 但是3个线程是同时进入的(同时进入if判断的!!!),第一个线程无法阻止其它两个子线程,所以会超卖2张)
}
}
//方式1:继承Thread
class SellTickets extends Thread{//继承Thread
//方式2:实现R接口
//class SellTickets implements Runnable{
private static int ticketNum=100;//使用static:让三个线程共享!!!!!!!!!!
public void run(){//重写run方法
while(true){
if(ticketNum<=0){
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);//休眠50ms
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//如果有票:就卖
System.out.println("窗口"+Thread.currentThread().getName()+"售出1张票"
+"还剩"+(--ticketNum)+"张票");
}
}
}
(2)改进1:
线程终止:
使用背景:eg上个售票系统:我们希望票售完后会自动停止,避免出现多售的情况;
一.基本说明:
1.当线程完成任务后,会自动退出
2.还可以通过使用变量来控制run方法退出的方式停止线程,即-->通知方式
总结:如果我们希望A线程去通知B线程退出,只需要在A线程中可以控制B线程的变量即可:可以在B中使用set方法
然后在A线程中调用方法实现
*/
public static void main(String[] args) throws InterruptedException {
A a=new A();//创建线程对象
a.start();//启动线程
//如果希望在main线程控制A线程的终止:就必须可以修改loop变量!!!!!:--->:使用set方法
//让A线程退出run方法,从而中止A线程-->:通知方式
System.out.println("休息10s.....");//让main线程休息10s,但不会影响下面的A线程的执行!!!
Thread.sleep(10*1000);
a.setLoop(false);//传入false//直接将循环条件改变
//实现了在main线程中控制另一个线程:A!!!!!!!!!!
}
}
class A extends Thread{//继承
private boolean loop=true;
int count=0;
public void run(){
while(loop){//放入循环中:loop=true,会一直执行
System.out.println(++count);
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void setLoop(boolean loop){//!!!通过set方法在main中实现修改loop!!!
this.loop=loop;
}
}
(3)改进
5.守护线程:
用户线程和守护线程:
1.用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
2.守护线程(DaemonThread):一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束;
(不用使用通知才结束,由线程机制觉得,main结束后,子线程发现已经不需要守护了)
3.常见的守护线程:垃圾回收机制;
守护线程使用背景:比如我们有一个子线程和main线程,其中子线程是无限循环,当main线程执行完毕后,子线程并不会停止
但是如果现在我们已经不需要子线程了--->:因此我们可以使用守护线程:来实现当main线程结束之后,子线程可以自动退出
演示
*/
public static void main(String[] args) throws InterruptedException {
myDaemon m=new myDaemon();//创建子线程:m
//!!!如果我们希望main线程结束后,子线程myDaemon(无限循环)也能自动结束;!!!
//只需将线程设置为守护线程即可:--->:即使用m.setDaemon(true);方法!!!
m.setDaemon(true);
m.start();//启动线程!!!
//注意:子线程的创建必须放到上面,否则他会先执行完main线程的for循环再创建子线程
//且(因为这里是守护线程,所以执行完main线程中的for后就结束了,自线程相当于没有创建和执行)
//main线程:
for(int i=1;i<=10;i++){
Thread.sleep(1000);//main线程也休眠1s
System.out.println("你干嘛啊啊啊啊..."+i);
}
}
}
class myDaemon extends Thread{//子线程:myDaemon
public void run() {
for (; ; ) {
try {
Thread.sleep(50);//休眠1s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("坤坤跳舞...");
}
}
}
6.线程同步机制
线程同步机制:
1.”同步“:在多线程中,一些敏感的数据不允许有多个线程同时访问,此时就使用同步访问技术,
保证数据在任何时刻,最多只有一个线程访问,以保证数据的完整性。
2.线程同步 解释:比如在同一时刻,当有一个线程在对内存进行操作时,其它线程都不可以对这个内存的·地址进行操作
直到该线程完成操作,其它线程才可以进入;
即
3.同步具体方法:---*>:Synchronized
(1.)同步代码块:
synchronized(对象){//得到对象的锁,才能操作同步数据!!!!!
//需要被同步的代码
}
(2.)synchronized还可以放在方法声明中,表示整个方法 为 同步方法
public synchronized void m(String name){
//需要被同步的代码
}
!!!!使用背景:eg买票存在超卖,与三个窗口同时卖1张票的的问题:
防止三个线程同时进入买票系统----->:这样卖出的票有顺序且不会重复卖与超卖
*/
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
MM sellTickets=new MM();
MM sellTickets1=new MM();
MM sellTickets2=new MM();
sellTickets.start();//启动main子线程1
sellTickets1.start();//启动main子线程2
sellTickets2.start();//启动main子线程3
}
}
//解决买票溢出问题---->:使用synchronized实现线程同步!!!!!!!!
class MM extends Thread{
Boolean loop=true;//增加一个控制变量---->:通知子线程结束!!!!!!!!!
//类似于:通知退出!!!!!!!
private static int ticketNum=100;//使用static:让三个线程共享!!!!!!!!!!
//说明:a:public synchronized void sell(){};--->就是一个同步方法(s在方法中)
// b:这时互斥锁在当前对象(this
// c:也可以在代码块中写synchronized;---->:就是一个同步代码块
// eg: synchronized(this){}:互斥锁还在当前对象,{}将子线程全部包括即可!!!
public synchronized void sell(){//锁加在当前对象上!!
// 有多个线程进来的时候,把他们全都堵在这,只有1个锁,
// 一个线程(拿锁)执行完毕后,再让另一个线程进入
// 使用synchronized同步方法:在同一时刻,只能有一个线程来执行sell方法!!!!
// 给方法上锁,只有拿到这个锁才能执行sell方法!!!
//如果没有s,就会超买!
if(ticketNum<=0){//所以就不会三个同时进入判断!!!
System.out.println("售票结束");
loop=false;//loop设置为false--->:退出循环!!!!
return ;
}
try {
Thread.sleep(1000);//休眠50ms
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//如果有票:就卖
System.out.println("窗口"+Thread.currentThread().getName()+"售出1张票"
+"还剩"+(--ticketNum)+"张票");
}
public void run(){//!!!!
while(loop){
sell();//调用sell方法
}
}
}
7.练习
(1.)
import java.util.Scanner;
public class Thread12Exe {
/*
练习:
在main方法中启动2个线程
第一个线程循环随机打印100以内的int
直到第二个线程从键盘上读取了”Q“命令
""""""""""""使用守护机制练习""""""""""""""
分析:可知:1.要实现在第二个线程中控制第一个线程--->:通知方式
在BBB线程必须持有AAA线程的对象:以通知的方式终止BBB线程的run方法:!!!退出了run方法也就退出了线程
---》:通过修改变量
2.先创建两个线程对应的类:AAA,BBB
*/
public static void main(String[] args) {
Scanner myScanner=new Scanner(System.in);
AAA aaa=new AAA();
aaa.start();//启动AAA线程;
BBB bbb=new BBB(aaa);
bbb.start();//启动BBB线程;
//注意://!!!将线程AAA的对象传给线程BBB,才能够在BBB线程中控制AAA线程!!!
}
}
class AAA extends Thread{//创建子线程1:AAA类:循环输出内容1-100;
private boolean loop=true;
public void run(){
while(loop){
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("随机数:"+(int )(Math.random()*100+1));//输出1-100int
}
}
public void setLoop(Boolean loop){//提供set方法:让BBB线程类调用来通知AAA线程退出!!
this.loop=loop;
}
}
class BBB extends Thread{//子线程2:BBB类:当第二个线程从键盘上读取了”Q“命令时,
// 通知AAA线程结束(即退出run方法)
Scanner myScanner=new Scanner(System.in);//创建Scanner对象
private AAA aaa;//在BBB线程必须持有AAA线程的对象:!!!
public BBB(AAA aaa) {//构造器
this.aaa = aaa;
}
public void run(){
while(true){
//接收到用户的输入
while(true) {
System.out.println("输入key,输入Q时退出:");
char key = myScanner.next().toUpperCase().charAt(0);//输入
if(key=='Q'){//当输入“Q”时,以通知的方式结束AAA线程!!!!!!
//只要能够修改AAA的loop变量即可!
aaa.setLoop(false);//调用set方法修改loop--->:终止AAA线程!!!!!
System.out.println("BBB线程也退出");
break;
}
}
}
}
}
(2.)
public class Thread1202Exe {
/*
练习2:
有两个用户分别从一张卡上取钱(总额10000)
每次都取出1000,当余额不足时,就不能取款了
不能出现超取的情况
分析:1.不能超取:--》应该使用线程同步问题!!!
(解决敏感数据--在同一个时间只允许有一个线程去操作数据,防止超取,来保证数据的统一性)
2.因此可以通过放一把锁来限制子线程,只有获取锁的线程才可以进入run方法(代码)去
操作数据,其它线程都会被阻塞
!!!总节:所有的线程来了以后,不能让他们立即进入代码执行数据,而是放一个锁先堵住他们。
只有获取锁的线程才可以进入run方法(代码)去操作数据,其它线程都会被阻塞等待
1.创建两个窗口:--即两个子线程分别操作数据money
2.要操作的敏感数据:money;
*/
public static void main(String[] args) {
TT1 tt=new TT1();
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//创建1个线程类TT1对象,然后将同一个tt对象放入两个线程中----,所以三两个线程操作的是同一个对象
//所以就实现了,处理的是同一个空间下的数据
Thread thread=new Thread(tt);//创建两个线程
Thread thread1=new Thread(tt);
thread.start();
thread1.start();
//注意:因为要对同一个代码:即钱操作,所以写一个类即可!!!!!!!!!!
//创建两个线程:都对下面run代码进行操作:因为面对的是同一个10000块
}
}
class TT1 implements Runnable{//子线程1:TT1类
private int money=10000;//money
public void run(){
while(true){
/*
两个线程同时start;
1.这里s实现了线程同步。
2.当多个线程执行到这时:就会去争夺 this对象锁(非公平锁)
3.哪个线程争夺到了(获取)this对象锁,就执行s代码块!!!!!!(执行完后会释放this对象锁,方便给下一次争夺)
4.如果争夺不到this对象锁的就阻塞在这里,准备下一次争夺;
给关键数据加上锁:-->:相当于哪个对象抢到了这个锁,this就是指的哪个对象;--->:
这样就实现了,同一时刻只允许1个线程执行money。
*///重点:就是将要争夺的代码放上锁!!!!!!!!!!!
synchronized (this) {
if (money < 1000) {//先判断:如果<1000:就退出
System.out.println("余额不足");
break;
}
//如果余额充足,就取钱
money -= 1000;
System.out.println(Thread.currentThread().getName() + "取了1000¥");
System.out.println("当前余额还剩:" + money + "¥");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
8.线程常用方法
(1.)方法1
1.yield(静态方法):Thread.yield();
线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定成功。
比如:有两个线程t1,t2,如果t1使用yield()方法,即相当于告诉编译器先执行t2线程,但礼让时间即概率不一定成功
如果资源有限就可能不会成功,资源充足可能成功
2.join:
线程插队。插队的线程一旦成功,则肯定执行完插入的线程所有的任务。
eg:在t1线程中调用了t2方法(调用对方的join方法:因为是要对方插过来)
意味:比如现在t1和t2线程正在进行中,然后再t1线程中的某个位置调用了t2.join()方法
则这时相当于t1主动放弃CPU,让它将t2全部执行完毕后,再回头执行t1线程,一定会成功;
案例:在main线程中创建1个子线程,每隔1s输出hello,输出20次。主线程每隔1s,输出hi,输出20次;
要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续执行
*/
public static void main(String[] args) throws InterruptedException {
TT tt=new TT();//创建子线程
tt.start();//启动子线程
//主线程
for(int i=1;i<=20;i++){
Thread.sleep(1000);
System.out.println("hi"+i);
if(i==5){//因为我要让tt子线程先执行完毕再执行main线程,!!!!!1
System.out.println("main线程将CPU让给tt线程,,即让tt线程插队");
tt.join();//所以就直接让tt子线程插队
}
}
//因为主线程和子线程的休眠时间一样都是:1s,所以会交替执行!!!!!!!!!!!!!
}
}
class TT extends Thread{
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello"+i);
}
}
}
(2.)方法2
public class Thread05Methods {
/*
线程常用方法:
一:常用方法第一组:
1.setName
二:细节:
1.start底层会创建新的线程。 而调用run,run就是一个简单的方法调用,不会启动新的线程
2.线程优先级的范围:
MAX_PRIORITY:10
MIN....:1
NORM....:5
3.interrupt:中断线程(!不是终止),但并没有真正的结束线程。所以一般用于中断正在休眠的线程
eg:t线程正在休眠,我们调用:t.interrupt();-->:可以终止休眠!,使线程继续执行
即中断休眠:使它继续工作!
4.sleep:使当前线程休眠
*/
public static void main(String[] args) throws InterruptedException {
T t =new T();//创建线程对象
t.setName("kunkun");//方法1;setName:设置线程名字
t.setPriority(Thread.MAX_PRIORITY);//设置线程优先级
t.start();//启动线程;
//现在要求:当main线程输出5个hi后,就中断子线程的休眠;
//main线程:
for (int i=0;i<5;i++){
Thread.sleep(1000);
System.out.println("hi..."+i);
}
//执行完for:打印5个hi
t.interrupt();//中断T线程休眠,继续执行程序!!!!!!
System.out.println(t.getName()+" 优先级"+t.getPriority());//获取线程优先级
}
}
class T extends Thread{//继承线程--》:自定义线程类
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName():返回线程名称
System.out.println("线程名字:" + Thread.currentThread().getName() + "吃包子" + i);
}
try {
System.out.println(Thread.currentThread().getName()+"休眠中........");
Thread.sleep(20000);//休眠20s
} catch (InterruptedException e) {
//当线程执行到interrupt方法时:就会catch一个异常,可以加入自己的业务逻辑
//interruptedException e:是捕获到的一个中断异常(不是终止!!!)
System.out.println(Thread.currentThread().getName() + "被interrupt");
}
}
}
}