进程:一个正在进行的程序
线程:一个执行路径
注意:
1,一个进程中自带一个线程,称为主线程
2,一个进程中可以有多个线程,称为多线程
3,除主线程外,都是子线程
4,线程分为守护线程(后台线程)与前台线程
5,如果一个进程有前台线程存活,那么程序就不会被销毁.
6,如果一个进程中所有前台线程都结束,那么进程也将被销毁,此时不管是否有守护线程正在执行
7,宏观角度来看,线程是同时进行的.从微观角度来说,多个线程交替执行
同步:
同时只能有一个线程对其进行操作
线程安全的
异步
同时可以有多个线程对其进行操作
线程不安全的
线程的使用
步骤:
1,创建
2,使用
创建:
方案1:创建Thread的子类对象
方式1:
1,创建一个类
2,使其继承与Thread
3,重写run方法
4,创建该类对象,线程对象
方式2:
Thread 线程对象 = new Thread(){
重写run方法
}
方式2:将Thread与Runnable分开创建,然后合并使用
方式1:
1,创建一个类
2,实现Runnable接口
3,重写run方法
4,创建该类对象(线程任务对象)
5,创建Thread对象,传入线程任务对象
方式2:
Thread 线程对象 = new Thread(new Runnable(){
重写run方法
});
package com.qf.demo02;
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
//方式1
MyThread myThread01 = new MyThread();
//方式2
Thread thread = new Thread() {
public void run() {
}
};
//方式3
MyRunnable myRunnable = new MyRunnable();
Thread thread2 = new Thread(myRunnable);
//方式4
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
};
Thread thread3 = new Thread(runnable);
}
}
//方式1
class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
}
}
//方式3
class MyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
使用
1,启动线程
void start();
1,开辟新的执行路径(线程)
2,此时该线程才会与其他线程抢夺CPU执行权
注意:线程启动后会执行run方法中的代码
2,线程的休眠
static void sleep(毫秒)
注意:
1,线程休眠期间不会抢夺CPU执行权
2,使用类名调用方法,在那个线程中调用,就让那个线程休眠
练习:在子线程中进行10秒倒计时
package com.qf.demo02;
public class Demo02 {
public static void main(String[] args) {
Thread thread = new Thread() {
public void run() {
for (int i = 10; i > 0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
};
thread.start();
}
}
3,获取当前线程
static Thread currentThread();
4,线程名称
获取线程名称
String getName()
设置线程名称
1,使用setName方法
线程对象.setName(线程名称);
2,在创建线程对象时传入线程名称
new Thread(线程任务,线程名称);
注意:在线程启动前设置
5,线程的优先级
概念:线程抢夺到CPU执行权的概率(不能做到百分百)
注意:
1,取值范围1~10
2,在线程启动前设置
3,默认优先级为5
设置线程优先级
void setPriority(int newPriority)
获取优先级
int getPriority()
6,线程的礼让
概念:当线程获取到了CPU执行权后,又释放CPU执行权,然后从新抢夺
方法:
static void yield()
7,线程的合并
概念:将多个线程合并为一个线程
方法:
void join()
注意:
在线程2中使用线程1对象调用该方法,表示将线程1未完成的代码合并到线程2中,此时只要执行完线程1中剩余的代码,才会继续执行线程2剩余的代码
一般不会自己合并自己
8,守护线程
设置线程为守护线程:
void setDaemon(boolean on)
true:守护线程
flase:前台线程,默认
注意:当一个进程中所有前台线程执行完毕后,进程将会结束
线程的安全问题
原因:因为多个线程操作同一个数据导致的
解决方法:保证同时只能有一个线程操作
Java如何保证通知只能使用一个线程操作:
使用synchronized的关键字
使用:
同步代码块
语法:
synchronized(锁对象){
要同步的代码
}
注意:
1,保证多个线程使用的锁对象是同一个对象
2,所有的类的对象都可以作为锁对象
同步方法
语法:使用synchronized修饰方法
访问权限修饰符 synchronized 返回值类型 方法名(形参列表){
方法体
}
注意:
谁调用同步方法,谁就是锁对象
同步静态方法
语法:使用synchronized修饰方法
访问权限修饰符 synchronized static 返回值类型 方法名(形参列表){
方法体
}
注意:
锁对象是同步静态方法所属的类的类对象
练习:
在生活中同时做多个事情,那么在代码就应该用线程描述
案例:
购买火车票
假设:
四个窗口:四个线程
线程任务:买票,票没了就不买了
package com.qf.demo03;
public class MyThread implements Runnable{
private static int num = 100;
@Override
public void run() {
while (num > 0) {
suo();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void suo() {
String name = Thread.currentThread().getName();
if (num <= 0) {
return;
}
num--;
System.out.println(name+"卖出了一张票,剩余票数为"+num);
}
}
package com.qf.demo03;
public class Demo05 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread01 = new Thread(myThread,"窗口一");
Thread thread02 = new Thread(myThread,"窗口二");
Thread thread03 = new Thread(myThread,"窗口三");
Thread thread04 = new Thread(myThread,"窗口四");
thread01.start();
thread02.start();
thread03.start();
thread04.start();
}
}
线程间的通讯
注意:
线程间通讯的方法是Object提供的方法
方法:
休眠
wait():无限期休眠
wait(timeout):有限期休眠
timeout:休眠时间,单位毫秒
sleep与wait的区别:
sleep:
1,是Thread类提供的方法
2,是静态方法
3,休眠期间不会释放锁资源
4,在线程中都可以使用
5,使用线程对或Thread类名调用
wait:
1,是Object类提供的方法
2,不是静态方法
3,休眠期间会释放锁资源
4,只能在同步代码中使用
5,只能使用当前所在的同步的锁对象调用
唤醒
notify():随机唤醒一个正在休眠的线程(参考注意点3)
notifyAll():唤醒所有正在休眠的线程(参考注意点3)
注意:
1,只能在同步代码中使用
2,只能使用当前所在的同步的锁对象调用
3,只能唤醒使用该对象使其休眠的线程
a锁锁了三线程a1,a2,a3
b锁锁了二线程b1,b2
a.notifyAll();只能唤醒a1,a2,a3
a.notify():随机在a1,a2,a3唤醒一个
案例:
启动两个线程
一个线程打印1~52
一个线程打印A~Z
要求:打印结果为12A34B56C78D...5152Z
package com.qf.demo04;
/*
* 启动两个线程
一个线程打印1~52
一个线程打印A~Z
要求:打印结果为12A34B56C78D...5152Z
*/
public class Demo04 {
public static void main(String[] args) {
String tag = "...";
Thread thread01 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 53; i++) {
synchronized (tag) {
System.out.print(i);
if (i == 52) {
try {
tag.wait();//当i=52时,数字输出完毕,数字部分睡眠
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tag.notify(); //唤醒字母部分
}else if (i % 2 == 0) {
tag.notify(); //如果数字为偶数代表两个数输入完毕,唤醒字母部分
try {
tag.wait(); //唤醒字母部分后睡眠数字部分
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
});
thread01.start();
Thread thread02 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 65; i < 91; i++) {
synchronized (tag) {
try {
tag.wait(100);//刚开始需要睡眠,保证字母后输出
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print((char)i);
tag.notify(); //输出一个字母唤醒数字
if (i == 90) {
try {
tag.wait(); //如果i=90代表字母输出结束,睡眠字母部分
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
});
thread02.start();
}
}