概念性东西
//对着视频练习打字
程序:完成特定任务、用某种语言编写的一组指令的集合,即指有一段静态的代码,静态对象
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程;有它自身的产生、存在和消亡的过程。——生命周期
线程:进程可进一步细化为线程,是一个由程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的;
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间的通信更便捷、高效。但多个线程操作共享的系统资源可能带来安全隐患。
多线程创造
1、创造一个继承于Thread
2、重写Thread类的run()
3、创建Thread类的子类
4、通过此对象调用start()
package com.atguigu.java;
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
//不能直接调用run()方法启动线程
//t1.run();
//再启动一个线程,遍历100以内的偶数,不可以还让已经start()的线程去执行
//需重新创建一个线程
MyThread t2 =new MyThread();
t2.start();
for(int i = 0;i < 100;i++){
if(i %2 == 0){
System.out.println("_____main_____");
}
}
System.out.println("hello");
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i =0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
}
结果(节选):
例题
两个线程,一个输出100内偶数,一个奇数
package com.atguigu.java;
public class ThreadTest {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
Mythread2 t2 = new Mythread2();
t1.start();
t2.start();
//创造Thread类的匿名子类对象
new Thread(){
@Override
public void run() {
System.out.println("sdf");
}
}.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i =0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
}
class MyThread1 extends MyThread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i%2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class Mythread2 extends MyThread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i%2 == 1){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
Thread常用方法
start()
run()
currentThread():静态方法,将创建的线程要执行的操作声明在此方法中
getName():获取当前线程的名字
setName():设置当前线程的名字
yield():释放当前cpu的执行权
join():在线程a中调用线程b的join()方法,此时线程a就进入堵塞状态,直到线程b完全执行完后,线程a才结束堵塞状态
sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒,在指定毫秒内,线程是堵塞的
isAlive():判断当前线程释放存活
package com.atguigu.java1;
/*测试Thread常用方法
start()
run()
currentThread():静态方法,将创建的线程要执行的操作声明在此方法中
getName():获取当前线程的名字
setName():设置当前线程的名字*/
//yield():释放当前cpu的执行权
//join():在线程a中调用线程b的join()方法,此时线程a就进入堵塞状态,知道线程b完全执行完后,线程a才结束堵塞状态
//sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒,在指定毫秒内,线程是堵塞的
//isAlive():判断当前线程释放存活
public class ThreadDemo {
public static void main(String[] args) {
MyException thread1 = new MyException();
int num = 100;
thread1.setName("线程一");//给线程命名
thread1.start();
Thread.currentThread().setName("主线程");
for(int i = 10;i < num;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
if(i == 50){
try {
thread1.join();//当i到50,占用线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(thread1.isAlive());
}
}
class MyException extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
if(i % 2 == 0){
try {
sleep(1);//停止线程1毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
结果
在主线程运行到50时,线程1插队,并且插队后完成了其工作,主线程才继续
请添加图片描述
线程结束后,thread1.isAlive()结果为false
线程的调度
package com.atguigu.java1;
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
thread1.setPriority(10);
thread2.setPriority(Thread.NORM_PRIORITY);
thread1.start();
thread2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 200;i++){
System.out.println(this.getName() + ":" + i);
}
}
public MyThread(String name){
this.setName(name);
}
}
设置线程1的优先级为10,线程2的优先级为5,执行打印200个数,可以看到线程1先执行完
多线程创建的另一种方法
package com.atguigu.java2;
public class windowTest {
public static void main(String[] args) {
SellThread s = new SellThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SellThread implements Runnable{
private static int ticket = 100;
@Override
//重写run方法
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
结果:
注意
如果启动一个线程,必须调用start(),不能调用run()的方式启动线程
如果再启动一个线程,必须创建一个Thread子类对象,调用此对象的start()
两种方法对比:
开发中,优先选择实现Runable接口的方法
原因:1、实现的方式没类的单继承性的局限性
2、实现的方式更适合来处理多个线程共享数据的情况
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中;
Interrupt方法
用于打断某个线程
package day21Test;
public class day21TestMian {
public static void main(String[] args) {
new Classroom();
}
}
class Classroom implements Runnable{
private Thread teacher;
private Thread student;
public Classroom(){
teacher = new Thread(this,"大老师");
student = new Thread(this,"小学生");
student.setPriority(Thread.MAX_PRIORITY);
student.start();
teacher.start();
}
@Override
public void run() {
Thread current = Thread.currentThread();//获取当前的线程
if(current == student){
System.out.println(current.getName() + "睡觉");
try {
Thread.sleep(1000*60*60);
} catch (InterruptedException e) {
System.out.println("学生被老师叫醒了");
e.printStackTrace();
}
}else if(current == teacher){
for(int i = 0;i<3;i++){
System.out.println(current.getName() + "说:\"起床\"");
}
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
student.interrupt();//中断学生的休眠状态
}
}
}
结果:
线程的生命周期
几种状态:
创建:new
就绪:被start()后,将进入线程队列等待CPU时间片,此时具备了运行条件,只是没分配到CPU资源
堵塞:在某种特殊情况下,被人为挂起或执行输出操作时,让出CPU并临时中止自己的执行,进入堵塞状态
运行:被调度且获得CPU资源
结束:完成工作或强制退出或异常
线程的同步
public class TicketSell {
//创建三个窗口卖票,总票数为100票,用Runable接口的方式
//问题:卖票过程中,出现了重票、错票-->线程安全问题
//问题出现的原因:当某个线程操作车票过程中,尚未完成时,其他线程参与进来,也操作车票
//解决:当一个线程在操作共享数据(ticket)时,其他线程不能参
// 与进来,直到操作完,其他线程才可以开始操作ticket,这种情况即使线程a出现了堵塞,也不能改变
//两种解决方式
// 方式一: 同步代码块
// synchronized(同步监视器){
// //需要同步的代码
// }
// 说明:1、操作共享数据的代码,即为需要同步的代码
// 2、共享数据:多个线程共同操作的数据
// 3、同步监视器,俗称:锁。任何一个类的对象,都可以称之为锁
// 要求:多个线程必须要共用同一把锁
// 方式二:同步方法
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
private int ticket = 1000;
Object obj = new Object();
@Override
public void run() {
while (true){
//方式1
synchronized(obj) {
if (ticket > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
好处:解决了线程安全问题
局限:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
死锁
不同的线程占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
演示:
package com.atguigu;
public class lock {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}